Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 45 additions & 9 deletions main/espFeatures/simpleRadioFeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#include <jac/machine/functionFactory.h>
#include <jac/machine/machine.h>

#include <esp_wifi.h>
#include <simple_radio.h>
#include <sstream>
#include <iomanip>


template<>
Expand All @@ -17,6 +19,8 @@ struct jac::ConvTraits<PacketDataType> {
return Value::from(ctx, "string");
case PacketDataType::KeyValue:
return Value::from(ctx, "keyvalue");
case PacketDataType::Blob:
return Value::from(ctx, "blob");
}
}

Expand All @@ -31,22 +35,26 @@ struct jac::ConvTraits<PacketDataType> {
else if (str == "keyvalue") {
return PacketDataType::KeyValue;
}
else if (str == "blob") {
return PacketDataType::Blob;
}
else {
throw std::runtime_error("Invalid PacketDataType");
}
}
};

using EspBdAddress = std::array<uint8_t, ESP_BD_ADDR_LEN>;
using EspBdAddress = std::array<uint8_t, sizeof(simple_radio_addr_t)>;

template<>
struct jac::ConvTraits<EspBdAddress> {
static Value to(ContextRef ctx, EspBdAddress val) {
std::stringstream ss;
ss << std::hex;
for (int i = 0; i < 6; i++) {
ss << static_cast<int>(val[i]);
if (i != 5) {
ss << std::setfill('0');
for (size_t i = 0; i < val.size(); i++) {
ss << std::setw(2) << static_cast<int>(val[i]);
if (i + 1 != val.size()) {
ss << ":";
}
}
Expand All @@ -58,11 +66,11 @@ struct jac::ConvTraits<EspBdAddress> {
EspBdAddress addr;
std::stringstream ss(str);
ss >> std::hex;
for (int i = 0; i < 6; i++) {
for (size_t i = 0; i < addr.size(); i++) {
int byte;
ss >> byte;
addr[i] = static_cast<uint8_t>(byte);
if (i != 5) {
if (i + 1 != addr.size()) {
char c;
ss >> c;
if (c != ':') {
Expand All @@ -78,7 +86,7 @@ template<>
struct jac::ConvTraits<PacketInfo> {
static Value to(ContextRef ctx, PacketInfo val) {
EspBdAddress addr;
std::copy(val.addr, val.addr + ESP_BD_ADDR_LEN, addr.begin());
std::copy(std::begin(val.addr), std::end(val.addr), addr.begin());
auto obj = Object::create(ctx);
obj.set("group", static_cast<int>(val.group));
obj.set("address", addr);
Expand All @@ -92,7 +100,7 @@ struct jac::ConvTraits<PacketInfo> {

PacketInfo info;
info.group = obj.get<int>("group");
std::copy(addr.begin(), addr.end(), info.addr);
std::copy(addr.begin(), addr.end(), std::begin(info.addr));
info.rssi = obj.get<int>("rssi");
return info;
}
Expand All @@ -112,6 +120,13 @@ class SimpleRadioFeature : public Next {
simpleradioModule.addExport("begin", ff.newFunction(noal::function([](int group) {
auto config = SimpleRadio.DEFAULT_CONFIG;
config.init_nvs = false; // Jaculus-Esp32 initializes it
wifi_mode_t wifiMode = WIFI_MODE_NULL;
if (esp_wifi_get_mode(&wifiMode) == ESP_OK) {
config.init_netif = false;
config.init_event_loop = false;
config.init_wifi = false;
config.channel = 0;
}
esp_err_t err = SimpleRadio.begin(group, config);
if (err != ESP_OK) {
throw std::runtime_error("Failed to initialize SimpleRadio: " + std::to_string(err));
Expand All @@ -124,11 +139,20 @@ class SimpleRadioFeature : public Next {
SimpleRadio.address(res.data());
return res;
})));
simpleradioModule.addExport("addressBytes", ff.newFunction(noal::function([this]() {
std::array<uint8_t, sizeof(simple_radio_addr_t)> mac;
SimpleRadio.address(mac.data());
return this->toUint8Array(std::vector<uint8_t>(mac.begin(), mac.end()));
})));
simpleradioModule.addExport("sendString", ff.newFunction(noal::function([](std::string str) { SimpleRadio.sendString(str); })));
simpleradioModule.addExport("sendNumber", ff.newFunction(noal::function([](int num) { SimpleRadio.sendNumber(num); })));
simpleradioModule.addExport("sendNumber", ff.newFunction(noal::function([](double num) { SimpleRadio.sendNumber(num); })));
simpleradioModule.addExport("sendKeyValue", ff.newFunction(noal::function([](std::string key, double value) {
SimpleRadio.sendKeyValue(key, value);
})));
simpleradioModule.addExport("sendBlob", ff.newFunction(noal::function([this](jac::Value data) {
auto dataVec = this->toStdVector(data);
SimpleRadio.sendBlob(dataVec);
})));
simpleradioModule.addExport("on", ff.newFunction(noal::function([this](PacketDataType type, jac::Function callback) {
switch (type) {
case PacketDataType::Number:
Expand All @@ -152,6 +176,14 @@ class SimpleRadioFeature : public Next {
});
});
break;
case PacketDataType::Blob:
SimpleRadio.setOnBlobCallback([this, callback](std::span<const uint8_t> data, PacketInfo info) mutable {
auto dataVec = std::vector<uint8_t>(data.begin(), data.end());
this->scheduleEvent([this, callback, data = std::move(dataVec), info]() mutable {
callback.call<void>(this->toUint8Array(data), info);
});
});
break;
}
})));
simpleradioModule.addExport("off", ff.newFunction(noal::function([](PacketDataType type) {
Expand All @@ -165,6 +197,9 @@ class SimpleRadioFeature : public Next {
case PacketDataType::KeyValue:
SimpleRadio.setOnKeyValueCallback(nullptr);
break;
case PacketDataType::Blob:
SimpleRadio.setOnBlobCallback(nullptr);
break;
}
})));
simpleradioModule.addExport("end", ff.newFunction(noal::function([]() { SimpleRadio.end(); })));
Expand All @@ -174,6 +209,7 @@ class SimpleRadioFeature : public Next {
SimpleRadio.setOnNumberCallback(nullptr);
SimpleRadio.setOnStringCallback(nullptr);
SimpleRadio.setOnKeyValueCallback(nullptr);
SimpleRadio.setOnBlobCallback(nullptr);
SimpleRadio.end();
}
};
22 changes: 22 additions & 0 deletions main/espFeatures/wifiFeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

#include "../platform/espWifi.h"

#include "esp_mac.h"
#include <array>
#include <iomanip>
#include <sstream>

template<class Next>
class WifiFeature : public Next {
public:
Expand All @@ -32,5 +37,22 @@ class WifiFeature : public Next {
}
return nsHandle->keys();
})));

module.addExport("address", ff.newFunction(noal::function([]() {
std::array<uint8_t, 6> mac{};
if (esp_efuse_mac_get_default(mac.data()) != ESP_OK) {
throw std::runtime_error("Failed to read device MAC address");
}

std::stringstream ss;
ss << std::hex << std::setfill('0');
for (size_t i = 0; i < mac.size(); ++i) {
ss << std::setw(2) << static_cast<int>(mac[i]);
if (i + 1 != mac.size()) {
ss << ":";
}
}
return ss.str();
})));
}
};
3 changes: 2 additions & 1 deletion main/idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ dependencies:
path: src
simple-radio:
git: https://github.com/RoboticsBrno/Esp32-simple-radio.git
version: v1.2.2
version: v2.1.2
override_path: ../../Esp32-simple-radio/
SmartLeds:
git: https://github.com/RoboticsBrno/SmartLeds.git
version: v3.1.6
Expand Down
18 changes: 16 additions & 2 deletions ts-examples/@types/simpleradio.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
declare module "simpleradio" {
type PacketDataType = "number" | "string" | "keyvalue";
type PacketDataType = "number" | "string" | "keyvalue" | "blob";
type BlobData = ArrayBuffer | Uint8Array | number[];

interface PacketInfo {
group: number;
Expand All @@ -15,7 +16,7 @@ declare module "simpleradio" {

/**
* Set the radio group.
* @param group The radio group to use, between 0 and 15 inclusive.
* @param group The radio group to use, between 0 and 255 inclusive.
*/
function setGroup(group: number): void;

Expand Down Expand Up @@ -50,6 +51,12 @@ declare module "simpleradio" {
*/
function sendKeyValue(key: string, value: number): void;

/**
* Send a binary blob.
* @param data The binary data to send.
*/
function sendBlob(data: BlobData): void;

/**
* Register a callback for a packet type.
* @param type The packet type to register for.
Expand All @@ -71,6 +78,13 @@ declare module "simpleradio" {
*/
function on(type: "keyvalue", callback: (key: string, value: number, info: PacketInfo) => void): void;

/**
* Register a callback for a packet type.
* @param type The packet type to register for.
* @param callback The callback to register.
*/
function on(type: "blob", callback: (data: Uint8Array, info: PacketInfo) => void): void;

/**
* Unregister a callback for a packet type.
* @param type The packet type to unregister for.
Expand Down
90 changes: 90 additions & 0 deletions ts-examples/src/radiopresence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as simpleradio from "simpleradio";
import * as wifi from "wifi";

/**
* Presence test for SimpleRadio v2.
*
* On startup:
* - uses MAC address in standard string form as device identity
* - broadcasts it periodically
* - listens for identities from other devices
*
* Run this on multiple boards in the same group and watch logs.
*/

const GROUP = 1;
const BROADCAST_PERIOD_MS = 1000;
const PEER_TIMEOUT_MS = 5000;

const myAddress = wifi.address();

interface PeerState {
id: string;
rssi: number;
lastSeenMs: number;
}

const peers: { [address: string]: PeerState } = {};

function nowMs(): number {
return Date.now();
}

function printPeers(): void {
const now = nowMs();
const entries: string[] = [];

for (const address in peers) {
const peer = peers[address];
const age = now - peer.lastSeenMs;

if (age > PEER_TIMEOUT_MS) {
continue;
}

entries.push(`${peer.id}@${address} (RSSI ${peer.rssi}, ${age}ms ago)`);
}

if (entries.length === 0) {
console.log("No peers visible yet.");
}
else {
console.log(`Visible peers (${entries.length}): ${entries.join(", ")}`);
}
}

function announce(): void {
// Send our identity as standard MAC string.
simpleradio.sendString(myAddress);
}

simpleradio.begin(GROUP);

console.log(`SimpleRadio presence test started`);
console.log(`Group: ${simpleradio.group()}, Radio Address: ${simpleradio.address()}, WiFi Address: ${myAddress}`);

simpleradio.on("string", (peerId, info) => {

// Ignore our own packets (self-loop can happen on some setups).
if (peerId === myAddress) {
return;
}

const firstSeen = peers[info.address] === undefined;
peers[info.address] = {
id: peerId,
rssi: info.rssi,
lastSeenMs: nowMs(),
};

if (firstSeen) {
console.log(`Discovered peer ${peerId} at ${info.address} (RSSI ${info.rssi})`);
}
else {
console.log(`Updated peer ${peerId} at ${info.address} (RSSI ${info.rssi})`);
}
});

announce();
setInterval(announce, BROADCAST_PERIOD_MS);
setInterval(printPeers, BROADCAST_PERIOD_MS);
Loading