mirror of
https://github.com/g4klx/MMDVMHost
synced 2025-12-22 16:25:45 +08:00
Add RSSI reporting to FM and AX.25.
This commit is contained in:
@@ -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 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_trace(trace),
|
||||
m_rssiMapper(rssiMapper),
|
||||
m_enabled(true)
|
||||
{
|
||||
assert(rssiMapper != NULL);
|
||||
}
|
||||
|
||||
CAX25Control::~CAX25Control()
|
||||
@@ -48,17 +50,33 @@ bool CAX25Control::writeModem(unsigned char *data, unsigned int len)
|
||||
if (!m_enabled)
|
||||
return false;
|
||||
|
||||
if (m_trace)
|
||||
decode(data, len);
|
||||
bool hasRSSI = data[0U] == TAG_RSSI;
|
||||
|
||||
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);
|
||||
|
||||
if (m_network == NULL)
|
||||
return true;
|
||||
|
||||
return m_network->write(data, len);
|
||||
return m_network->write(data + offset, len - offset);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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(data != NULL);
|
||||
@@ -205,6 +223,9 @@ void CAX25Control::decodeJSON(const char* source, const unsigned char* data, uns
|
||||
decodeAddressJSON(data + 0U, text, isDigi);
|
||||
json["destination_cs"] = text;
|
||||
|
||||
if (rssi != 0)
|
||||
json["rssi"] = rssi;
|
||||
|
||||
unsigned int n = 14U;
|
||||
|
||||
if (more) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#if !defined(AX25Control_H)
|
||||
#define AX25Control_H
|
||||
|
||||
#include "RSSIInterpolator.h"
|
||||
#include "AX25Network.h"
|
||||
#include "Defines.h"
|
||||
|
||||
@@ -28,7 +29,7 @@
|
||||
|
||||
class CAX25Control {
|
||||
public:
|
||||
CAX25Control(CAX25Network* network, bool trace);
|
||||
CAX25Control(CAX25Network* network, bool trace, CRSSIInterpolator* rssiMapper);
|
||||
~CAX25Control();
|
||||
|
||||
bool writeModem(unsigned char* data, unsigned int len);
|
||||
@@ -40,10 +41,11 @@ public:
|
||||
private:
|
||||
CAX25Network* m_network;
|
||||
bool m_trace;
|
||||
CRSSIInterpolator* m_rssiMapper;
|
||||
bool m_enabled;
|
||||
|
||||
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 decodeAddressJSON(const unsigned char* data, std::string& text, bool& isDigi) const;
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ const unsigned char TAG_HEADER = 0x00U;
|
||||
const unsigned char TAG_DATA = 0x01U;
|
||||
const unsigned char TAG_LOST = 0x02U;
|
||||
const unsigned char TAG_EOT = 0x03U;
|
||||
const unsigned char TAG_RSSI = 0x04U;
|
||||
|
||||
const unsigned int DSTAR_MODEM_DATA_LEN = 220U;
|
||||
|
||||
|
||||
@@ -43,12 +43,13 @@ const unsigned char FS_TIMEOUT_EXT = 9U;
|
||||
const unsigned char FS_TIMEOUT_WAIT_EXT = 10U;
|
||||
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_txAudioGain(txAudioGain),
|
||||
m_rxAudioGain(rxAudioGain),
|
||||
m_preEmphasisOn(preEmphasisOn),
|
||||
m_deEmphasisOn(deEmphasisOn),
|
||||
m_rssiMapper(rssiMapper),
|
||||
m_enabled(false),
|
||||
m_incomingRFAudio(1600U, "Incoming RF FM Audio"),
|
||||
m_preEmphasis(NULL),
|
||||
@@ -59,6 +60,7 @@ m_filterStage3(NULL)
|
||||
{
|
||||
assert(txAudioGain > 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_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(length > 0U);
|
||||
|
||||
if (m_network == NULL)
|
||||
return true;
|
||||
|
||||
if (data[0U] == TAG_HEADER) {
|
||||
switch (data[1U]) {
|
||||
case FS_LISTENING: writeJSON("listening"); break;
|
||||
@@ -107,6 +106,24 @@ bool CFMControl::writeModem(const unsigned char* data, unsigned int length)
|
||||
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)
|
||||
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);
|
||||
unsigned int bufferLength = m_incomingRFAudio.dataSize();
|
||||
|
||||
if (bufferLength > 240U) // 160 samples 12-bit
|
||||
bufferLength = 240U; // 160 samples 12-bit
|
||||
|
||||
if (bufferLength >= 3U) {
|
||||
bufferLength = bufferLength - bufferLength % 3U; // Round down to nearest multiple of 3
|
||||
|
||||
unsigned char bufferData[240U]; // 160 samples 12-bit
|
||||
m_incomingRFAudio.getData(bufferData, bufferLength);
|
||||
|
||||
unsigned int pack = 0U;
|
||||
unsigned char* packPointer = (unsigned char*)&pack;
|
||||
|
||||
float out[160U]; // 160 samples 12-bit
|
||||
unsigned int nOut = 0U;
|
||||
short unpackedSamples[2U];
|
||||
|
||||
for (unsigned int i = 0U; i < bufferLength; i += 3U) {
|
||||
// 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[2U] = bufferData[i + 2U];
|
||||
|
||||
unpackedSamples[1U] = short(int(pack & FM_MASK) - 2048);
|
||||
unpackedSamples[0U] = short(int(pack >> 12 & FM_MASK) - 2048); //
|
||||
short unpackedSamples[2U];
|
||||
unpackedSamples[1U] = short(int((pack >> 0) & FM_MASK) - 2048);
|
||||
unpackedSamples[0U] = short(int((pack >> 12) & FM_MASK) - 2048); //
|
||||
|
||||
// Process unpacked sample pair
|
||||
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);
|
||||
|
||||
// Pack 2 samples into 3 bytes
|
||||
if ((i & 1U) == 0) {
|
||||
if ((i & 1U) == 0U) {
|
||||
pack = 0U;
|
||||
pack = sample12bit << 12;
|
||||
} else {
|
||||
@@ -225,5 +245,15 @@ void CFMControl::writeJSON(const char* state)
|
||||
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
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "FMNetwork.h"
|
||||
#include "Defines.h"
|
||||
#include "IIRDirectForm1Filter.h"
|
||||
#include "RSSIInterpolator.h"
|
||||
|
||||
#if defined(USE_FM)
|
||||
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
class CFMControl {
|
||||
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();
|
||||
|
||||
bool writeModem(const unsigned char* data, unsigned int length);
|
||||
@@ -51,6 +52,7 @@ private:
|
||||
float m_rxAudioGain;
|
||||
bool m_preEmphasisOn;
|
||||
bool m_deEmphasisOn;
|
||||
CRSSIInterpolator* m_rssiMapper;
|
||||
bool m_enabled;
|
||||
CRingBuffer<unsigned char> m_incomingRFAudio;
|
||||
CIIRDirectForm1Filter* m_preEmphasis;
|
||||
@@ -60,6 +62,7 @@ private:
|
||||
CIIRDirectForm1Filter* m_filterStage3;
|
||||
|
||||
void writeJSON(const char* state);
|
||||
void writeJSONRSSI(int rssi);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -352,7 +352,6 @@ int CMMDVMHost::run()
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
::LogInitialise(m_conf.getLogDisplayLevel(), m_conf.getLogMQTTLevel());
|
||||
|
||||
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(" Trace: %s", trace ? "yes" : "no");
|
||||
|
||||
m_ax25 = new CAX25Control(m_ax25Network, trace);
|
||||
m_ax25 = new CAX25Control(m_ax25Network, trace, rssi);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -890,7 +889,7 @@ int CMMDVMHost::run()
|
||||
float rxAudioGain = m_conf.getFMRXAudioGain();
|
||||
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
|
||||
|
||||
@@ -1119,7 +1118,7 @@ int CMMDVMHost::run()
|
||||
if (m_mode == MODE_IDLE || m_mode == MODE_FM) {
|
||||
m_ax25->writeModem(data, len);
|
||||
} 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
|
||||
|
||||
37
Modem.cpp
37
Modem.cpp
@@ -98,6 +98,7 @@ const unsigned char MMDVM_POCSAG_DATA = 0x50U;
|
||||
|
||||
#if defined(USE_AX25)
|
||||
const unsigned char MMDVM_AX25_DATA = 0x55U;
|
||||
const unsigned char MMDVM_AX25_DATA_EX = 0x56U;
|
||||
#endif
|
||||
|
||||
#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_STATUS = 0x66U;
|
||||
const unsigned char MMDVM_FM_EOT = 0x67U;
|
||||
const unsigned char MMDVM_FM_RSSI = 0x68U;
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
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
|
||||
|
||||
#if defined(USE_AX25)
|
||||
@@ -936,8 +952,25 @@ void CModem::clock(unsigned int ms)
|
||||
if (m_trace)
|
||||
CUtils::dump(1U, "RX AX.25 Data", m_buffer, m_length);
|
||||
|
||||
unsigned int data = m_length - m_offset;
|
||||
m_rxAX25Data.addData((unsigned char*)&data, sizeof(unsigned int));
|
||||
unsigned int data1 = m_length - m_offset + 1U;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
#if !defined(VERSION_H)
|
||||
#define VERSION_H
|
||||
|
||||
const char* VERSION = "20230730";
|
||||
const char* VERSION = "20230804";
|
||||
|
||||
#endif
|
||||
|
||||
@@ -198,6 +198,8 @@
|
||||
},
|
||||
"type": {"$ref": "#/$defs/ax25_type"},
|
||||
"pid": {"$ref": "#/$defs/ax25_pid"},
|
||||
"rssi": {"$ref": "#/$defs/rssi"},
|
||||
"ber": {"$ref": "#/$defs/ber"},
|
||||
"data": {"type": "string"},
|
||||
"required": ["timestamp", "source", "destination", "source", "type"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user