diff --git a/src/svxlink/reflector/Reflector.cpp b/src/svxlink/reflector/Reflector.cpp index 756197b4..dbfa6d12 100644 --- a/src/svxlink/reflector/Reflector.cpp +++ b/src/svxlink/reflector/Reflector.cpp @@ -704,6 +704,25 @@ void Reflector::httpRequestReceived(Async::HttpServerConnection *con, } } } + if (qth.isMember("tx") && qth["tx"].isObject()) + { + //std::cout << "### Found tx" << std::endl; + Json::Value::Members txs(qth["tx"].getMemberNames()); + for (Json::Value::Members::const_iterator it=txs.begin(); it!=txs.end(); ++it) + { + //std::cout << "### member=" << *it << std::endl; + const std::string& tx_id_str(*it); + if (tx_id_str.size() == 1) + { + char tx_id(tx_id_str[0]); + Json::Value& tx(qth["tx"][tx_id_str]); + if (client->txExist(tx_id)) + { + tx["transmit"] = client->txTransmit(tx_id); + } + } + } + } } } status["nodes"][client->callsign()] = node; diff --git a/src/svxlink/reflector/ReflectorClient.cpp b/src/svxlink/reflector/ReflectorClient.cpp index d32cf2c6..60da69d9 100644 --- a/src/svxlink/reflector/ReflectorClient.cpp +++ b/src/svxlink/reflector/ReflectorClient.cpp @@ -318,6 +318,9 @@ void ReflectorClient::onFrameReceived(FramedTcpConnection *con, case MsgSignalStrengthValues::TYPE: handleMsgSignalStrengthValues(ss); break; + case MsgTxStatus::TYPE: + handleMsgTxStatus(ss); + break; #if 0 case MsgNodeInfo::TYPE: handleNodeInfo(ss); @@ -555,6 +558,28 @@ void ReflectorClient::handleMsgSignalStrengthValues(std::istream& is) } /* ReflectorClient::handleMsgSignalStrengthValues */ +void ReflectorClient::handleMsgTxStatus(std::istream& is) +{ + MsgTxStatus msg; + if (!msg.unpack(is)) + { + cerr << "*** WARNING[" << callsign() + << "]: Could not unpack incoming MsgTxStatus message" << endl; + return; + } + typedef MsgTxStatus::Txs::const_iterator TxsIter; + for (TxsIter it = msg.txs().begin(); it != msg.txs().end(); ++it) + { + const MsgTxStatus::Tx& tx = *it; + std::cout << "### MsgTxStatus:" + << " id=" << tx.id() + << " transmit=" << tx.transmit() + << std::endl; + setTxTransmit(tx.id(), tx.transmit()); + } +} /* ReflectorClient::handleMsgTxStatus */ + + void ReflectorClient::handleRequestQsy(std::istream& is) { MsgRequestQsy msg; diff --git a/src/svxlink/reflector/ReflectorClient.h b/src/svxlink/reflector/ReflectorClient.h index 0428727d..339c04a1 100644 --- a/src/svxlink/reflector/ReflectorClient.h +++ b/src/svxlink/reflector/ReflectorClient.h @@ -127,6 +127,13 @@ class ReflectorClient }; typedef std::map RxMap; + struct Tx + { + std::string name; + bool transmit; + }; + typedef std::map TxMap; + class Filter { public: @@ -394,6 +401,14 @@ class ReflectorClient bool rxActive(char id) { return m_rx_map[id].active; } //RxMap& rxMap(void) { return m_rx_map; } //const RxMap& rxMap(void) const { return m_rx_map; } + + bool txExist(char tx_id) const + { + return m_tx_map.find(tx_id) != m_tx_map.end(); + } + void setTxTransmit(char id, bool transmit) { m_tx_map[id].transmit = transmit; } + bool txTransmit(char id) { return m_tx_map[id].transmit; } + const Json::Value& nodeInfo(void) const { return m_node_info; } private: @@ -429,6 +444,7 @@ class ReflectorClient uint32_t m_current_tg; std::set m_monitored_tgs; RxMap m_rx_map; + TxMap m_tx_map; Json::Value m_node_info; ReflectorClient(const ReflectorClient&); @@ -441,6 +457,7 @@ class ReflectorClient void handleTgMonitor(std::istream& is); void handleNodeInfo(std::istream& is); void handleMsgSignalStrengthValues(std::istream& is); + void handleMsgTxStatus(std::istream& is); void handleRequestQsy(std::istream& is); void handleStateEvent(std::istream& is); void handleMsgError(std::istream& is); diff --git a/src/svxlink/reflector/ReflectorMsg.h b/src/svxlink/reflector/ReflectorMsg.h index 2ebaa8d1..3f919c44 100644 --- a/src/svxlink/reflector/ReflectorMsg.h +++ b/src/svxlink/reflector/ReflectorMsg.h @@ -1021,6 +1021,57 @@ struct MsgSignalStrengthValues }; /* MsgSignalStrengthValues */ +/** +@brief Tx status message +@author Tobias Blomberg / SM0SVX +@date 2019-10-19 + +This message is used by a client to send tx status to the reflector server. +*/ +class MsgTxStatus : public ReflectorMsgBase<113> +{ + public: + class Tx : public Async::Msg + { + public: + Tx(void) : m_id('?'), m_flags(0) {} + Tx(char id) : m_id(id), m_flags(0) {} + void setTransmit(bool transmit) { setBit(BIT_TRANSMIT, transmit); } + bool transmit(void) const { return getBit(BIT_TRANSMIT); } + char id(void) const { return m_id; } + + ASYNC_MSG_MEMBERS(m_id, m_flags) + + private: + typedef enum {BIT_TRANSMIT=0} Bit; + + char m_id; + uint8_t m_flags; + + bool getBit(Bit bitno) const + { + uint8_t bit = 1 << static_cast(bitno); + return (m_flags & bit) != 0; + } + void setBit(Bit bitno, bool is_enabled) + { + uint8_t bit = 1 << static_cast(bitno); + m_flags = (m_flags & ~bit) | (is_enabled ? bit : 0); + } + }; + typedef std::vector Txs; + + MsgTxStatus(void) {} + MsgTxStatus(const Txs& txs) : m_txs(txs) {} + Txs& txs(void) { return m_txs; } + void pushBack(const Tx& tx) { m_txs.push_back(tx); } + + ASYNC_MSG_MEMBERS(m_txs) + + private: + Txs m_txs; +}; /* class MsgTxStatus */ + /***************************** UDP Messages *****************************/ diff --git a/src/svxlink/svxlink/Logic.cpp b/src/svxlink/svxlink/Logic.cpp index 194d7997..e53846e8 100644 --- a/src/svxlink/svxlink/Logic.cpp +++ b/src/svxlink/svxlink/Logic.cpp @@ -557,6 +557,7 @@ bool Logic::initialize(void) } tx().transmitterStateChange.connect( mem_fun(*this, &Logic::transmitterStateChange)); + tx().publishStateEvent.connect(mem_fun(*this, &Logic::onPublishStateEvent)); prev_tx_src->registerSink(m_tx); prev_tx_src = 0; diff --git a/src/svxlink/svxlink/ReflectorLogic.cpp b/src/svxlink/svxlink/ReflectorLogic.cpp index a91fe15d..2d401f32 100644 --- a/src/svxlink/svxlink/ReflectorLogic.cpp +++ b/src/svxlink/svxlink/ReflectorLogic.cpp @@ -512,6 +512,54 @@ void ReflectorLogic::remoteReceivedPublishStateEvent( //sendUdpMsg(msg); sendMsg(msg); } + else if (event_name == "Tx:state") + { + MsgTxStatus msg; + std::istringstream is(data); + Json::Value tx_data; + is >> tx_data; + std::string name = tx_data.get("name", "").asString(); + std::string id_str = tx_data.get("id", "?").asString(); + if (id_str.size() != 1) + { + return; + } + char id = id_str[0]; + if (id != '\0') + { + bool transmit = tx_data.get("transmit", false).asBool(); + MsgTxStatus::Tx tx(id); + tx.setTransmit(transmit); + msg.pushBack(tx); + sendMsg(msg); + } + } + else if (event_name == "MultiTx:state") + { + MsgTxStatus msg; + std::istringstream is(data); + Json::Value tx_arr; + is >> tx_arr; + for (Json::Value::ArrayIndex i = 0; i != tx_arr.size(); i++) + { + Json::Value& tx_data = tx_arr[i]; + std::string name = tx_data.get("name", "").asString(); + std::string id_str = tx_data.get("id", "").asString(); + if (id_str.size() != 1) + { + return; + } + char id = id_str[0]; + if (id != '\0') + { + bool transmit = tx_data.get("transmit", false).asBool(); + MsgTxStatus::Tx tx(id); + tx.setTransmit(transmit); + msg.pushBack(tx); + } + } + sendMsg(msg); + } } /* ReflectorLogic::remoteReceivedPublishStateEvent */ diff --git a/src/svxlink/svxlink/svxlink.conf.in b/src/svxlink/svxlink/svxlink.conf.in index 54de22ea..2b1c23e4 100644 --- a/src/svxlink/svxlink/svxlink.conf.in +++ b/src/svxlink/svxlink/svxlink.conf.in @@ -247,6 +247,7 @@ DTMF_SERIAL=/dev/ttyS0 [Tx1] TYPE=Local +TX_ID=T AUDIO_DEV=alsa:plughw:0 AUDIO_CHANNEL=0 #AUDIO_DEV_KEEP_OPEN=0 diff --git a/src/svxlink/trx/LocalTx.cpp b/src/svxlink/trx/LocalTx.cpp index 98e5b6b1..abbecdfa 100644 --- a/src/svxlink/trx/LocalTx.cpp +++ b/src/svxlink/trx/LocalTx.cpp @@ -286,7 +286,13 @@ LocalTx::~LocalTx(void) bool LocalTx::initialize(void) { string value; - + + char tx_id = '\0'; + if (cfg.getValue(name(), "TX_ID", tx_id)) + { + setId(tx_id); + } + string audio_dev; if (!cfg.getValue(name(), "AUDIO_DEV", audio_dev)) { diff --git a/src/svxlink/trx/MultiTx.cpp b/src/svxlink/trx/MultiTx.cpp index 8434361b..de4b449d 100644 --- a/src/svxlink/trx/MultiTx.cpp +++ b/src/svxlink/trx/MultiTx.cpp @@ -43,6 +43,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ****************************************************************************/ #include +#include @@ -115,9 +116,11 @@ using namespace Async; ****************************************************************************/ MultiTx::MultiTx(Config& cfg, const string& name) - : Tx(name), cfg(cfg), splitter(0) + : Tx(name), cfg(cfg), splitter(0), + m_tx_state_delay_timer(100, Async::Timer::TYPE_ONESHOT, false) { - + m_tx_state_delay_timer.expired.connect( + sigc::hide(sigc::mem_fun(*this, &MultiTx::txStateDelayExpired))); } /* MultiTx::MultiTx */ @@ -139,6 +142,12 @@ MultiTx::~MultiTx(void) bool MultiTx::initialize(void) { + char tx_id = '\0'; + if (cfg.getValue(name(), "TX_ID", tx_id)) + { + setId(tx_id); + } + string transmitters; if (!cfg.getValue(name(), "TRANSMITTERS", transmitters)) { @@ -270,19 +279,50 @@ void MultiTx::sendData(const std::vector &msg) void MultiTx::onTransmitterStateChange(void) { + bool is_transmitting = false; list::const_iterator it; for (it=txs.begin(); it!=txs.end(); ++it) { if ((*it)->isTransmitting()) { - setIsTransmitting(true); - return; + is_transmitting = true; + break; } } - setIsTransmitting(false); + setIsTransmitting(is_transmitting); + m_tx_state_delay_timer.setEnable(true); + m_tx_state_delay_timer.reset(); } /* MultiTx::onTransmitterStateChange */ +void MultiTx::txStateDelayExpired(void) +{ + Json::Value event(Json::arrayValue); + list::const_iterator it; + for (it=txs.begin(); it!=txs.end(); ++it) + { + Tx *tx = *it; + char tx_id = tx->id(); + if (tx_id != '\0') + { + Json::Value tx_state(Json::objectValue); + tx_state["name"] = tx->name(); + tx_state["id"] = std::string(&tx_id, &tx_id+1); + tx_state["transmit"] = tx->isTransmitting(); + event.append(tx_state); + } + } + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = ""; //The JSON document is written on a single line + Json::StreamWriter* writer = builder.newStreamWriter(); + std::stringstream os; + writer->write(event, &os); + delete writer; + //std::cout << "### " << os.str() << std::endl; + publishStateEvent("MultiTx:state", os.str()); +} /* MultiTx::txStateDelayExpired */ + /* * This file has not been truncated diff --git a/src/svxlink/trx/MultiTx.h b/src/svxlink/trx/MultiTx.h index 9b813b06..725e7258 100644 --- a/src/svxlink/trx/MultiTx.h +++ b/src/svxlink/trx/MultiTx.h @@ -44,6 +44,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ****************************************************************************/ +#include /**************************************************************************** @@ -190,10 +191,12 @@ class MultiTx : public Tx Async::Config &cfg; std::list txs; Async::AudioSplitter *splitter; + Async::Timer m_tx_state_delay_timer; MultiTx(const MultiTx&); MultiTx& operator=(const MultiTx&); void onTransmitterStateChange(void); + void txStateDelayExpired(void); }; /* class MultiTx */ diff --git a/src/svxlink/trx/Tx.cpp b/src/svxlink/trx/Tx.cpp index d8ebcefb..68c461f4 100644 --- a/src/svxlink/trx/Tx.cpp +++ b/src/svxlink/trx/Tx.cpp @@ -33,6 +33,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ****************************************************************************/ #include +#include +#include @@ -57,7 +59,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "DummyRxTx.h" - /**************************************************************************** * * Namespaces to use @@ -242,6 +243,23 @@ void Tx::setIsTransmitting(bool is_transmitting) } m_is_transmitting = is_transmitting; transmitterStateChange(is_transmitting); + + char tx_id = id(); + if (tx_id != '\0') + { + Json::Value tx(Json::objectValue); + tx["name"] = name(); + tx["id"] = std::string(&tx_id, &tx_id+1); + tx["transmit"] = is_transmitting; + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = ""; //The JSON document is written on a single line + Json::StreamWriter* writer = builder.newStreamWriter(); + std::stringstream os; + writer->write(tx, &os); + delete writer; + publishStateEvent("Tx:state", os.str()); + } } } /* Tx::setIsTransmitting */ diff --git a/src/svxlink/trx/Tx.h b/src/svxlink/trx/Tx.h index e4b0a5a7..0e149c4a 100644 --- a/src/svxlink/trx/Tx.h +++ b/src/svxlink/trx/Tx.h @@ -136,7 +136,8 @@ class Tx : public sigc::trackable, public Async::AudioSink * @param tx_name The name of the transmitter */ Tx(std::string tx_name) - : m_name(tx_name), m_verbose(true), m_is_transmitting(false) + : m_name(tx_name), m_tx_id('\0'), m_verbose(true), + m_is_transmitting(false) { } @@ -157,6 +158,12 @@ class Tx : public sigc::trackable, public Async::AudioSink */ const std::string& name(void) const { return m_name; } + /** + * @brief Return the ID of the transmitter + * @return The transmitter ID is returned + */ + char id(void) const { return m_tx_id; } + /* * @brief Set the verbosity level of the transmitter * @param verbose Set to \em false to keep the rx from printing things @@ -246,11 +253,27 @@ class Tx : public sigc::trackable, public Async::AudioSink */ sigc::signal transmitterStateChange; + /** + * @brief A signal that is emitted to publish a state update event + * @param event_name The name of the event + * @param msg The state update message + * + * This signal is emitted when a receiver wish to publish a state update + * message. A state update message is a free text message that can be used + * by subscribers to act on certain state changes within SvxLink. The + * event name must be unique within SvxLink. The recommended format is + * :, e.g. Tx:tx_state. + */ + sigc::signal publishStateEvent; + protected: + void setId(char id) { m_tx_id = id; } void setIsTransmitting(bool is_transmitting); private: std::string m_name; + char m_tx_id; bool m_verbose; bool m_is_transmitting; diff --git a/src/versions b/src/versions index 1ca36859..a6f0485c 100644 --- a/src/versions +++ b/src/versions @@ -11,7 +11,7 @@ LIBECHOLIB=1.3.3 LIBASYNC=1.6.0.99.3-reflector_tg # SvxLink versions -SVXLINK=1.7.99.9-reflector_tg +SVXLINK=1.7.99.10-reflector_tg MODULE_HELP=1.0.0 MODULE_PARROT=1.1.1 MODULE_ECHO_LINK=1.5.0