Add RSSI reporting to FM and AX.25.

This commit is contained in:
Jonathan Naylor
2023-08-04 16:12:38 +01:00
parent 0b69928256
commit fdc917518b
9 changed files with 224 additions and 133 deletions

View File

@@ -30,11 +30,13 @@ const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04
#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) #define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) #define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
CAX25Control::CAX25Control(CAX25Network* network, bool trace) : CAX25Control::CAX25Control(CAX25Network* network, bool trace, CRSSIInterpolator* rssiMapper) :
m_network(network), m_network(network),
m_trace(trace), m_trace(trace),
m_rssiMapper(rssiMapper),
m_enabled(true) m_enabled(true)
{ {
assert(rssiMapper != NULL);
} }
CAX25Control::~CAX25Control() CAX25Control::~CAX25Control()
@@ -48,17 +50,33 @@ bool CAX25Control::writeModem(unsigned char *data, unsigned int len)
if (!m_enabled) if (!m_enabled)
return false; return false;
if (m_trace) bool hasRSSI = data[0U] == TAG_RSSI;
decode(data, len);
decodeJSON("rf", data, len); unsigned int offset = hasRSSI ? 3U : 1U;
int rssi = 0;
if (hasRSSI) {
uint16_t raw = 0U;
raw |= (data[1U] << 8) & 0xFF00U;
raw |= (data[2U] << 0) & 0x00FFU;
// Convert the raw RSSI to dBm
rssi = m_rssiMapper->interpolate(raw);
if (rssi != 0)
LogDebug("AX.25, raw RSSI: %u, reported RSSI: %d dBm", raw, rssi);
}
if (m_trace)
decode(data + offset, len - offset);
decodeJSON("rf", data + offset, len - offset, rssi);
CUtils::dump(1U, "AX.25 received packet", data, len); CUtils::dump(1U, "AX.25 received packet", data, len);
if (m_network == NULL) if (m_network == NULL)
return true; return true;
return m_network->write(data, len); return m_network->write(data + offset, len - offset);
} }
unsigned int CAX25Control::readModem(unsigned char* data) unsigned int CAX25Control::readModem(unsigned char* data)
@@ -185,7 +203,7 @@ void CAX25Control::decode(const unsigned char* data, unsigned int length)
LogMessage("AX.25, %s %.*s", text.c_str(), length - n, data + n); LogMessage("AX.25, %s %.*s", text.c_str(), length - n, data + n);
} }
void CAX25Control::decodeJSON(const char* source, const unsigned char* data, unsigned int length) void CAX25Control::decodeJSON(const char* source, const unsigned char* data, unsigned int length, int rssi)
{ {
assert(source != NULL); assert(source != NULL);
assert(data != NULL); assert(data != NULL);
@@ -205,6 +223,9 @@ void CAX25Control::decodeJSON(const char* source, const unsigned char* data, uns
decodeAddressJSON(data + 0U, text, isDigi); decodeAddressJSON(data + 0U, text, isDigi);
json["destination_cs"] = text; json["destination_cs"] = text;
if (rssi != 0)
json["rssi"] = rssi;
unsigned int n = 14U; unsigned int n = 14U;
if (more) { if (more) {

View File

@@ -19,6 +19,7 @@
#if !defined(AX25Control_H) #if !defined(AX25Control_H)
#define AX25Control_H #define AX25Control_H
#include "RSSIInterpolator.h"
#include "AX25Network.h" #include "AX25Network.h"
#include "Defines.h" #include "Defines.h"
@@ -28,7 +29,7 @@
class CAX25Control { class CAX25Control {
public: public:
CAX25Control(CAX25Network* network, bool trace); CAX25Control(CAX25Network* network, bool trace, CRSSIInterpolator* rssiMapper);
~CAX25Control(); ~CAX25Control();
bool writeModem(unsigned char* data, unsigned int len); bool writeModem(unsigned char* data, unsigned int len);
@@ -40,10 +41,11 @@ public:
private: private:
CAX25Network* m_network; CAX25Network* m_network;
bool m_trace; bool m_trace;
CRSSIInterpolator* m_rssiMapper;
bool m_enabled; bool m_enabled;
void decode(const unsigned char* data, unsigned int length); void decode(const unsigned char* data, unsigned int length);
void decodeJSON(const char* source, const unsigned char* data, unsigned int length); void decodeJSON(const char* source, const unsigned char* data, unsigned int length, int rssi = 0);
bool decodeAddress(const unsigned char* data, std::string& text, bool isDigi = false) const; bool decodeAddress(const unsigned char* data, std::string& text, bool isDigi = false) const;
bool decodeAddressJSON(const unsigned char* data, std::string& text, bool& isDigi) const; bool decodeAddressJSON(const unsigned char* data, std::string& text, bool& isDigi) const;
}; };

View File

@@ -50,6 +50,7 @@ const unsigned char TAG_HEADER = 0x00U;
const unsigned char TAG_DATA = 0x01U; const unsigned char TAG_DATA = 0x01U;
const unsigned char TAG_LOST = 0x02U; const unsigned char TAG_LOST = 0x02U;
const unsigned char TAG_EOT = 0x03U; const unsigned char TAG_EOT = 0x03U;
const unsigned char TAG_RSSI = 0x04U;
const unsigned int DSTAR_MODEM_DATA_LEN = 220U; const unsigned int DSTAR_MODEM_DATA_LEN = 220U;

View File

@@ -43,12 +43,13 @@ const unsigned char FS_TIMEOUT_EXT = 9U;
const unsigned char FS_TIMEOUT_WAIT_EXT = 10U; const unsigned char FS_TIMEOUT_WAIT_EXT = 10U;
const unsigned char FS_HANG = 11U; const unsigned char FS_HANG = 11U;
CFMControl::CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn) : CFMControl::CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn, CRSSIInterpolator* rssiMapper) :
m_network(network), m_network(network),
m_txAudioGain(txAudioGain), m_txAudioGain(txAudioGain),
m_rxAudioGain(rxAudioGain), m_rxAudioGain(rxAudioGain),
m_preEmphasisOn(preEmphasisOn), m_preEmphasisOn(preEmphasisOn),
m_deEmphasisOn(deEmphasisOn), m_deEmphasisOn(deEmphasisOn),
m_rssiMapper(rssiMapper),
m_enabled(false), m_enabled(false),
m_incomingRFAudio(1600U, "Incoming RF FM Audio"), m_incomingRFAudio(1600U, "Incoming RF FM Audio"),
m_preEmphasis(NULL), m_preEmphasis(NULL),
@@ -59,6 +60,7 @@ m_filterStage3(NULL)
{ {
assert(txAudioGain > 0.0F); assert(txAudioGain > 0.0F);
assert(rxAudioGain > 0.0F); assert(rxAudioGain > 0.0F);
assert(rssiMapper != NULL);
m_preEmphasis = new CIIRDirectForm1Filter(8.315375384336983F, -7.03334621603483F,0.0F,1.0F, 0.282029168302153F,0.0F, PREEMPHASIS_GAIN_DB); m_preEmphasis = new CIIRDirectForm1Filter(8.315375384336983F, -7.03334621603483F,0.0F,1.0F, 0.282029168302153F,0.0F, PREEMPHASIS_GAIN_DB);
m_deEmphasis = new CIIRDirectForm1Filter(0.07708787090460224F, 0.07708787090460224F,0.0F, 1.0F, -0.8458242581907955F,0.0F, DEEMPHASIS_GAIN_DB); m_deEmphasis = new CIIRDirectForm1Filter(0.07708787090460224F, 0.07708787090460224F,0.0F, 1.0F, -0.8458242581907955F,0.0F, DEEMPHASIS_GAIN_DB);
@@ -84,9 +86,6 @@ bool CFMControl::writeModem(const unsigned char* data, unsigned int length)
assert(data != NULL); assert(data != NULL);
assert(length > 0U); assert(length > 0U);
if (m_network == NULL)
return true;
if (data[0U] == TAG_HEADER) { if (data[0U] == TAG_HEADER) {
switch (data[1U]) { switch (data[1U]) {
case FS_LISTENING: writeJSON("listening"); break; case FS_LISTENING: writeJSON("listening"); break;
@@ -107,6 +106,24 @@ bool CFMControl::writeModem(const unsigned char* data, unsigned int length)
return true; return true;
} }
if (data[0U] == TAG_RSSI) {
uint16_t raw = 0U;
raw |= (data[0U] << 8) & 0xFF00U;
raw |= (data[1U] << 0) & 0x00FFU;
// Convert the raw RSSI to dBm
int rssi = m_rssiMapper->interpolate(raw);
if (rssi != 0) {
LogDebug("FM, raw RSSI: %u, reported RSSI: %d dBm", raw, rssi);
writeJSONRSSI(rssi);
}
return true;
}
if (m_network == NULL)
return true;
if (data[0U] == TAG_EOT) if (data[0U] == TAG_EOT)
return m_network->writeEnd(); return m_network->writeEnd();
@@ -115,19 +132,21 @@ bool CFMControl::writeModem(const unsigned char* data, unsigned int length)
m_incomingRFAudio.addData(data + 1U, length - 1U); m_incomingRFAudio.addData(data + 1U, length - 1U);
unsigned int bufferLength = m_incomingRFAudio.dataSize(); unsigned int bufferLength = m_incomingRFAudio.dataSize();
if (bufferLength > 240U) // 160 samples 12-bit if (bufferLength > 240U) // 160 samples 12-bit
bufferLength = 240U; // 160 samples 12-bit bufferLength = 240U; // 160 samples 12-bit
if (bufferLength >= 3U) { if (bufferLength >= 3U) {
bufferLength = bufferLength - bufferLength % 3U; // Round down to nearest multiple of 3 bufferLength = bufferLength - bufferLength % 3U; // Round down to nearest multiple of 3
unsigned char bufferData[240U]; // 160 samples 12-bit unsigned char bufferData[240U]; // 160 samples 12-bit
m_incomingRFAudio.getData(bufferData, bufferLength); m_incomingRFAudio.getData(bufferData, bufferLength);
unsigned int pack = 0U; unsigned int pack = 0U;
unsigned char* packPointer = (unsigned char*)&pack; unsigned char* packPointer = (unsigned char*)&pack;
float out[160U]; // 160 samples 12-bit float out[160U]; // 160 samples 12-bit
unsigned int nOut = 0U; unsigned int nOut = 0U;
short unpackedSamples[2U];
for (unsigned int i = 0U; i < bufferLength; i += 3U) { for (unsigned int i = 0U; i < bufferLength; i += 3U) {
// Extract unsigned 12 bit unsigned sample pairs packed into 3 bytes to 16 bit signed // Extract unsigned 12 bit unsigned sample pairs packed into 3 bytes to 16 bit signed
@@ -135,8 +154,9 @@ bool CFMControl::writeModem(const unsigned char* data, unsigned int length)
packPointer[1U] = bufferData[i + 1U]; packPointer[1U] = bufferData[i + 1U];
packPointer[2U] = bufferData[i + 2U]; packPointer[2U] = bufferData[i + 2U];
unpackedSamples[1U] = short(int(pack & FM_MASK) - 2048); short unpackedSamples[2U];
unpackedSamples[0U] = short(int(pack >> 12 & FM_MASK) - 2048); // unpackedSamples[1U] = short(int((pack >> 0) & FM_MASK) - 2048);
unpackedSamples[0U] = short(int((pack >> 12) & FM_MASK) - 2048); //
// Process unpacked sample pair // Process unpacked sample pair
for (unsigned char j = 0U; j < 2U; j++) { for (unsigned char j = 0U; j < 2U; j++) {
@@ -188,7 +208,7 @@ unsigned int CFMControl::readModem(unsigned char* data, unsigned int space)
unsigned int sample12bit = (unsigned int)((sampleFloat + 1.0F) * 2048.0F + 0.5F); unsigned int sample12bit = (unsigned int)((sampleFloat + 1.0F) * 2048.0F + 0.5F);
// Pack 2 samples into 3 bytes // Pack 2 samples into 3 bytes
if ((i & 1U) == 0) { if ((i & 1U) == 0U) {
pack = 0U; pack = 0U;
pack = sample12bit << 12; pack = sample12bit << 12;
} else { } else {
@@ -225,5 +245,15 @@ void CFMControl::writeJSON(const char* state)
WriteJSON("FM", json); WriteJSON("FM", json);
} }
void CFMControl::writeJSONRSSI(int rssi)
{
nlohmann::json json;
json["timestamp"] = CUtils::createTimestamp();
json["mode"] = "FM";
json["value"] = rssi;
WriteJSON("RSSI", json);
}
#endif #endif

View File

@@ -22,6 +22,7 @@
#include "FMNetwork.h" #include "FMNetwork.h"
#include "Defines.h" #include "Defines.h"
#include "IIRDirectForm1Filter.h" #include "IIRDirectForm1Filter.h"
#include "RSSIInterpolator.h"
#if defined(USE_FM) #if defined(USE_FM)
@@ -34,7 +35,7 @@
class CFMControl { class CFMControl {
public: public:
CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn); CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn, CRSSIInterpolator* rssiMapper);
~CFMControl(); ~CFMControl();
bool writeModem(const unsigned char* data, unsigned int length); bool writeModem(const unsigned char* data, unsigned int length);
@@ -51,6 +52,7 @@ private:
float m_rxAudioGain; float m_rxAudioGain;
bool m_preEmphasisOn; bool m_preEmphasisOn;
bool m_deEmphasisOn; bool m_deEmphasisOn;
CRSSIInterpolator* m_rssiMapper;
bool m_enabled; bool m_enabled;
CRingBuffer<unsigned char> m_incomingRFAudio; CRingBuffer<unsigned char> m_incomingRFAudio;
CIIRDirectForm1Filter* m_preEmphasis; CIIRDirectForm1Filter* m_preEmphasis;
@@ -60,6 +62,7 @@ private:
CIIRDirectForm1Filter* m_filterStage3; CIIRDirectForm1Filter* m_filterStage3;
void writeJSON(const char* state); void writeJSON(const char* state);
void writeJSONRSSI(int rssi);
}; };
#endif #endif

View File

@@ -352,7 +352,6 @@ int CMMDVMHost::run()
} }
#endif #endif
#endif #endif
::LogInitialise(m_conf.getLogDisplayLevel(), m_conf.getLogMQTTLevel()); ::LogInitialise(m_conf.getLogDisplayLevel(), m_conf.getLogMQTTLevel());
std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>> subscriptions; std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>> subscriptions;
@@ -878,7 +877,7 @@ int CMMDVMHost::run()
LogInfo(" P-Persist: %u", pPersist); LogInfo(" P-Persist: %u", pPersist);
LogInfo(" Trace: %s", trace ? "yes" : "no"); LogInfo(" Trace: %s", trace ? "yes" : "no");
m_ax25 = new CAX25Control(m_ax25Network, trace); m_ax25 = new CAX25Control(m_ax25Network, trace, rssi);
} }
#endif #endif
@@ -890,7 +889,7 @@ int CMMDVMHost::run()
float rxAudioGain = m_conf.getFMRXAudioGain(); float rxAudioGain = m_conf.getFMRXAudioGain();
m_fmRFModeHang = m_conf.getFMModeHang(); m_fmRFModeHang = m_conf.getFMModeHang();
m_fm = new CFMControl(m_fmNetwork, txAudioGain, rxAudioGain, preEmphasis, deEmphasis); m_fm = new CFMControl(m_fmNetwork, txAudioGain, rxAudioGain, preEmphasis, deEmphasis, rssi);
} }
#endif #endif
@@ -1119,7 +1118,7 @@ int CMMDVMHost::run()
if (m_mode == MODE_IDLE || m_mode == MODE_FM) { if (m_mode == MODE_IDLE || m_mode == MODE_FM) {
m_ax25->writeModem(data, len); m_ax25->writeModem(data, len);
} else if (m_mode != MODE_LOCKOUT) { } else if (m_mode != MODE_LOCKOUT) {
LogWarning("NXDN modem data received when in mode %u", m_mode); LogWarning("AX.25 modem data received when in mode %u", m_mode);
} }
} }
#endif #endif

View File

@@ -98,6 +98,7 @@ const unsigned char MMDVM_POCSAG_DATA = 0x50U;
#if defined(USE_AX25) #if defined(USE_AX25)
const unsigned char MMDVM_AX25_DATA = 0x55U; const unsigned char MMDVM_AX25_DATA = 0x55U;
const unsigned char MMDVM_AX25_DATA_EX = 0x56U;
#endif #endif
#if defined(USE_FM) #if defined(USE_FM)
@@ -108,6 +109,7 @@ const unsigned char MMDVM_FM_PARAMS4 = 0x63U;
const unsigned char MMDVM_FM_DATA = 0x65U; const unsigned char MMDVM_FM_DATA = 0x65U;
const unsigned char MMDVM_FM_STATUS = 0x66U; const unsigned char MMDVM_FM_STATUS = 0x66U;
const unsigned char MMDVM_FM_EOT = 0x67U; const unsigned char MMDVM_FM_EOT = 0x67U;
const unsigned char MMDVM_FM_RSSI = 0x68U;
#endif #endif
const unsigned char MMDVM_ACK = 0x70U; const unsigned char MMDVM_ACK = 0x70U;
@@ -929,6 +931,20 @@ void CModem::clock(unsigned int ms)
m_rxFMData.addData(m_buffer + m_offset, m_length - m_offset); m_rxFMData.addData(m_buffer + m_offset, m_length - m_offset);
} }
break; break;
case MMDVM_FM_RSSI: {
if(m_trace)
CUtils::dump(1U, "RX FM RSSI", m_buffer, m_length);
unsigned int data1 = m_length - m_offset + 1U;
m_rxFMData.addData((unsigned char*)&data1, sizeof(unsigned int));
unsigned char data2 = TAG_RSSI;
m_rxFMData.addData(&data2, 1U);
m_rxFMData.addData(m_buffer + m_offset, m_length - m_offset);
}
break;
#endif #endif
#if defined(USE_AX25) #if defined(USE_AX25)
@@ -936,8 +952,25 @@ void CModem::clock(unsigned int ms)
if (m_trace) if (m_trace)
CUtils::dump(1U, "RX AX.25 Data", m_buffer, m_length); CUtils::dump(1U, "RX AX.25 Data", m_buffer, m_length);
unsigned int data = m_length - m_offset; unsigned int data1 = m_length - m_offset + 1U;
m_rxAX25Data.addData((unsigned char*)&data, sizeof(unsigned int)); m_rxAX25Data.addData((unsigned char*)&data1, sizeof(unsigned int));
unsigned char data2 = TAG_DATA;
m_rxFMData.addData(&data2, 1U);
m_rxAX25Data.addData(m_buffer + m_offset, m_length - m_offset);
}
break;
case MMDVM_AX25_DATA_EX: {
if (m_trace)
CUtils::dump(1U, "RX AX.25 Data Extended", m_buffer, m_length);
unsigned int data1 = m_length - m_offset + 1U;
m_rxAX25Data.addData((unsigned char*)&data1, sizeof(unsigned int));
unsigned char data2 = TAG_RSSI;
m_rxFMData.addData(&data2, 1U);
m_rxAX25Data.addData(m_buffer + m_offset, m_length - m_offset); m_rxAX25Data.addData(m_buffer + m_offset, m_length - m_offset);
} }

View File

@@ -19,6 +19,6 @@
#if !defined(VERSION_H) #if !defined(VERSION_H)
#define VERSION_H #define VERSION_H
const char* VERSION = "20230730"; const char* VERSION = "20230804";
#endif #endif

View File

@@ -198,6 +198,8 @@
}, },
"type": {"$ref": "#/$defs/ax25_type"}, "type": {"$ref": "#/$defs/ax25_type"},
"pid": {"$ref": "#/$defs/ax25_pid"}, "pid": {"$ref": "#/$defs/ax25_pid"},
"rssi": {"$ref": "#/$defs/rssi"},
"ber": {"$ref": "#/$defs/ber"},
"data": {"type": "string"}, "data": {"type": "string"},
"required": ["timestamp", "source", "destination", "source", "type"] "required": ["timestamp", "source", "destination", "source", "type"]
}, },