diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index acd8cba8..31bd2ccc 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -312,7 +312,17 @@ namespace p25 INHIBIT = 0x007FU, //! Radio Inhibit CHECK_ACK = 0x0080U, //! Radio Check Ack UNINHIBIT_ACK = 0x00FEU, //! Radio Uninhibit Ack - INHIBIT_ACK = 0x00FFU //! Radio Inhibit Ack + INHIBIT_ACK = 0x00FFU, //! Radio Inhibit Ack + + DYN_REGRP_REQ = 0x0200U, //! MFID $90 (Motorola) Dynamic Regroup IR + DYN_REGRP_CANCEL = 0x0201U, //! MFID $90 (Motorola) Dynamic Regroup IR Cancellation + DYN_REGRP_LOCK = 0x0202U, //! MFID $90 (Motorola) Lock Selector + DYN_REGRP_UNLOCK = 0x0203U, //! MFID $90 (Motorola) Unlock Selector + + DYN_REGRP_REQ_ACK = 0x0280U, //! MFID $90 (Motorola) Dynamic Regroup IR Ack + DYN_REGRP_CANCEL_ACK = 0x0281U, //! MFID $90 (Motorola) Dynamic Regroup IR Cancellation Ack + DYN_REGRP_LOCK_ACK = 0x0282U, //! MFID $90 (Motorola) Lock Selector Ack + DYN_REGRP_UNLOCK_ACK = 0x0283U, //! MFID $90 (Motorola) Unlock Selector Ack }; } diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/network/RESTAPI.cpp index 1cca6712..25fcb313 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/network/RESTAPI.cpp @@ -1517,6 +1517,26 @@ void RESTAPI::restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, else if (::strtolower(command) == RID_CMD_UNINHIBIT) { m_network->m_tagP25->write_TSDU_Ext_Func(peerId, ExtendedFunctions::UNINHIBIT, WUID_FNE, dstId); } + else if (::strtolower(command) == RID_CMD_DYN_REGRP) { + // validate source ID is a integer within the JSON blob + if (!req["tgId"].is()) { + errorPayload(reply, "talkgroup ID was not valid"); + return; + } + + uint32_t tgId = req["tgId"].get(); + + m_network->m_tagP25->write_TSDU_Ext_Func(peerId, ExtendedFunctions::DYN_REGRP_REQ, tgId, dstId); + } + else if (::strtolower(command) == RID_CMD_DYN_REGRP_CANCEL) { + m_network->m_tagP25->write_TSDU_Ext_Func(peerId, ExtendedFunctions::DYN_REGRP_CANCEL, 0U, dstId); + } + else if (::strtolower(command) == RID_CMD_DYN_REGRP_LOCK) { + m_network->m_tagP25->write_TSDU_Ext_Func(peerId, ExtendedFunctions::DYN_REGRP_LOCK, 0U, dstId); + } + else if (::strtolower(command) == RID_CMD_DYN_REGRP_UNLOCK) { + m_network->m_tagP25->write_TSDU_Ext_Func(peerId, ExtendedFunctions::DYN_REGRP_UNLOCK, 0U, dstId); + } else if (::strtolower(command) == RID_CMD_GAQ) { m_network->m_tagP25->write_TSDU_Grp_Aff_Q(peerId, dstId); } diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 329b026e..d5b89141 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -534,8 +534,13 @@ void TagP25Data::write_TSDU_Ext_Func(uint32_t peerId, uint32_t func, uint32_t ar iosp->setSrcId(arg); iosp->setDstId(dstId); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, op = $%02X, arg = %u, tgt = %u", - iosp->toString().c_str(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); + // class $02 is Motorola -- set the MFID properly + if ((func >> 8) == 0x02U) { + iosp->setMFId(MFG_MOT); + } + + LogMessage(LOG_NET, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", + iosp->toString().c_str(), iosp->getMFId(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); write_TSDU(peerId, iosp.get()); } diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index 7f5063fc..67e03f7c 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -1779,6 +1779,26 @@ void RESTAPI::restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, else if (::strtolower(command) == RID_CMD_UNINHIBIT) { m_p25->control()->writeRF_TSDU_Ext_Func(ExtendedFunctions::UNINHIBIT, WUID_FNE, dstId); } + else if (::strtolower(command) == RID_CMD_DYN_REGRP) { + // validate source ID is a integer within the JSON blob + if (!req["tgId"].is()) { + errorPayload(reply, "talkgroup ID was not valid"); + return; + } + + uint32_t tgId = req["tgId"].get(); + + m_p25->control()->writeRF_TSDU_Ext_Func(ExtendedFunctions::DYN_REGRP_REQ, tgId, dstId); + } + else if (::strtolower(command) == RID_CMD_DYN_REGRP_CANCEL) { + m_p25->control()->writeRF_TSDU_Ext_Func(ExtendedFunctions::DYN_REGRP_CANCEL, 0U, dstId); + } + else if (::strtolower(command) == RID_CMD_DYN_REGRP_LOCK) { + m_p25->control()->writeRF_TSDU_Ext_Func(ExtendedFunctions::DYN_REGRP_LOCK, 0U, dstId); + } + else if (::strtolower(command) == RID_CMD_DYN_REGRP_UNLOCK) { + m_p25->control()->writeRF_TSDU_Ext_Func(ExtendedFunctions::DYN_REGRP_UNLOCK, 0U, dstId); + } else if (::strtolower(command) == RID_CMD_GAQ) { m_p25->control()->writeRF_TSDU_Grp_Aff_Q(dstId); } diff --git a/src/host/network/RESTDefines.h b/src/host/network/RESTDefines.h index 4b6c0a58..046188a2 100644 --- a/src/host/network/RESTDefines.h +++ b/src/host/network/RESTDefines.h @@ -49,6 +49,10 @@ #define RID_CMD_CHECK "check" #define RID_CMD_INHIBIT "inhibit" #define RID_CMD_UNINHIBIT "uninhibit" +#define RID_CMD_DYN_REGRP "dyn-regrp" +#define RID_CMD_DYN_REGRP_CANCEL "dyn-regrp-cancel" +#define RID_CMD_DYN_REGRP_LOCK "dyn-regrp-lock" +#define RID_CMD_DYN_REGRP_UNLOCK "dyn-regrp-unlock" #define RID_CMD_GAQ "group-aff-req" #define RID_CMD_UREG "unit-reg" #define RID_CMD_EMERG "emerg" diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 417a733f..d4362dbd 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -487,14 +487,30 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetExtendedFunction() == ExtendedFunctions::CHECK_ACK) { + switch (iosp->getExtendedFunction()) { + // Standard + case ExtendedFunctions::CHECK_ACK: ::ActivityLog("P25", true, "radio check response from %u to %u", srcId, dstId); - } - else if (iosp->getExtendedFunction() == ExtendedFunctions::INHIBIT_ACK) { + break; + case ExtendedFunctions::INHIBIT_ACK: ::ActivityLog("P25", true, "radio inhibit response from %u to %u", srcId, dstId); - } - else if (iosp->getExtendedFunction() == ExtendedFunctions::UNINHIBIT_ACK) { + break; + case ExtendedFunctions::UNINHIBIT_ACK: ::ActivityLog("P25", true, "radio uninhibit response from %u to %u", srcId, dstId); + break; + // Dynamic Regroup + case ExtendedFunctions::DYN_REGRP_REQ_ACK: + ::ActivityLog("P25", true, "radio dynamic regroup response from %u to TG%u", srcId, dstId); + break; + case ExtendedFunctions::DYN_REGRP_CANCEL_ACK: + ::ActivityLog("P25", true, "radio dynamic regroup cancel response from %u to TG%u", srcId, dstId); + break; + case ExtendedFunctions::DYN_REGRP_LOCK_ACK: + ::ActivityLog("P25", true, "radio dynamic regroup selector lock response from %u", srcId); + break; + case ExtendedFunctions::DYN_REGRP_UNLOCK_ACK: + ::ActivityLog("P25", true, "radio dynamic regroup selector unlock response from %u", srcId); + break; } writeRF_TSDU_SBF(iosp, true); @@ -913,14 +929,30 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr } // generate activity log entry - if (iosp->getExtendedFunction() == ExtendedFunctions::CHECK_ACK) { + switch (iosp->getExtendedFunction()) { + // Standard + case ExtendedFunctions::CHECK_ACK: ::ActivityLog("P25", false, "radio check response from %u to %u", srcId, dstId); - } - else if (iosp->getExtendedFunction() == ExtendedFunctions::INHIBIT_ACK) { + break; + case ExtendedFunctions::INHIBIT_ACK: ::ActivityLog("P25", false, "radio inhibit response from %u to %u", srcId, dstId); - } - else if (iosp->getExtendedFunction() == ExtendedFunctions::UNINHIBIT_ACK) { + break; + case ExtendedFunctions::UNINHIBIT_ACK: ::ActivityLog("P25", false, "radio uninhibit response from %u to %u", srcId, dstId); + break; + // Dynamic Regroup + case ExtendedFunctions::DYN_REGRP_REQ_ACK: + ::ActivityLog("P25", false, "radio dynamic regroup response from %u to TG%u", srcId, dstId); + break; + case ExtendedFunctions::DYN_REGRP_CANCEL_ACK: + ::ActivityLog("P25", false, "radio dynamic regroup cancel response from %u to TG%u", srcId, dstId); + break; + case ExtendedFunctions::DYN_REGRP_LOCK_ACK: + ::ActivityLog("P25", false, "radio dynamic regroup selector lock response from %u", srcId); + break; + case ExtendedFunctions::DYN_REGRP_UNLOCK_ACK: + ::ActivityLog("P25", false, "radio dynamic regroup selector unlock response from %u", srcId); + break; } } break; @@ -1070,20 +1102,41 @@ void ControlSignaling::writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32 m_lastMFID = MFG_STANDARD; } + // class $02 is Motorola -- set the MFID properly + if ((func >> 8) == 0x02U) { + iosp->setMFId(MFG_MOT); + } + if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, op = $%02X, arg = %u, tgt = %u", - iosp->toString().c_str(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); + LogMessage(LOG_RF, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", + iosp->toString().c_str(), iosp->getMFId(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); } // generate activity log entry - if (func == ExtendedFunctions::CHECK) { + switch (func) { + // Standard + case ExtendedFunctions::CHECK: ::ActivityLog("P25", true, "radio check request from %u to %u", arg, dstId); - } - else if (func == ExtendedFunctions::INHIBIT) { + break; + case ExtendedFunctions::INHIBIT: ::ActivityLog("P25", true, "radio inhibit request from %u to %u", arg, dstId); - } - else if (func == ExtendedFunctions::UNINHIBIT) { + break; + case ExtendedFunctions::UNINHIBIT: ::ActivityLog("P25", true, "radio uninhibit request from %u to %u", arg, dstId); + break; + // Dynamic Regroup + case ExtendedFunctions::DYN_REGRP_REQ: + ::ActivityLog("P25", true, "radio dynamic regroup request TG%u for %u", arg, dstId); + break; + case ExtendedFunctions::DYN_REGRP_CANCEL: + ::ActivityLog("P25", true, "radio dynamic regroup cancel for %u", dstId); + break; + case ExtendedFunctions::DYN_REGRP_LOCK: + ::ActivityLog("P25", true, "radio dynamic regroup selector lock for %u", dstId); + break; + case ExtendedFunctions::DYN_REGRP_UNLOCK: + ::ActivityLog("P25", true, "radio dynamic regroup selector unlock for %u", dstId); + break; } writeRF_TSDU_SBF_Imm(iosp.get(), false); diff --git a/src/remote/RESTClientMain.cpp b/src/remote/RESTClientMain.cpp index f8b66c3a..96d940f1 100644 --- a/src/remote/RESTClientMain.cpp +++ b/src/remote/RESTClientMain.cpp @@ -92,12 +92,20 @@ #define RCD_P25_RID_CHECK "p25-rid-check" #define RCD_P25_RID_INHIBIT "p25-rid-inhibit" #define RCD_P25_RID_UNINHIBIT "p25-rid-uninhibit" +#define RCD_P25_RID_DYN_REGRP "p25-rid-dyn-regrp" +#define RCD_P25_RID_DYN_REGRP_CANCEL "p25-rid-dyn-regrp-cancel" +#define RCD_P25_RID_DYN_REGRP_LOCK "p25-rid-dyn-regrp-lock" +#define RCD_P25_RID_DYN_REGRP_UNLOCK "p25-rid-dyn-regrp-unlock" #define RCD_P25_RID_GAQ "p25-rid-gaq" #define RCD_P25_RID_UREG "p25-rid-ureg" #define RCD_FNE_P25_RID_PAGE "fne-p25-rid-page" #define RCD_FNE_P25_RID_CHECK "fne-p25-rid-check" #define RCD_FNE_P25_RID_INHIBIT "fne-p25-rid-inhibit" #define RCD_FNE_P25_RID_UNINHIBIT "fne-p25-rid-uninhibit" +#define RCD_FNE_P25_RID_DYN_REGRP "fne-p25-rid-dyn-regrp" +#define RCD_FNE_P25_RID_DYN_REGRP_CANCEL "fne-p25-rid-dyn-regrp-cancel" +#define RCD_FNE_P25_RID_DYN_REGRP_LOCK "fne-p25-rid-dyn-regrp-lock" +#define RCD_FNE_P25_RID_DYN_REGRP_UNLOCK "fne-p25-rid-dyn-regrp-unlock" #define RCD_FNE_P25_RID_GAQ "fne-p25-rid-gaq" #define RCD_FNE_P25_RID_UREG "fne-p25-rid-ureg" @@ -254,6 +262,14 @@ void usage(const char* message, const char* arg) reply += " p25-rid-check Radio Checks the specified RID\r\n"; reply += " p25-rid-inhibit Inhibits the specified RID\r\n"; reply += " p25-rid-uninhibit Uninhibits the specified RID\r\n"; + reply += " p25-rid-dyn-regrp \r\n"; + reply += " Dynamic Regroup Request to the specified RID\r\n"; + reply += " p25-rid-dyn-regrp-cancel \r\n"; + reply += " Dynamic Regroup Cancellation to the specified RID\r\n"; + reply += " p25-rid-dyn-regrp-lock \r\n"; + reply += " Dynamic Regroup Selector Lock to the specified RID\r\n"; + reply += " p25-rid-dyn-regrp-unlock \r\n"; + reply += " Dynamic Regroup Selector Unlock to the specified RID\r\n"; reply += " p25-rid-gaq Group affiliation queries the specified RID\r\n"; reply += " p25-rid-ureg Demand unit registration for the specified RID\r\n"; reply += "\r\n"; @@ -695,7 +711,7 @@ int main(int argc, char** argv) req["dstId"].set(dstId); if (rcom == RCD_FNE_P25_RID_PAGE) { - uint32_t peerId = getArgUInt32(args, 2U); + uint32_t peerId = getArgUInt32(args, 1U); req["peerId"].set(peerId); } @@ -708,7 +724,7 @@ int main(int argc, char** argv) req["dstId"].set(dstId); if (rcom == RCD_P25_RID_CHECK) { - uint32_t peerId = getArgUInt32(args, 2U); + uint32_t peerId = getArgUInt32(args, 1U); req["peerId"].set(peerId); } @@ -721,7 +737,7 @@ int main(int argc, char** argv) req["dstId"].set(dstId); if (rcom == RCD_FNE_P25_RID_INHIBIT) { - uint32_t peerId = getArgUInt32(args, 2U); + uint32_t peerId = getArgUInt32(args, 1U); req["peerId"].set(peerId); } @@ -734,12 +750,66 @@ int main(int argc, char** argv) req["dstId"].set(dstId); if (rcom == RCD_FNE_P25_RID_UNINHIBIT) { + uint32_t peerId = getArgUInt32(args, 1U); + req["peerId"].set(peerId); + } + + retCode = client->send(HTTP_PUT, PUT_P25_RID, req, response); + } + else if ((rcom == RCD_P25_RID_DYN_REGRP || rcom == RCD_FNE_P25_RID_DYN_REGRP) && argCnt >= 1U) { + json::object req = json::object(); + req["command"].set(std::string(RID_CMD_DYN_REGRP)); + uint32_t dstId = getArgUInt32(args, 0U); + req["dstId"].set(dstId); + uint32_t tgId = getArgUInt32(args, 1U); + req["tgId"].set(tgId); + + if (rcom == RCD_FNE_P25_RID_DYN_REGRP) { uint32_t peerId = getArgUInt32(args, 2U); req["peerId"].set(peerId); } retCode = client->send(HTTP_PUT, PUT_P25_RID, req, response); } + else if ((rcom == RCD_P25_RID_DYN_REGRP_CANCEL || rcom == RCD_FNE_P25_RID_DYN_REGRP_CANCEL) && argCnt >= 1U) { + json::object req = json::object(); + req["command"].set(std::string(RID_CMD_DYN_REGRP_CANCEL)); + uint32_t dstId = getArgUInt32(args, 0U); + req["dstId"].set(dstId); + + if (rcom == RCD_FNE_P25_RID_DYN_REGRP_CANCEL) { + uint32_t peerId = getArgUInt32(args, 1U); + req["peerId"].set(peerId); + } + + retCode = client->send(HTTP_PUT, PUT_P25_RID, req, response); + } + else if ((rcom == RCD_P25_RID_DYN_REGRP_LOCK || rcom == RCD_FNE_P25_RID_DYN_REGRP_LOCK) && argCnt >= 1U) { + json::object req = json::object(); + req["command"].set(std::string(RID_CMD_DYN_REGRP_LOCK)); + uint32_t dstId = getArgUInt32(args, 0U); + req["dstId"].set(dstId); + + if (rcom == RCD_FNE_P25_RID_DYN_REGRP_LOCK) { + uint32_t peerId = getArgUInt32(args, 1U); + req["peerId"].set(peerId); + } + + retCode = client->send(HTTP_PUT, PUT_P25_RID, req, response); + } + else if ((rcom == RCD_P25_RID_DYN_REGRP_UNLOCK || rcom == RCD_FNE_P25_RID_DYN_REGRP_UNLOCK) && argCnt >= 1U) { + json::object req = json::object(); + req["command"].set(std::string(RID_CMD_DYN_REGRP_UNLOCK)); + uint32_t dstId = getArgUInt32(args, 0U); + req["dstId"].set(dstId); + + if (rcom == RCD_FNE_P25_RID_DYN_REGRP_UNLOCK) { + uint32_t peerId = getArgUInt32(args, 1U); + req["peerId"].set(peerId); + } + + retCode = client->send(HTTP_PUT, PUT_P25_RID, req, response); + } else if ((rcom == RCD_P25_RID_GAQ || rcom == RCD_FNE_P25_RID_GAQ) && argCnt >= 1U) { json::object req = json::object(); req["command"].set(std::string(RID_CMD_GAQ)); @@ -747,7 +817,7 @@ int main(int argc, char** argv) req["dstId"].set(dstId); if (rcom == RCD_FNE_P25_RID_GAQ) { - uint32_t peerId = getArgUInt32(args, 2U); + uint32_t peerId = getArgUInt32(args, 1U); req["peerId"].set(peerId); } @@ -760,7 +830,7 @@ int main(int argc, char** argv) req["dstId"].set(dstId); if (rcom == RCD_FNE_P25_RID_UREG) { - uint32_t peerId = getArgUInt32(args, 2U); + uint32_t peerId = getArgUInt32(args, 1U); req["peerId"].set(peerId); }