Add BER display to the Nextion and reduce Talker Id logging.
This commit is contained in:
parent
d7b872d6c2
commit
f0387d25dd
93
DMRSlot.cpp
93
DMRSlot.cpp
|
@ -54,6 +54,12 @@ FLCO CDMRSlot::m_flco2;
|
|||
unsigned char CDMRSlot::m_id2 = 0U;
|
||||
bool CDMRSlot::m_voice2 = true;
|
||||
|
||||
const unsigned char TALKER_ID_NONE = 0x00U;
|
||||
const unsigned char TALKER_ID_HEADER = 0x01U;
|
||||
const unsigned char TALKER_ID_BLOCK1 = 0x02U;
|
||||
const unsigned char TALKER_ID_BLOCK2 = 0x04U;
|
||||
const unsigned char TALKER_ID_BLOCK3 = 0x08U;
|
||||
|
||||
// #define DUMP_DMR
|
||||
|
||||
CDMRSlot::CDMRSlot(unsigned int slotNo, unsigned int timeout) :
|
||||
|
@ -65,10 +71,12 @@ m_rfEmbeddedLC(),
|
|||
m_rfEmbeddedData(NULL),
|
||||
m_rfEmbeddedReadN(0U),
|
||||
m_rfEmbeddedWriteN(1U),
|
||||
m_rfTalkerId(TALKER_ID_NONE),
|
||||
m_netEmbeddedLC(),
|
||||
m_netEmbeddedData(NULL),
|
||||
m_netEmbeddedReadN(0U),
|
||||
m_netEmbeddedWriteN(1U),
|
||||
m_netTalkerId(TALKER_ID_NONE),
|
||||
m_rfLC(NULL),
|
||||
m_netLC(NULL),
|
||||
m_rfDataHeader(),
|
||||
|
@ -222,8 +230,10 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
m_rfSeqNo = 0U;
|
||||
m_rfBits = 1U;
|
||||
m_rfErrs = 0U;
|
||||
|
||||
m_rfEmbeddedReadN = 0U;
|
||||
m_rfEmbeddedWriteN = 1U;
|
||||
m_rfTalkerId = TALKER_ID_NONE;
|
||||
|
||||
m_minRSSI = m_rssi;
|
||||
m_maxRSSI = m_rssi;
|
||||
|
@ -452,7 +462,7 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
if (ret) {
|
||||
trellis.encode(payload, data + 2U);
|
||||
} else {
|
||||
LogMessage("DMR Slot %u, unfixable RF rate 3/4 data", m_slotNo);
|
||||
LogDebug("DMR Slot %u, unfixable rate 3/4 data", m_slotNo);
|
||||
CUtils::dump(1U, "Data", data + 2U, DMR_FRAME_LENGTH_BYTES);
|
||||
}
|
||||
}
|
||||
|
@ -485,7 +495,8 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
unsigned char fid = m_rfLC->getFID();
|
||||
if (fid == FID_ETSI || fid == FID_DMRA) {
|
||||
errors = m_fec.regenerateDMR(data + 2U);
|
||||
LogDebug("DMR Slot %u, audio sequence no. 0, errs: %u/141", m_slotNo, errors);
|
||||
LogDebug("DMR Slot %u, audio sequence no. 0, errs: %u/141 (%.1f%%)", m_slotNo, errors, float(errors) / 1.41F);
|
||||
m_display->writeDMRBER(m_slotNo, float(errors) / 1.41F);
|
||||
m_rfErrs += errors;
|
||||
}
|
||||
|
||||
|
@ -518,7 +529,8 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
unsigned char fid = m_rfLC->getFID();
|
||||
if (fid == FID_ETSI || fid == FID_DMRA) {
|
||||
errors = m_fec.regenerateDMR(data + 2U);
|
||||
LogDebug("DMR Slot %u, audio sequence no. %u, errs: %u/141", m_slotNo, m_rfN, errors);
|
||||
LogDebug("DMR Slot %u, audio sequence no. %u, errs: %u/141 (%.1f%%)", m_slotNo, m_rfN, errors, float(errors) / 1.41F);
|
||||
m_display->writeDMRBER(m_slotNo, float(errors) / 1.41F);
|
||||
m_rfErrs += errors;
|
||||
}
|
||||
|
||||
|
@ -547,23 +559,35 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
break;
|
||||
case FLCO_GPS_INFO:
|
||||
::sprintf(text, "DMR Slot %u, Embedded GPS Info", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_HEADER:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Header", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_rfTalkerId & TALKER_ID_HEADER)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Header", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_rfTalkerId |= TALKER_ID_HEADER;
|
||||
}
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_BLOCK1:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 1", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_rfTalkerId & TALKER_ID_BLOCK1)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 1", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_rfTalkerId |= TALKER_ID_BLOCK1;
|
||||
}
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_BLOCK2:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 2", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_rfTalkerId & TALKER_ID_BLOCK2)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 2", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_rfTalkerId |= TALKER_ID_BLOCK2;
|
||||
}
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_BLOCK3:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 3", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_rfTalkerId & TALKER_ID_BLOCK3)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 3", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_rfTalkerId |= TALKER_ID_BLOCK3;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
::sprintf(text, "DMR Slot %u, Unknown Embedded Data", m_slotNo);
|
||||
|
@ -657,8 +681,10 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
m_rfSeqNo = 0U;
|
||||
m_rfBits = 1U;
|
||||
m_rfErrs = 0U;
|
||||
|
||||
m_rfEmbeddedReadN = 0U;
|
||||
m_rfEmbeddedWriteN = 1U;
|
||||
m_rfTalkerId = TALKER_ID_NONE;
|
||||
|
||||
m_minRSSI = m_rssi;
|
||||
m_maxRSSI = m_rssi;
|
||||
|
@ -686,7 +712,7 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
unsigned char fid = m_rfLC->getFID();
|
||||
if (fid == FID_ETSI || fid == FID_DMRA) {
|
||||
errors = m_fec.regenerateDMR(data + 2U);
|
||||
LogDebug("DMR Slot %u, audio sequence no. %u, errs: %u/141", m_slotNo, m_rfN, errors);
|
||||
LogDebug("DMR Slot %u, audio sequence no. %u, errs: %u/141 (%.1f%%)", m_slotNo, m_rfN, errors, float(errors) / 1.41F);
|
||||
m_rfErrs += errors;
|
||||
}
|
||||
|
||||
|
@ -710,6 +736,7 @@ void CDMRSlot::writeModem(unsigned char *data, unsigned int len)
|
|||
setShortLC(m_slotNo, dstId, flco, true);
|
||||
m_display->writeDMR(m_slotNo, src, flco == FLCO_GROUP, dst, "R");
|
||||
m_display->writeDMRRSSI(m_slotNo, m_rssi);
|
||||
m_display->writeDMRBER(m_slotNo, float(errors) / 1.41F);
|
||||
}
|
||||
|
||||
LogMessage("DMR Slot %u, received RF late entry from %s to %s%s", m_slotNo, src.c_str(), flco == FLCO_GROUP ? "TG " : "", dst.c_str());
|
||||
|
@ -939,8 +966,10 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData)
|
|||
m_netLost = 0U;
|
||||
m_netBits = 1U;
|
||||
m_netErrs = 0U;
|
||||
|
||||
m_netEmbeddedReadN = 0U;
|
||||
m_netEmbeddedWriteN = 1U;
|
||||
m_netTalkerId = TALKER_ID_NONE;
|
||||
|
||||
if (m_duplex) {
|
||||
m_queue.clear();
|
||||
|
@ -1195,8 +1224,10 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData)
|
|||
m_netLost = 0U;
|
||||
m_netBits = 1U;
|
||||
m_netErrs = 0U;
|
||||
|
||||
m_netEmbeddedReadN = 0U;
|
||||
m_netEmbeddedWriteN = 1U;
|
||||
m_netTalkerId = TALKER_ID_NONE;
|
||||
|
||||
m_netState = RS_NET_AUDIO;
|
||||
|
||||
|
@ -1284,23 +1315,35 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData)
|
|||
break;
|
||||
case FLCO_GPS_INFO:
|
||||
::sprintf(text, "DMR Slot %u, Embedded GPS Info", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_HEADER:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Header", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_netTalkerId & TALKER_ID_HEADER)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Header", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_netTalkerId |= TALKER_ID_HEADER;
|
||||
}
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_BLOCK1:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 1", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_netTalkerId & TALKER_ID_BLOCK1)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 1", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_netTalkerId |= TALKER_ID_BLOCK1;
|
||||
}
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_BLOCK2:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 2", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_netTalkerId & TALKER_ID_BLOCK2)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 2", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_netTalkerId |= TALKER_ID_BLOCK2;
|
||||
}
|
||||
break;
|
||||
case FLCO_TALKER_ALIAS_BLOCK3:
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 3", m_slotNo);
|
||||
CUtils::dump(1U, text, data, 9U);
|
||||
if (!(m_netTalkerId & TALKER_ID_BLOCK3)) {
|
||||
::sprintf(text, "DMR Slot %u, Embedded Talker Alias Block 3", m_slotNo);
|
||||
CUtils::dump(2U, text, data, 9U);
|
||||
m_netTalkerId |= TALKER_ID_BLOCK3;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
::sprintf(text, "DMR Slot %u, Unknown Embedded Data", m_slotNo);
|
||||
|
@ -1426,12 +1469,8 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData)
|
|||
CDMRTrellis trellis;
|
||||
unsigned char payload[18U];
|
||||
bool ret = trellis.decode(data + 2U, payload);
|
||||
if (ret) {
|
||||
if (ret)
|
||||
trellis.encode(payload, data + 2U);
|
||||
} else {
|
||||
LogMessage("DMR Slot %u, unfixable network rate 3/4 data", m_slotNo);
|
||||
CUtils::dump(1U, "Data", data + 2U, DMR_FRAME_LENGTH_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
// Regenerate the Slot Type
|
||||
|
|
|
@ -61,10 +61,12 @@ private:
|
|||
CDMREmbeddedData* m_rfEmbeddedData;
|
||||
unsigned int m_rfEmbeddedReadN;
|
||||
unsigned int m_rfEmbeddedWriteN;
|
||||
unsigned char m_rfTalkerId;
|
||||
CDMREmbeddedData m_netEmbeddedLC;
|
||||
CDMREmbeddedData* m_netEmbeddedData;
|
||||
unsigned int m_netEmbeddedReadN;
|
||||
unsigned int m_netEmbeddedWriteN;
|
||||
unsigned char m_netTalkerId;
|
||||
CDMRLC* m_rfLC;
|
||||
CDMRLC* m_netLC;
|
||||
CDMRDataHeader m_rfDataHeader;
|
||||
|
|
|
@ -300,12 +300,14 @@ bool CDStarControl::writeModem(unsigned char *data, unsigned int len)
|
|||
return false;
|
||||
} else if (m_rfState == RS_RF_AUDIO) {
|
||||
unsigned int errors = 0U;
|
||||
if (!m_rfHeader.isDataPacket())
|
||||
if (!m_rfHeader.isDataPacket()) {
|
||||
errors = m_fec.regenerateDStar(data + 1U);
|
||||
m_display->writeDStarBER(float(errors) / 0.48F);
|
||||
LogDebug("D-Star, audio sequence no. %u, errs: %u/48 (%.1f%%)", m_rfN, errors, float(errors) / 0.48F);
|
||||
m_rfErrs += errors;
|
||||
}
|
||||
|
||||
m_rfErrs += errors;
|
||||
m_rfBits += 48U;
|
||||
|
||||
m_rfFrames++;
|
||||
|
||||
// The sync is regenerated by the modem so can do exact match
|
||||
|
@ -318,8 +320,6 @@ bool CDStarControl::writeModem(unsigned char *data, unsigned int len)
|
|||
m_display->writeDStarRSSI(m_rssi);
|
||||
}
|
||||
|
||||
// LogDebug("D-Star, audio sequence no. %u, errs: %u/48", m_rfN, errors);
|
||||
|
||||
if (m_net)
|
||||
writeNetworkDataRF(data, errors, false);
|
||||
|
||||
|
@ -428,14 +428,14 @@ bool CDStarControl::writeModem(unsigned char *data, unsigned int len)
|
|||
delete header;
|
||||
|
||||
unsigned int errors = 0U;
|
||||
if (!m_rfHeader.isDataPacket())
|
||||
if (!m_rfHeader.isDataPacket()) {
|
||||
errors = m_fec.regenerateDStar(data + 1U);
|
||||
LogDebug("D-Star, audio sequence no. %u, errs: %u/48 (%.1f%%)", m_rfN, errors, float(errors) / 0.48F);
|
||||
m_rfErrs += errors;
|
||||
}
|
||||
|
||||
m_rfErrs += errors;
|
||||
m_rfBits += 48U;
|
||||
|
||||
// LogDebug("D-Star, audio sequence no. %u, errs: %u/48", m_rfN, errors);
|
||||
|
||||
if (m_net)
|
||||
writeNetworkDataRF(data, errors, false);
|
||||
|
||||
|
@ -451,6 +451,7 @@ bool CDStarControl::writeModem(unsigned char *data, unsigned int len)
|
|||
if (m_netState == RS_NET_IDLE) {
|
||||
m_display->writeDStar((char*)my1, (char*)my2, (char*)your, "R", " ");
|
||||
m_display->writeDStarRSSI(m_rssi);
|
||||
m_display->writeDStarBER(float(errors) / 0.48F);
|
||||
}
|
||||
|
||||
LogMessage("D-Star, received RF late entry from %8.8s/%4.4s to %8.8s", my1, my2, your);
|
||||
|
@ -624,8 +625,6 @@ void CDStarControl::writeNetwork()
|
|||
|
||||
m_netN = n;
|
||||
|
||||
// LogDebug("D-Star, audio sequence no. %u, errs: %u/48", m_netN, errors);
|
||||
|
||||
// Regenerate the sync
|
||||
if (n == 0U)
|
||||
CSync::addDStarSync(data + 2U);
|
||||
|
|
36
Display.cpp
36
Display.cpp
|
@ -90,6 +90,11 @@ void CDisplay::writeDStarRSSI(unsigned char rssi)
|
|||
writeDStarRSSIInt(rssi);
|
||||
}
|
||||
|
||||
void CDisplay::writeDStarBER(float ber)
|
||||
{
|
||||
writeDStarBERInt(ber);
|
||||
}
|
||||
|
||||
void CDisplay::clearDStar()
|
||||
{
|
||||
if (m_timer1.hasExpired()) {
|
||||
|
@ -122,6 +127,11 @@ void CDisplay::writeDMRRSSI(unsigned int slotNo, unsigned char rssi)
|
|||
writeDMRRSSIInt(slotNo, rssi);
|
||||
}
|
||||
|
||||
void CDisplay::writeDMRBER(unsigned int slotNo, float ber)
|
||||
{
|
||||
writeDMRBERInt(slotNo, ber);
|
||||
}
|
||||
|
||||
void CDisplay::clearDMR(unsigned int slotNo)
|
||||
{
|
||||
if (slotNo == 1U) {
|
||||
|
@ -162,6 +172,11 @@ void CDisplay::writeFusionRSSI(unsigned char rssi)
|
|||
writeFusionRSSIInt(rssi);
|
||||
}
|
||||
|
||||
void CDisplay::writeFusionBER(float ber)
|
||||
{
|
||||
writeFusionBERInt(ber);
|
||||
}
|
||||
|
||||
void CDisplay::clearFusion()
|
||||
{
|
||||
if (m_timer1.hasExpired()) {
|
||||
|
@ -190,6 +205,11 @@ void CDisplay::writeP25RSSI(unsigned char rssi)
|
|||
writeP25RSSIInt(rssi);
|
||||
}
|
||||
|
||||
void CDisplay::writeP25BER(float ber)
|
||||
{
|
||||
writeP25BERInt(ber);
|
||||
}
|
||||
|
||||
void CDisplay::clearP25()
|
||||
{
|
||||
if (m_timer1.hasExpired()) {
|
||||
|
@ -265,14 +285,30 @@ void CDisplay::writeDStarRSSIInt(unsigned char rssi)
|
|||
{
|
||||
}
|
||||
|
||||
void CDisplay::writeDStarBERInt(float ber)
|
||||
{
|
||||
}
|
||||
|
||||
void CDisplay::writeDMRRSSIInt(unsigned int slotNo, unsigned char rssi)
|
||||
{
|
||||
}
|
||||
|
||||
void CDisplay::writeDMRBERInt(unsigned int slotNo, float ber)
|
||||
{
|
||||
}
|
||||
|
||||
void CDisplay::writeFusionRSSIInt(unsigned char rssi)
|
||||
{
|
||||
}
|
||||
|
||||
void CDisplay::writeFusionBERInt(float ber)
|
||||
{
|
||||
}
|
||||
|
||||
void CDisplay::writeP25RSSIInt(unsigned char rssi)
|
||||
{
|
||||
}
|
||||
|
||||
void CDisplay::writeP25BERInt(float ber)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -37,18 +37,22 @@ public:
|
|||
|
||||
void writeDStar(const char* my1, const char* my2, const char* your, const char* type, const char* reflector);
|
||||
void writeDStarRSSI(unsigned char rssi);
|
||||
void writeDStarBER(float ber);
|
||||
void clearDStar();
|
||||
|
||||
void writeDMR(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type);
|
||||
void writeDMRRSSI(unsigned int slotNo, unsigned char rssi);
|
||||
void writeDMRBER(unsigned int slotNo, float ber);
|
||||
void clearDMR(unsigned int slotNo);
|
||||
|
||||
void writeFusion(const char* source, const char* dest, const char* type, const char* origin);
|
||||
void writeFusionRSSI(unsigned char rssi);
|
||||
void writeFusionBER(float ber);
|
||||
void clearFusion();
|
||||
|
||||
void writeP25(const char* source, bool group, unsigned int dest, const char* type);
|
||||
void writeP25RSSI(unsigned char rssi);
|
||||
void writeP25BER(float ber);
|
||||
void clearP25();
|
||||
|
||||
void writeCW();
|
||||
|
@ -65,18 +69,22 @@ protected:
|
|||
|
||||
virtual void writeDStarInt(const char* my1, const char* my2, const char* your, const char* type, const char* reflector) = 0;
|
||||
virtual void writeDStarRSSIInt(unsigned char rssi);
|
||||
virtual void writeDStarBERInt(float ber);
|
||||
virtual void clearDStarInt() = 0;
|
||||
|
||||
virtual void writeDMRInt(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type) = 0;
|
||||
virtual void writeDMRRSSIInt(unsigned int slotNo, unsigned char rssi);
|
||||
virtual void writeDMRBERInt(unsigned int slotNo, float ber);
|
||||
virtual void clearDMRInt(unsigned int slotNo) = 0;
|
||||
|
||||
virtual void writeFusionInt(const char* source, const char* dest, const char* type, const char* origin) = 0;
|
||||
virtual void writeFusionRSSIInt(unsigned char rssi);
|
||||
virtual void writeFusionBERInt(float ber);
|
||||
virtual void clearFusionInt() = 0;
|
||||
|
||||
virtual void writeP25Int(const char* source, bool group, unsigned int dest, const char* type) = 0;
|
||||
virtual void writeP25RSSIInt(unsigned char rssi);
|
||||
virtual void writeP25BERInt(float ber);
|
||||
virtual void clearP25Int() = 0;
|
||||
|
||||
virtual void writeCWInt() = 0;
|
||||
|
|
65
Modem.cpp
65
Modem.cpp
|
@ -465,7 +465,19 @@ void CModem::clock(unsigned int ms)
|
|||
LogWarning("Received a NAK from the MMDVM, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]);
|
||||
break;
|
||||
|
||||
default:
|
||||
case MMDVM_DEBUG1:
|
||||
case MMDVM_DEBUG2:
|
||||
case MMDVM_DEBUG3:
|
||||
case MMDVM_DEBUG4:
|
||||
case MMDVM_DEBUG5:
|
||||
printDebug();
|
||||
break;
|
||||
|
||||
case MMDVM_SAMPLES:
|
||||
printSamples();
|
||||
break;
|
||||
|
||||
default:
|
||||
LogMessage("Unknown message, type: %02X", m_buffer[2U]);
|
||||
CUtils::dump("Buffer dump", m_buffer, m_length);
|
||||
break;
|
||||
|
@ -1112,39 +1124,6 @@ RESP_TYPE_MMDVM CModem::getResponse()
|
|||
if (ret == 0)
|
||||
return RTM_TIMEOUT;
|
||||
|
||||
switch (m_buffer[2U]) {
|
||||
case MMDVM_DSTAR_HEADER:
|
||||
case MMDVM_DSTAR_DATA:
|
||||
case MMDVM_DSTAR_LOST:
|
||||
case MMDVM_DSTAR_EOT:
|
||||
case MMDVM_DMR_DATA1:
|
||||
case MMDVM_DMR_DATA2:
|
||||
case MMDVM_DMR_LOST1:
|
||||
case MMDVM_DMR_LOST2:
|
||||
case MMDVM_YSF_DATA:
|
||||
case MMDVM_YSF_LOST:
|
||||
case MMDVM_P25_HDR:
|
||||
case MMDVM_P25_LDU:
|
||||
case MMDVM_P25_LOST:
|
||||
case MMDVM_GET_STATUS:
|
||||
case MMDVM_GET_VERSION:
|
||||
case MMDVM_ACK:
|
||||
case MMDVM_NAK:
|
||||
case MMDVM_SERIAL:
|
||||
case MMDVM_SAMPLES:
|
||||
case MMDVM_DEBUG1:
|
||||
case MMDVM_DEBUG2:
|
||||
case MMDVM_DEBUG3:
|
||||
case MMDVM_DEBUG4:
|
||||
case MMDVM_DEBUG5:
|
||||
break;
|
||||
|
||||
default:
|
||||
LogError("Unknown message, type: %02X", m_buffer[2U]);
|
||||
m_offset = 0U;
|
||||
return RTM_ERROR;
|
||||
}
|
||||
|
||||
m_offset = 3U;
|
||||
}
|
||||
|
||||
|
@ -1167,23 +1146,9 @@ RESP_TYPE_MMDVM CModem::getResponse()
|
|||
|
||||
m_offset = 0U;
|
||||
|
||||
switch (m_buffer[2U]) {
|
||||
case MMDVM_DEBUG1:
|
||||
case MMDVM_DEBUG2:
|
||||
case MMDVM_DEBUG3:
|
||||
case MMDVM_DEBUG4:
|
||||
case MMDVM_DEBUG5:
|
||||
printDebug();
|
||||
return RTM_TIMEOUT;
|
||||
// CUtils::dump(1U, "Received", m_buffer, m_length);
|
||||
|
||||
case MMDVM_SAMPLES:
|
||||
printSamples();
|
||||
return RTM_TIMEOUT;
|
||||
|
||||
default:
|
||||
// CUtils::dump(1U, "Received", m_buffer, m_length);
|
||||
return RTM_OK;
|
||||
}
|
||||
return RTM_OK;
|
||||
}
|
||||
|
||||
HW_TYPE CModem::getHWType() const
|
||||
|
|
208
Nextion.cpp
208
Nextion.cpp
|
@ -25,10 +25,14 @@
|
|||
#include <ctime>
|
||||
#include <clocale>
|
||||
|
||||
const unsigned int DSTAR_RSSI_COUNT = 3U; // 3 * 420ms = 1260ms
|
||||
const unsigned int DMR_RSSI_COUNT = 4U; // 4 * 360ms = 1440ms
|
||||
const unsigned int DSTAR_RSSI_COUNT = 3U; // 3 * 420ms = 1260ms
|
||||
const unsigned int DSTAR_BER_COUNT = 63U; // 63 * 20ms = 1260ms
|
||||
const unsigned int DMR_RSSI_COUNT = 4U; // 4 * 360ms = 1440ms
|
||||
const unsigned int DMR_BER_COUNT = 24U; // 24 * 60ms = 1440ms
|
||||
const unsigned int YSF_RSSI_COUNT = 13U; // 13 * 100ms = 1300ms
|
||||
const unsigned int P25_RSSI_COUNT = 7U; // 7 * 180ms = 1260ms
|
||||
const unsigned int YSF_BER_COUNT = 13U; // 13 * 100ms = 1300ms
|
||||
const unsigned int P25_RSSI_COUNT = 7U; // 7 * 180ms = 1260ms
|
||||
const unsigned int P25_BER_COUNT = 7U; // 7 * 180ms = 1260ms
|
||||
|
||||
CNextion::CNextion(const std::string& callsign, unsigned int dmrid, ISerialPort* serial, unsigned int brightness, bool displayClock, bool utc, unsigned int idleBrightness) :
|
||||
CDisplay(),
|
||||
|
@ -41,8 +45,14 @@ m_displayClock(displayClock),
|
|||
m_utc(utc),
|
||||
m_idleBrightness(idleBrightness),
|
||||
m_clockDisplayTimer(1000U, 0U, 400U),
|
||||
m_rssiCount1(0U),
|
||||
m_rssiCount2(0U)
|
||||
m_rssiAccum1(0U),
|
||||
m_rssiAccum2(0U),
|
||||
m_berAccum1(0.0F),
|
||||
m_berAccum2(0.0F),
|
||||
m_rssiCount1(1U),
|
||||
m_rssiCount2(1U),
|
||||
m_berCount1(1U),
|
||||
m_berCount2(1U)
|
||||
{
|
||||
assert(serial != NULL);
|
||||
assert(brightness >= 0U && brightness <= 100U);
|
||||
|
@ -129,8 +139,8 @@ void CNextion::writeDStarInt(const char* my1, const char* my2, const char* your,
|
|||
assert(type != NULL);
|
||||
assert(reflector != NULL);
|
||||
|
||||
if (m_mode != MODE_DSTAR)
|
||||
sendCommand("page DStar");
|
||||
if (m_mode != MODE_DSTAR)
|
||||
sendCommand("page DStar");
|
||||
|
||||
char text[30U];
|
||||
::sprintf(text, "dim=%u", m_brightness);
|
||||
|
@ -150,20 +160,38 @@ void CNextion::writeDStarInt(const char* my1, const char* my2, const char* your,
|
|||
m_clockDisplayTimer.stop();
|
||||
|
||||
m_mode = MODE_DSTAR;
|
||||
m_rssiCount1 = 0U;
|
||||
m_rssiAccum1 = 0U;
|
||||
m_berAccum1 = 0.0F;
|
||||
m_rssiCount1 = 1U;
|
||||
m_berCount1 = 1U;
|
||||
}
|
||||
|
||||
void CNextion::writeDStarRSSIInt(unsigned char rssi)
|
||||
{
|
||||
if (m_rssiCount1 == 0U) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t3.txt=\"-%udBm\"", rssi);
|
||||
sendCommand(text);
|
||||
}
|
||||
|
||||
m_rssiAccum1 += rssi;
|
||||
m_rssiCount1++;
|
||||
if (m_rssiCount1 >= DSTAR_RSSI_COUNT)
|
||||
m_rssiCount1 = 0U;
|
||||
|
||||
if (m_rssiCount1 == DSTAR_RSSI_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t3.txt=\"-%udBm\"", m_rssiAccum1 / DSTAR_RSSI_COUNT);
|
||||
sendCommand(text);
|
||||
m_rssiAccum1 = 0U;
|
||||
m_rssiCount1 = 1U;
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::writeDStarBERInt(float ber)
|
||||
{
|
||||
m_berAccum1 += ber;
|
||||
m_berCount1++;
|
||||
|
||||
if (m_berCount1 == DSTAR_BER_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t4.txt=\"%.1f%%\"", m_berAccum1 / float(DSTAR_BER_COUNT));
|
||||
sendCommand(text);
|
||||
m_berAccum1 = 0.0F;
|
||||
m_berCount1 = 1U;
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::clearDStarInt()
|
||||
|
@ -172,6 +200,7 @@ void CNextion::clearDStarInt()
|
|||
sendCommand("t1.txt=\"\"");
|
||||
sendCommand("t2.txt=\"\"");
|
||||
sendCommand("t3.txt=\"\"");
|
||||
sendCommand("t4.txt=\"\"");
|
||||
}
|
||||
|
||||
void CNextion::writeDMRInt(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type)
|
||||
|
@ -208,32 +237,67 @@ void CNextion::writeDMRInt(unsigned int slotNo, const std::string& src, bool gro
|
|||
m_clockDisplayTimer.stop();
|
||||
|
||||
m_mode = MODE_DMR;
|
||||
m_rssiCount1 = 0U;
|
||||
m_rssiCount2 = 0U;
|
||||
m_rssiAccum1 = 0U;
|
||||
m_rssiAccum2 = 0U;
|
||||
m_berAccum1 = 0.0F;
|
||||
m_berAccum2 = 0.0F;
|
||||
m_rssiCount1 = 1U;
|
||||
m_rssiCount2 = 1U;
|
||||
m_berCount1 = 1U;
|
||||
m_berCount2 = 1U;
|
||||
}
|
||||
|
||||
void CNextion::writeDMRRSSIInt(unsigned int slotNo, unsigned char rssi)
|
||||
{
|
||||
if (slotNo == 1U) {
|
||||
if (m_rssiCount1 == 0U) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t4.txt=\"-%udBm\"", rssi);
|
||||
sendCommand(text);
|
||||
}
|
||||
|
||||
m_rssiAccum1 += rssi;
|
||||
m_rssiCount1++;
|
||||
if (m_rssiCount1 >= DMR_RSSI_COUNT)
|
||||
m_rssiCount1 = 0U;
|
||||
} else {
|
||||
if (m_rssiCount2 == 0U) {
|
||||
|
||||
if (m_rssiCount1 == DMR_RSSI_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t5.txt=\"-%udBm\"", rssi);
|
||||
::sprintf(text, "t4.txt=\"-%udBm\"", m_rssiAccum1 / DMR_RSSI_COUNT);
|
||||
sendCommand(text);
|
||||
m_rssiAccum1 = 0U;
|
||||
m_rssiCount1 = 1U;
|
||||
}
|
||||
|
||||
} else {
|
||||
m_rssiAccum2 += rssi;
|
||||
m_rssiCount2++;
|
||||
if (m_rssiCount2 >= DMR_RSSI_COUNT)
|
||||
m_rssiCount2 = 0U;
|
||||
|
||||
if (m_rssiCount2 == DMR_RSSI_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t5.txt=\"-%udBm\"", m_rssiAccum2 / DMR_RSSI_COUNT);
|
||||
sendCommand(text);
|
||||
m_rssiAccum2 = 0U;
|
||||
m_rssiCount2 = 1U;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::writeDMRBERInt(unsigned int slotNo, float ber)
|
||||
{
|
||||
if (slotNo == 1U) {
|
||||
m_berAccum1 += ber;
|
||||
m_berCount1++;
|
||||
|
||||
if (m_berCount1 == DMR_BER_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t6.txt=\"%.1f%%\"", m_berAccum1 / DMR_BER_COUNT);
|
||||
sendCommand(text);
|
||||
m_berAccum1 = 0U;
|
||||
m_berCount1 = 1U;
|
||||
}
|
||||
} else {
|
||||
m_berAccum2 += ber;
|
||||
m_berCount2++;
|
||||
|
||||
if (m_berCount2 == DMR_BER_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t7.txt=\"%.1f%%\"", m_berAccum2 / DMR_BER_COUNT);
|
||||
sendCommand(text);
|
||||
m_berAccum2 = 0U;
|
||||
m_berCount2 = 1U;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,11 +307,13 @@ void CNextion::clearDMRInt(unsigned int slotNo)
|
|||
sendCommand("t0.txt=\"1 Listening\"");
|
||||
sendCommand("t1.txt=\"\"");
|
||||
sendCommand("t4.txt=\"\"");
|
||||
} else {
|
||||
sendCommand("t6.txt=\"\"");
|
||||
} else {
|
||||
sendCommand("t2.txt=\"2 Listening\"");
|
||||
sendCommand("t3.txt=\"\"");
|
||||
sendCommand("t5.txt=\"\"");
|
||||
}
|
||||
sendCommand("t7.txt=\"\"");
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::writeFusionInt(const char* source, const char* dest, const char* type, const char* origin)
|
||||
|
@ -277,20 +343,38 @@ void CNextion::writeFusionInt(const char* source, const char* dest, const char*
|
|||
m_clockDisplayTimer.stop();
|
||||
|
||||
m_mode = MODE_YSF;
|
||||
m_rssiCount1 = 0U;
|
||||
m_rssiAccum1 = 0U;
|
||||
m_berAccum1 = 0.0F;
|
||||
m_rssiCount1 = 1U;
|
||||
m_berCount1 = 1U;
|
||||
}
|
||||
|
||||
void CNextion::writeFusionRSSIInt(unsigned char rssi)
|
||||
{
|
||||
if (m_rssiCount1 == 0U) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t3.txt=\"-%udBm\"", rssi);
|
||||
sendCommand(text);
|
||||
}
|
||||
|
||||
m_rssiAccum1 += rssi;
|
||||
m_rssiCount1++;
|
||||
if (m_rssiCount1 >= YSF_RSSI_COUNT)
|
||||
m_rssiCount1 = 0U;
|
||||
|
||||
if (m_rssiCount1 == YSF_RSSI_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t3.txt=\"-%udBm\"", m_rssiAccum1 / YSF_RSSI_COUNT);
|
||||
sendCommand(text);
|
||||
m_rssiAccum1 = 0U;
|
||||
m_rssiCount1 = 1U;
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::writeFusionBERInt(float ber)
|
||||
{
|
||||
m_berAccum1 += ber;
|
||||
m_berCount1++;
|
||||
|
||||
if (m_berCount1 == YSF_BER_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t4.txt=\"%.1f%%\"", m_berAccum1 / float(YSF_BER_COUNT));
|
||||
sendCommand(text);
|
||||
m_berAccum1 = 0.0F;
|
||||
m_berCount1 = 1U;
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::clearFusionInt()
|
||||
|
@ -299,6 +383,7 @@ void CNextion::clearFusionInt()
|
|||
sendCommand("t1.txt=\"\"");
|
||||
sendCommand("t2.txt=\"\"");
|
||||
sendCommand("t3.txt=\"\"");
|
||||
sendCommand("t4.txt=\"\"");
|
||||
}
|
||||
|
||||
void CNextion::writeP25Int(const char* source, bool group, unsigned int dest, const char* type)
|
||||
|
@ -322,20 +407,38 @@ void CNextion::writeP25Int(const char* source, bool group, unsigned int dest, co
|
|||
m_clockDisplayTimer.stop();
|
||||
|
||||
m_mode = MODE_P25;
|
||||
m_rssiCount1 = 0U;
|
||||
m_rssiAccum1 = 0U;
|
||||
m_berAccum1 = 0.0F;
|
||||
m_rssiCount1 = 1U;
|
||||
m_berCount1 = 1U;
|
||||
}
|
||||
|
||||
void CNextion::writeP25RSSIInt(unsigned char rssi)
|
||||
{
|
||||
if (m_rssiCount1 == 0U) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t2.txt=\"-%udBm\"", rssi);
|
||||
sendCommand(text);
|
||||
}
|
||||
|
||||
m_rssiAccum1 += rssi;
|
||||
m_rssiCount1++;
|
||||
if (m_rssiCount1 >= P25_RSSI_COUNT)
|
||||
m_rssiCount1 = 0U;
|
||||
|
||||
if (m_rssiCount1 == P25_RSSI_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t2.txt=\"-%udBm\"", m_rssiAccum1 / P25_RSSI_COUNT);
|
||||
sendCommand(text);
|
||||
m_rssiAccum1 = 0U;
|
||||
m_rssiCount1 = 1U;
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::writeP25BERInt(float ber)
|
||||
{
|
||||
m_berAccum1 += ber;
|
||||
m_berCount1++;
|
||||
|
||||
if (m_berCount1 == P25_BER_COUNT) {
|
||||
char text[20U];
|
||||
::sprintf(text, "t3.txt=\"%.1f%%\"", m_berAccum1 / float(P25_BER_COUNT));
|
||||
sendCommand(text);
|
||||
m_berAccum1 = 0.0F;
|
||||
m_berCount1 = 1U;
|
||||
}
|
||||
}
|
||||
|
||||
void CNextion::clearP25Int()
|
||||
|
@ -343,6 +446,7 @@ void CNextion::clearP25Int()
|
|||
sendCommand("t0.txt=\"Listening\"");
|
||||
sendCommand("t1.txt=\"\"");
|
||||
sendCommand("t2.txt=\"\"");
|
||||
sendCommand("t3.txt=\"\"");
|
||||
}
|
||||
|
||||
void CNextion::writeCWInt()
|
||||
|
|
10
Nextion.h
10
Nextion.h
|
@ -43,18 +43,22 @@ protected:
|
|||
|
||||
virtual void writeDStarInt(const char* my1, const char* my2, const char* your, const char* type, const char* reflector);
|
||||
virtual void writeDStarRSSIInt(unsigned char rssi);
|
||||
virtual void writeDStarBERInt(float ber);
|
||||
virtual void clearDStarInt();
|
||||
|
||||
virtual void writeDMRInt(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type);
|
||||
virtual void writeDMRRSSIInt(unsigned int slotNo, unsigned char rssi);
|
||||
virtual void writeDMRBERInt(unsigned int slotNo, float ber);
|
||||
virtual void clearDMRInt(unsigned int slotNo);
|
||||
|
||||
virtual void writeFusionInt(const char* source, const char* dest, const char* type, const char* origin);
|
||||
virtual void writeFusionRSSIInt(unsigned char rssi);
|
||||
virtual void writeFusionBERInt(float ber);
|
||||
virtual void clearFusionInt();
|
||||
|
||||
virtual void writeP25Int(const char* source, bool group, unsigned int dest, const char* type);
|
||||
virtual void writeP25RSSIInt(unsigned char rssi);
|
||||
virtual void writeP25BERInt(float ber);
|
||||
virtual void clearP25Int();
|
||||
|
||||
virtual void writeCWInt();
|
||||
|
@ -72,8 +76,14 @@ private:
|
|||
bool m_utc;
|
||||
unsigned int m_idleBrightness;
|
||||
CTimer m_clockDisplayTimer;
|
||||
unsigned int m_rssiAccum1;
|
||||
unsigned int m_rssiAccum2;
|
||||
float m_berAccum1;
|
||||
float m_berAccum2;
|
||||
unsigned int m_rssiCount1;
|
||||
unsigned int m_rssiCount2;
|
||||
unsigned int m_berCount1;
|
||||
unsigned int m_berCount2;
|
||||
|
||||
void sendCommand(const char* command);
|
||||
};
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -207,7 +207,9 @@ bool CP25Control::writeModem(unsigned char* data, unsigned int len)
|
|||
|
||||
// Regenerate Audio
|
||||
unsigned int errors = m_audio.process(data + 2U);
|
||||
LogDebug("P25, LDU1 audio, errs: %u/1233", errors);
|
||||
LogDebug("P25, LDU1 audio, errs: %u/1233 (%.1f%%)", errors, float(errors) / 12.33F);
|
||||
|
||||
m_display->writeP25BER(float(errors) / 12.33F);
|
||||
|
||||
m_rfBits += 1233U;
|
||||
m_rfErrs += errors;
|
||||
|
@ -260,7 +262,9 @@ bool CP25Control::writeModem(unsigned char* data, unsigned int len)
|
|||
|
||||
// Regenerate Audio
|
||||
unsigned int errors = m_audio.process(data + 2U);
|
||||
LogDebug("P25, LDU2 audio, errs: %u/1233", errors);
|
||||
LogDebug("P25, LDU2 audio, errs: %u/1233 (%.1f%%)", errors, float(errors) / 12.33F);
|
||||
|
||||
m_display->writeP25BER(float(errors) / 12.33F);
|
||||
|
||||
m_rfBits += 1233U;
|
||||
m_rfErrs += errors;
|
||||
|
|
|
@ -273,6 +273,7 @@ bool CYSFControl::writeModem(unsigned char *data, unsigned int len)
|
|||
unsigned int errors = m_rfPayload.processVDMode1Audio(data + 2U);
|
||||
m_rfErrs += errors;
|
||||
m_rfBits += 235U;
|
||||
m_display->writeFusionBER(float(errors) / 2.35F);
|
||||
LogDebug("YSF, V/D Mode 1, seq %u, AMBE FEC %u/235 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 2.35F);
|
||||
}
|
||||
break;
|
||||
|
@ -282,6 +283,7 @@ bool CYSFControl::writeModem(unsigned char *data, unsigned int len)
|
|||
unsigned int errors = m_rfPayload.processVDMode2Audio(data + 2U);
|
||||
m_rfErrs += errors;
|
||||
m_rfBits += 135U;
|
||||
m_display->writeFusionBER(float(errors) / 1.35F);
|
||||
LogDebug("YSF, V/D Mode 2, seq %u, Repetition FEC %u/135 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 1.35F);
|
||||
}
|
||||
break;
|
||||
|
@ -297,6 +299,7 @@ bool CYSFControl::writeModem(unsigned char *data, unsigned int len)
|
|||
unsigned int errors = m_rfPayload.processVoiceFRModeAudio(data + 2U);
|
||||
m_rfErrs += errors;
|
||||
m_rfBits += 720U;
|
||||
m_display->writeFusionBER(float(errors) / 7.2F);
|
||||
LogDebug("YSF, V Mode 3, seq %u, AMBE FEC %u/720 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 7.2F);
|
||||
}
|
||||
valid = false;
|
||||
|
@ -542,7 +545,6 @@ void CYSFControl::writeNetwork()
|
|||
// if (send) {
|
||||
m_netErrs += errors;
|
||||
m_netBits += 235U;
|
||||
LogDebug("YSF, V/D Mode 1, seq %u, AMBE FEC %u/235 (%.1f%%)", n, errors, float(errors) / 2.35F);
|
||||
// }
|
||||
}
|
||||
break;
|
||||
|
@ -554,13 +556,11 @@ void CYSFControl::writeNetwork()
|
|||
// if (send) {
|
||||
m_netErrs += errors;
|
||||
m_netBits += 135U;
|
||||
LogDebug("YSF, V/D Mode 2, seq %u, Repetition FEC %u/135 (%.1f%%)", n, errors, float(errors) / 1.35F);
|
||||
// }
|
||||
}
|
||||
break;
|
||||
|
||||
case YSF_DT_DATA_FR_MODE:
|
||||
LogDebug("YSF, Network data FICH B=%u/%u F=%u/%u", bn, bt, fn, ft);
|
||||
m_netPayload.processDataFRModeData(data + 35U, fn, gateway);
|
||||
break;
|
||||
|
||||
|
@ -572,7 +572,6 @@ void CYSFControl::writeNetwork()
|
|||
// if (send) {
|
||||
m_netErrs += errors;
|
||||
m_netBits += 720U;
|
||||
LogDebug("YSF, V Mode 3, seq %u, AMBE FEC %u/720 (%.1f%%)", n, errors, float(errors) / 7.2F);
|
||||
// }
|
||||
}
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue