mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 19:05:07 +00:00
Mesh gets contacts, channels, delivery receipts, QR sharing and an ESP-NOW radio. Mesh Chat is split into Mesh Contacts and Mesh Channels, `mesh inspect` replaces the Mesh Inspect app, and ESP-NOW and Wi-Fi now share the Wi-Fi radio safely.
253 lines
7.7 KiB
C++
253 lines
7.7 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Mock packet radio for the simulator. All started mock radios share an in-process ether:
|
|
// a transmission is delivered right away to every other mock radio that is on and tuned to the
|
|
// same modulation, frequency, sync word, spreading factor and bandwidth.
|
|
|
|
#include <tactility/concurrent/mutex.h>
|
|
#include <tactility/device.h>
|
|
#include <tactility/driver.h>
|
|
#include <tactility/drivers/radio.h>
|
|
#include <tactility/log.h>
|
|
|
|
#include <new>
|
|
|
|
#define TAG "mock_radio"
|
|
|
|
namespace {
|
|
|
|
constexpr int PARAMETER_COUNT = RADIO_PARAMETER_CHANNEL + 1;
|
|
constexpr float MOCK_RX_RSSI = -60.0f;
|
|
constexpr float MOCK_RX_SNR = 9.5f;
|
|
constexpr float MOCK_NOISE_FLOOR = -120.0f;
|
|
|
|
struct MockRadioCtx {
|
|
Device* device = nullptr;
|
|
RadioState state = RADIO_STATE_OFF;
|
|
RadioModulation modulation = RADIO_MODULATION_NONE;
|
|
int32_t parameters[PARAMETER_COUNT] = {};
|
|
RadioTxId nextTxId = 0;
|
|
MockRadioCtx* next = nullptr;
|
|
};
|
|
|
|
struct EtherMutex {
|
|
Mutex handle {};
|
|
EtherMutex() { mutex_construct(&handle); }
|
|
~EtherMutex() { mutex_destruct(&handle); }
|
|
};
|
|
|
|
// Guards the ether list and the state of every mock radio on it
|
|
EtherMutex etherMutex;
|
|
MockRadioCtx* ether = nullptr;
|
|
|
|
#define GET_CTX(device) (static_cast<MockRadioCtx*>(device_get_driver_data(device)))
|
|
|
|
bool isTunedAlike(const MockRadioCtx* a, const MockRadioCtx* b) {
|
|
constexpr RadioParameter matching[] = {
|
|
RADIO_PARAMETER_FREQUENCY,
|
|
RADIO_PARAMETER_SYNC_WORD,
|
|
RADIO_PARAMETER_SPREADING_FACTOR,
|
|
RADIO_PARAMETER_BANDWIDTH,
|
|
};
|
|
if (a->modulation != b->modulation) {
|
|
return false;
|
|
}
|
|
for (auto parameter : matching) {
|
|
if (a->parameters[parameter] != b->parameters[parameter]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void setState(MockRadioCtx* ctx, RadioState state) {
|
|
ctx->state = state;
|
|
radio_state_event_emit(ctx->device, state);
|
|
}
|
|
|
|
error_t apiGetRadioState(Device* device, RadioState* state) {
|
|
mutex_lock(ðerMutex.handle);
|
|
*state = GET_CTX(device)->state;
|
|
mutex_unlock(ðerMutex.handle);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
error_t apiSetEnabled(Device* device, bool enabled) {
|
|
auto* ctx = GET_CTX(device);
|
|
mutex_lock(ðerMutex.handle);
|
|
error_t result = ERROR_NONE;
|
|
if (enabled) {
|
|
if (ctx->modulation == RADIO_MODULATION_NONE) {
|
|
result = ERROR_INVALID_STATE;
|
|
} else if (ctx->state != RADIO_STATE_ON) {
|
|
setState(ctx, RADIO_STATE_ON_PENDING);
|
|
setState(ctx, RADIO_STATE_ON);
|
|
}
|
|
} else if (ctx->state != RADIO_STATE_OFF) {
|
|
setState(ctx, RADIO_STATE_OFF_PENDING);
|
|
setState(ctx, RADIO_STATE_OFF);
|
|
}
|
|
mutex_unlock(ðerMutex.handle);
|
|
return result;
|
|
}
|
|
|
|
error_t apiSetModulation(Device* device, RadioModulation modulation) {
|
|
auto* ctx = GET_CTX(device);
|
|
mutex_lock(ðerMutex.handle);
|
|
error_t result = ERROR_NONE;
|
|
if (ctx->state == RADIO_STATE_ON || ctx->state == RADIO_STATE_ON_PENDING) {
|
|
result = ERROR_INVALID_STATE;
|
|
} else {
|
|
ctx->modulation = modulation;
|
|
}
|
|
mutex_unlock(ðerMutex.handle);
|
|
return result;
|
|
}
|
|
|
|
error_t apiGetModulation(Device* device, RadioModulation* modulation) {
|
|
mutex_lock(ðerMutex.handle);
|
|
*modulation = GET_CTX(device)->modulation;
|
|
mutex_unlock(ðerMutex.handle);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
bool apiCanTransmitOrReceive(Device*, RadioModulation modulation) {
|
|
return modulation != RADIO_MODULATION_NONE;
|
|
}
|
|
|
|
error_t apiSetParameter(Device* device, RadioParameter parameter, int32_t value) {
|
|
if (parameter < 0 || parameter >= PARAMETER_COUNT) {
|
|
return ERROR_NOT_SUPPORTED;
|
|
}
|
|
mutex_lock(ðerMutex.handle);
|
|
GET_CTX(device)->parameters[parameter] = value;
|
|
mutex_unlock(ðerMutex.handle);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
error_t apiGetParameter(Device* device, RadioParameter parameter, int32_t* value) {
|
|
if (parameter < 0 || parameter >= PARAMETER_COUNT) {
|
|
return ERROR_NOT_SUPPORTED;
|
|
}
|
|
mutex_lock(ðerMutex.handle);
|
|
*value = GET_CTX(device)->parameters[parameter];
|
|
mutex_unlock(ðerMutex.handle);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
error_t apiTransmit(Device* device, const uint8_t* data, size_t length, RadioTxId* id) {
|
|
auto* ctx = GET_CTX(device);
|
|
mutex_lock(ðerMutex.handle);
|
|
const RadioTxId txId = ctx->nextTxId++;
|
|
if (id != nullptr) {
|
|
*id = txId;
|
|
}
|
|
radio_tx_event_emit(device, txId, RADIO_TRANSMISSION_STATE_QUEUED);
|
|
|
|
if (ctx->state != RADIO_STATE_ON) {
|
|
radio_tx_event_emit(device, txId, RADIO_TRANSMISSION_STATE_ERROR);
|
|
mutex_unlock(ðerMutex.handle);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
radio_tx_event_emit(device, txId, RADIO_TRANSMISSION_STATE_TRANSMIT_PENDING);
|
|
for (MockRadioCtx* peer = ether; peer != nullptr; peer = peer->next) {
|
|
if (peer != ctx && peer->state == RADIO_STATE_ON && isTunedAlike(ctx, peer)) {
|
|
radio_rx_event_emit(peer->device, data, length, MOCK_RX_RSSI, MOCK_RX_SNR);
|
|
}
|
|
}
|
|
radio_tx_event_emit(device, txId, RADIO_TRANSMISSION_STATE_TRANSMITTED);
|
|
mutex_unlock(ðerMutex.handle);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
error_t apiGetRssi(Device* device, float* rssi) {
|
|
mutex_lock(ðerMutex.handle);
|
|
const bool on = GET_CTX(device)->state == RADIO_STATE_ON;
|
|
mutex_unlock(ðerMutex.handle);
|
|
if (!on) return ERROR_INVALID_STATE;
|
|
*rssi = MOCK_NOISE_FLOOR;
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
error_t apiIsReceiving(Device* device, bool* receiving) {
|
|
mutex_lock(ðerMutex.handle);
|
|
const bool on = GET_CTX(device)->state == RADIO_STATE_ON;
|
|
mutex_unlock(ðerMutex.handle);
|
|
if (!on) return ERROR_INVALID_STATE;
|
|
// Delivery is instantaneous, so a reception is never in progress
|
|
*receiving = false;
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
error_t startDevice(Device* device) {
|
|
auto* ctx = new (std::nothrow) MockRadioCtx();
|
|
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
|
|
|
|
ctx->device = device;
|
|
ctx->parameters[RADIO_PARAMETER_FREQUENCY] = 869525000;
|
|
ctx->parameters[RADIO_PARAMETER_BANDWIDTH] = 250000;
|
|
ctx->parameters[RADIO_PARAMETER_SPREADING_FACTOR] = 11;
|
|
ctx->parameters[RADIO_PARAMETER_CODING_RATE] = 5;
|
|
ctx->parameters[RADIO_PARAMETER_SYNC_WORD] = 0x12;
|
|
ctx->parameters[RADIO_PARAMETER_PREAMBLE_LENGTH] = 16;
|
|
ctx->parameters[RADIO_PARAMETER_POWER] = 14;
|
|
ctx->parameters[RADIO_PARAMETER_LBT_RSSI_THRESHOLD] = -90;
|
|
|
|
mutex_lock(ðerMutex.handle);
|
|
ctx->next = ether;
|
|
ether = ctx;
|
|
mutex_unlock(ðerMutex.handle);
|
|
|
|
device_set_driver_data(device, ctx);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
error_t stopDevice(Device* device) {
|
|
auto* ctx = GET_CTX(device);
|
|
if (ctx == nullptr) return ERROR_NONE;
|
|
|
|
mutex_lock(ðerMutex.handle);
|
|
for (MockRadioCtx** link = ðer; *link != nullptr; link = &(*link)->next) {
|
|
if (*link == ctx) {
|
|
*link = ctx->next;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(ðerMutex.handle);
|
|
|
|
device_set_driver_data(device, nullptr);
|
|
delete ctx;
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
const RadioApi mock_radio_api = {
|
|
.get_radio_state = apiGetRadioState,
|
|
.set_enabled = apiSetEnabled,
|
|
.set_modulation = apiSetModulation,
|
|
.get_modulation = apiGetModulation,
|
|
.can_transmit = apiCanTransmitOrReceive,
|
|
.can_receive = apiCanTransmitOrReceive,
|
|
.set_parameter = apiSetParameter,
|
|
.get_parameter = apiGetParameter,
|
|
.transmit = apiTransmit,
|
|
.get_rssi = apiGetRssi,
|
|
.is_receiving = apiIsReceiving,
|
|
};
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
Driver mock_radio_driver = {
|
|
.name = "mock_radio",
|
|
.compatible = (const char*[]) { "mock,radio", nullptr },
|
|
.start_device = startDevice,
|
|
.stop_device = stopDevice,
|
|
.api = (const void*)&mock_radio_api,
|
|
.device_type = &RADIO_TYPE,
|
|
.internal = nullptr
|
|
};
|
|
|
|
} // extern "C"
|