diff --git a/BUILD.md b/BUILD.md index a3b8623..1a57669 100644 --- a/BUILD.md +++ b/BUILD.md @@ -83,3 +83,15 @@ This would build and install all the programs in this repo using 4 threads (parallel build jobs), in release mode (no debug symbols) with a modified configuration directory. +### MQTT Dependency + +MQTT support is always compiled in. Install `libmosquitto-dev` before +building: + +```sh +sudo apt install libmosquitto-dev +``` + +See [MQTT.md](MQTT.md) for configuration, topic structure, and JSON format. +To disable MQTT at runtime, leave `mqttAddress` empty in the config file. + diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 0000000..cefe38b --- /dev/null +++ b/CONFIGURATION.md @@ -0,0 +1,340 @@ +# ircDDBGateway Configuration Reference + +> **Note:** The config parser treats any line beginning with `#` as a +> comment, and blank lines are skipped. Use `ircddbgateway.example` as +> your starting point for a clean config. + +**Config file location:** `/etc/ircddbgateway` +(or `/etc/ircddbgateway_` when using the `-name` command line option) + +**Format:** `key=value` — one setting per line, no spaces around the `=`. +Boolean values use `0` for off/false and `1` for on/true. + +--- + +## Gateway Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `gatewayType=0` | `0` | Gateway type — see [Gateway Types](#gateway-types) below. | +| `gatewayCallsign=` | *(empty)* | Gateway callsign, padded to 8 characters (e.g. `GB7XX G`). The final character is always `G` for gateway. | +| `gatewayAddress=` | *(empty)* | IP address to bind for gateway services. Leave empty for automatic. | +| `icomAddress=172.16.0.20` | `172.16.0.20` | Local IP address for Icom repeater stack communication. | +| `icomPort=20000` | `20000` | UDP port for Icom repeater stack communication. | +| `hbAddress=127.0.0.1` | `127.0.0.1` | IP address of the homebrew repeater (DStarRepeater). | +| `hbPort=20010` | `20010` | UDP port for homebrew repeater communication. | +| `latitude=0.000000` | `0.0` | Gateway latitude in decimal degrees (positive = North). | +| `longitude=0.000000` | `0.0` | Gateway longitude in decimal degrees (positive = East). | +| `description1=` | *(empty)* | First line of the gateway description (shown on reflector dashboards). | +| `description2=` | *(empty)* | Second line of the gateway description. | +| `url=` | *(empty)* | URL for the gateway (shown on reflector dashboards). | + +### Gateway Types + +| Value | Type | Description | +|:-----:|------|-------------| +| `0` | Repeater | Standard repeater gateway | +| `1` | Hotspot | Personal hotspot gateway | +| `2` | Dongle | DV Dongle/AMBE dongle gateway | +| `3` | STARnet | STARnet-only server (no repeater) | + +--- + +## Repeater Settings (1–4) + +Up to four repeater modules can be configured. Each module uses a numeric suffix (`1` through `4`). The settings below show the suffix `1` — replace with `2`, `3`, or `4` for additional modules. + +| Setting | Default | Description | +|---------|---------|-------------| +| `repeaterCall1=` | *(empty)* | Repeater callsign, 7 characters (e.g. `GB7XX`). Leave empty to disable this module. | +| `repeaterBand1= ` | ` ` *(space)* | Module letter (`A`, `B`, `C`, `D`, or `E`). Combined with callsign to form the 8-character repeater ID (e.g. `GB7XX B`). | +| `repeaterType1=0` | `0` | Repeater hardware type — see [Hardware Types](#hardware-types) below. | +| `repeaterAddress1=127.0.0.1` | `127.0.0.1` | IP address of the repeater hardware or DStarRepeater instance. | +| `repeaterPort1=20011` | `20011` | UDP port for repeater communication. Default ports are 20011–20014 for modules 1–4. | +| `reflector1=` | *(empty)* | Default reflector to link at startup (e.g. `REF001 C` or `XRF012 A`). | +| `atStartup1=0` | `0` | `1` = link to the default reflector at startup. | +| `reconnect1=0` | `0` | Reconnect behaviour — see [Reconnect Values](#reconnect-values) below. | +| `frequency1=0.00000` | `0.0` | Operating frequency in MHz (e.g. `438.50000`). Reported to reflector directories. | +| `offset1=0.0000` | `0.0` | Repeater offset in MHz (e.g. `-7.6000`). | +| `rangeKms1=0.000` | `0.0` | Coverage range in kilometres. | +| `latitude1=0.000000` | `0.0` | Repeater latitude in decimal degrees. | +| `longitude1=0.000000` | `0.0` | Repeater longitude in decimal degrees. | +| `agl1=0.000` | `0.0` | Antenna height above ground level in metres. | +| `description1_1=` | *(empty)* | First line of the repeater module description. | +| `description1_2=` | *(empty)* | Second line of the repeater module description. | +| `url1=` | *(empty)* | URL for this repeater module. | +| `band1_1=0` | `0` | Band data byte 1 (used by Icom stacks). | +| `band1_2=0` | `0` | Band data byte 2. | +| `band1_3=0` | `0` | Band data byte 3. | + +### Hardware Types + +| Value | Type | Description | +|:-----:|------|-------------| +| `0` | Homebrew | DStarRepeater-based homebrew repeater | +| `1` | Icom | Icom ID-RP2C repeater stack | +| `2` | Dummy | Dummy/test repeater (no hardware) | + +### Reconnect Values + +| Value | Behaviour | +|:-----:|-----------| +| `0` | Never — once unlinked, stay unlinked | +| `1` | Fixed — reconnect immediately to the default reflector | +| `2` | After 5 minutes | +| `3` | After 10 minutes | +| `4` | After 15 minutes | +| `5` | After 20 minutes | +| `6` | After 25 minutes | +| `7` | After 30 minutes | +| `8` | After 60 minutes | +| `9` | After 90 minutes | +| `10` | After 120 minutes | +| `11` | After 180 minutes | + +--- + +## ircDDB Network Settings (1–4) + +Up to four ircDDB networks can be configured simultaneously. Networks 1 and 2 are enabled by default. The settings below show network `1` — networks 2–4 use `2`, `3`, or `4` appended to the key names (e.g. `ircddbHostname2`). + +> **Note:** Network 1 uses key names without a numeric suffix (e.g. +> `ircddbHostname` not `ircddbHostname1`). + +| Setting | Default | Description | +|---------|---------|-------------| +| `ircddbEnabled=1` | `1` (nets 1–2), `0` (nets 3–4) | `1` = enable this ircDDB network. | +| `ircddbHostname=group1-irc.ircddb.net` | `group1-irc.ircddb.net` (net 1), `rr.openquad.net` (net 2), *(empty)* (nets 3–4) | Hostname of the ircDDB server. | +| `ircddbUsername=` | *(empty)* | Username for ircDDB authentication (usually your callsign). | +| `ircddbPassword=` | *(empty)* | Password for ircDDB authentication. | + +--- + +## Protocol and Service Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `aprsEnabled=0` | `0` | `1` = enable DPRS-to-APRS-IS gateway. Requires an APRS-IS connection. | +| `dextraEnabled=1` | `1` | `1` = enable DExtra (XRF) reflector protocol. | +| `dextraMaxDongles=5` | `5` | Maximum number of DExtra dongle connections allowed. | +| `dplusEnabled=0` | `0` | `1` = enable D-Plus (REF) reflector protocol. Requires registration. | +| `dplusMaxDongles=5` | `5` | Maximum number of D-Plus dongle connections allowed. | +| `dplusLogin=` | *(empty)* | Callsign for D-Plus authentication. Must be registered with US Trust. | +| `dcsEnabled=1` | `1` | `1` = enable DCS reflector protocol. | +| `ccsEnabled=1` | `1` | `1` = enable CCS7 call routing. | +| `ccsHost=CCS704 ` | `CCS704 ` | CCS server callsign (8 characters, space-padded). | +| `xlxEnabled=1` | `1` | `1` = enable XLX reflector protocol. | +| `xlxHostsFileUrl=...` | `http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster` | URL to download the XLX hosts list. | + +--- + +## STARnet Settings (1–5) + +Up to five STARnet digital voice groups can be configured. Each group uses a numeric suffix (`1` through `5`). + +| Setting | Default | Description | +|---------|---------|-------------| +| `starNetBand1=` | *(empty)* | Module letter for this STARnet group (e.g. `A`). Leave empty to disable. | +| `starNetCallsign1=` | *(empty)* | Callsign for the STARnet group (8 characters, e.g. `STN001 L`). | +| `starNetLogoff1=` | *(empty)* | Callsign to send to log off from the group. | +| `starNetInfo1=` | *(empty)* | Info text for the group. | +| `starNetPermanent1=` | *(empty)* | Comma-separated list of callsigns permanently subscribed to the group. | +| `starNetUserTimeout1=300` | `300` | User inactivity timeout in seconds. Users are removed from the group after this period. | +| `starNetGroupTimeout1=300` | `300` | Group inactivity timeout in seconds. The group is deactivated after this period. | +| `starNetCallsignSwitch1=0` | `0` | Callsign display mode — see [Callsign Switch Values](#callsign-switch-values) below. | +| `starNetTXMsgSwitch1=1` | `1` | `1` = transmit group status messages. | +| `starNetReflector1=` | *(empty)* | Reflector to link the STARnet group to (DExtra protocol). | + +### Callsign Switch Values + +| Value | Mode | Description | +|:-----:|------|-------------| +| `0` | Group Callsign | Show the group callsign in headers | +| `1` | User Callsign | Show the originating user's callsign in headers | + +--- + +## Remote Control + +| Setting | Default | Description | +|---------|---------|-------------| +| `remoteEnabled=0` | `0` | `1` = enable the remote control interface. | +| `remotePassword=` | *(empty)* | Password for remote control authentication. | +| `remotePort=0` | `0` | UDP port for remote control. `0` = disabled. | + +--- + +## Miscellaneous + +| Setting | Default | Description | +|---------|---------|-------------| +| `language=0` | `0` | Language for voice announcements — see [Language Values](#language-values) below. | +| `infoEnabled=1` | `1` | `1` = enable the `I` (info) callsign command. | +| `echoEnabled=1` | `1` | `1` = enable the echo (parrot) test feature. | +| `logEnabled=0` | `0` | `1` = enable logging to file. | +| `dratsEnabled=0` | `0` | `1` = enable D-RATS data transfer support. | +| `dtmfEnabled=1` | `1` | `1` = enable DTMF command decoding. | + +### Language Values + +| Value | Language | +|:-----:|----------| +| `0` | English (UK) | +| `1` | Deutsch | +| `2` | Dansk | +| `3` | Francais | +| `4` | Italiano | +| `5` | Polski | +| `6` | English (US) | +| `7` | Espanol | +| `8` | Svenska | +| `9` | Nederlands (NL) | +| `10` | Nederlands (BE) | +| `11` | Norsk | +| `12` | Portugues | + +--- + +## GPSD Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `gpsdEnabled=0` | `0` | `1` = enable GPSD support for automatic position updates. | +| `gpsdAddress=127.0.0.1` | `127.0.0.1` | GPSD server address. | +| `gpsdPort=2947` | `2947` | GPSD server port. | + +--- + +## MQTT Settings + +*MQTT is always compiled in. To disable at runtime, leave `mqttAddress` empty. See [MQTT.md](MQTT.md) for full details on topics, JSON format, and troubleshooting.* + +| Setting | Default | Description | +|---------|---------|-------------| +| `mqttAddress=127.0.0.1` | `127.0.0.1` | MQTT broker hostname or IP address. Set to empty to disable MQTT at runtime even when compiled in. | +| `mqttPort=1883` | `1883` | MQTT broker port. | +| `mqttKeepalive=60` | `60` | Keepalive interval in seconds. | +| `mqttAuth=0` | `0` | `1` = authenticate with the broker using `mqttUsername` and `mqttPassword`. | +| `mqttUsername=mmdvm` | `mmdvm` | Broker username (when `mqttAuth=1`). | +| `mqttPassword=mmdvm` | `mmdvm` | Broker password (when `mqttAuth=1`). | +| `mqttName=ircddb-gateway` | `ircddb-gateway` | Client name and topic prefix for all published messages. | + +### MQTT Topics + +With the default `mqttName` of `ircddb-gateway`, the following topics are published: + +| Topic | Content | +|-------|---------| +| `ircddb-gateway/log` | Timestamped log messages. | +| `ircddb-gateway/json` | JSON link status events (linking, unlinking, relinking, failed, status). | +| `ircddb-gateway/aprs-gateway/aprs` | APRS position data from the DPRS gateway. | + +--- + +## Window Position (GUI only) + +| Setting | Default | Description | +|---------|---------|-------------| +| `windowX=-1` | `-1` | Window X position. `-1` for system default. | +| `windowY=-1` | `-1` | Window Y position. `-1` for system default. | + +--- + +# Companion Program Configuration + +The following companion programs have their own separate config files. +The same `key=value` format and comment rules apply. Example configs +are provided in `linux/`. + +--- + +## TimeServer + +**Config file:** `/etc/timeserver` +(or `/etc/timeserver_` when using the `-name` option) + +**Example:** [linux/timeserver.example](linux/timeserver.example) + +The TimeServer transmits time announcements to one or more repeater modules at a configurable interval. + +| Setting | Default | Description | +|---------|---------|-------------| +| `callsign=` | *(empty)* | Gateway callsign (8 characters, e.g. `GB7XX G`). | +| `sendA=0` | `0` | `1` = send time announcements to module A. | +| `sendB=0` | `0` | `1` = send time announcements to module B. | +| `sendC=0` | `0` | `1` = send time announcements to module C. | +| `sendD=0` | `0` | `1` = send time announcements to module D. | +| `sendE=0` | `0` | `1` = send time announcements to module E. | +| `address=127.0.0.1` | `127.0.0.1` | Address of the ircDDBGateway instance. | +| `language=0` | `0` | Announcement language — see [TimeServer Language Values](#timeserver-language-values) below. | +| `format=0` | `0` | Announcement format — see [Format Values](#format-values) below. | +| `interval=0` | `0` | Announcement interval — see [Interval Values](#interval-values) below. | +| `windowX=-1` | `-1` | Window X position (GUI only). | +| `windowY=-1` | `-1` | Window Y position (GUI only). | + +### TimeServer Language Values + +| Value | Language | +|:-----:|----------| +| `0` | English (UK) 1 | +| `1` | English (UK) 2 | +| `2` | English (US) 1 | +| `3` | English (US) 2 | +| `4` | Deutsch 1 | +| `5` | Deutsch 2 | +| `6` | Francais | +| `7` | Nederlands | +| `8` | Svenska | +| `9` | Espanol | +| `10` | Norsk | +| `11` | Portugues | + +### Format Values + +| Value | Format | Description | +|:-----:|--------|-------------| +| `0` | Voice Time | Voice announcement of the current time | +| `1` | Voice All | Voice announcement of time, date, and location | +| `2` | Text Time | Slow-data text with the current time | + +### Interval Values + +| Value | Interval | +|:-----:|----------| +| `0` | Every 15 minutes | +| `1` | Every 30 minutes | +| `2` | Every 60 minutes | + +--- + +## TimerControl + +**Config file:** `/etc/timercontrol` +(or `/etc/timercontrol_` when using the `-name` option) + +**Example:** [linux/timercontrol.example](linux/timercontrol.example) + +TimerControl provides scheduled reflector linking — automatically linking and unlinking repeater modules at configured times. + +| Setting | Default | Description | +|---------|---------|-------------| +| `address=` | *(empty)* | Address of the ircDDBGateway remote control interface. | +| `port=0` | `0` | Port of the ircDDBGateway remote control interface. Must match `remotePort` in the gateway config. | +| `password=` | *(empty)* | Password for the remote control interface. Must match `remotePassword` in the gateway config. | +| `delay=0` | `0` | `1` = delay scheduled actions by a random amount to avoid all repeaters acting simultaneously. | +| `windowX=-1` | `-1` | Window X position (GUI only). | +| `windowY=-1` | `-1` | Window Y position (GUI only). | + +--- + +## Programs Without Config Files + +The following companion programs take their parameters entirely from +command line arguments and do not use config files: + +| Program | Description | +|---------|-------------| +| `texttransmit` | Sends a slow-data text message to a repeater module. | +| `voicetransmit` | Sends a voice announcement file to a repeater module. | +| `aprstransmit` | Sends APRS data to the gateway. | +| `remotecontrol` | GUI remote control client (uses wxWidgets config storage, not a text file). | diff --git a/Common/CCSHandler.cpp b/Common/CCSHandler.cpp index a787256..2f99208 100644 --- a/Common/CCSHandler.cpp +++ b/Common/CCSHandler.cpp @@ -253,7 +253,7 @@ void CCCSHandler::process(CAMBEData& data) m_handler->ccsLinkMade(m_yourCall, m_direction); wxLogMessage(wxT("CCS: New incoming link to %s from %s @ %s"), m_local.c_str(), m_yourCall.c_str(), m_rptCall1.c_str()); - WriteJSONLinking("ccs", "in", "network", m_local, m_yourCall); + WriteJSONLinking(m_local.ToStdString(), "network", "ccs", m_yourCall.ToStdString()); } else { if (!m_yourCall.IsSameAs(myCall1) && !m_rptCall1.IsSameAs(rptCall1)) { wxLogMessage(wxT("CCS: Rejecting new incoming CCS link from %s @ %s to %s"), myCall1.c_str(), rptCall1.c_str(), yourCall.c_str()); @@ -305,7 +305,7 @@ void CCCSHandler::process(CCCSData& data) case CT_TERMINATE: if (m_state == CS_ACTIVE) { wxLogMessage(wxT("CCS: Link between %s and %s has been terminated"), data.getLocal().c_str(), data.getRemote().c_str()); - WriteJSONUnlinked("ccs", "user", m_local, m_yourCall); + WriteJSONUnlinked(m_local.ToStdString(), "user"); m_stateChange = true; m_state = CS_CONNECTED; m_inactivityTimer.stop(); @@ -423,13 +423,13 @@ void CCCSHandler::startLink(const wxString& dtmf, const wxString& user, const wx wxString callsign = findInCache(dtmf); if (!callsign.IsEmpty()) { wxLogMessage(wxT("CCS: New outgoing link to %s/%s via %s by %s"), dtmf.c_str(), callsign.c_str(), type.c_str(), user.c_str()); - WriteJSONLinking("ccs", "out", "user", user, callsign); + WriteJSONLinking(user.ToStdString(), "user", "ccs", callsign.ToStdString()); m_handler->ccsLinkMade(callsign, m_direction); m_yourCall = callsign; m_rptCall1 = callsign; } else { wxLogMessage(wxT("CCS: New outgoing link to %s via %s by %s"), dtmf.c_str(), type.c_str(), user.c_str()); - WriteJSONLinking("ccs", "out", "user", user, dtmf); + WriteJSONLinking(user.ToStdString(), "user", "ccs", dtmf.ToStdString()); m_yourCall = dtmf; m_yourCall.resize(LONG_CALLSIGN_LENGTH, wxT(' ')); m_rptCall1.Clear(); @@ -452,7 +452,7 @@ void CCCSHandler::stopLink(const wxString& user, const wxString& type) if (!user.IsEmpty() && !type.IsEmpty()) wxLogMessage(wxT("CCS: Link to %s from %s has been terminated via %s by %s"), m_yourCall.c_str(), m_local.c_str(), type.c_str(), user.c_str()); - WriteJSONUnlinked("ccs", "user", m_local, m_yourCall); + WriteJSONUnlinked(m_local.ToStdString(), "user"); CCCSData data(m_local, m_yourCall, CT_TERMINATE); data.setDestination(m_ccsAddress, CCS_PORT); @@ -479,7 +479,7 @@ void CCCSHandler::unlink(const wxString& callsign) return; wxLogMessage(wxT("CCS: Link to %s from %s has been terminated by command"), m_yourCall.c_str(), m_local.c_str()); - WriteJSONUnlinked("ccs", "user", m_local, m_yourCall); + WriteJSONUnlinked(m_local.ToStdString(), "user"); CCCSData data(m_local, m_yourCall, CT_TERMINATE); data.setDestination(m_ccsAddress, CCS_PORT); @@ -550,7 +550,7 @@ void CCCSHandler::clockInt(unsigned int ms) if (m_pollInactivityTimer.isRunning() && m_pollInactivityTimer.hasExpired()) { wxLogMessage(wxT("CCS: Connection has failed (poll inactivity) for %s, reconnecting"), m_callsign.c_str()); - WriteJSONRelinking("ccs", m_local, m_yourCall); + WriteJSONRelinking(m_local.ToStdString(), "ccs", m_yourCall.ToStdString()); m_announceTimer.stop(); m_pollInactivityTimer.stop(); @@ -590,7 +590,7 @@ void CCCSHandler::clockInt(unsigned int ms) if (m_inactivityTimer.isRunning() && m_inactivityTimer.hasExpired()) { wxLogMessage(wxT("CCS: Activity timeout on link for %s"), m_callsign.c_str(), m_callsign.c_str()); - WriteJSONUnlinked("ccs", "timer", m_local, m_yourCall); + WriteJSONUnlinked(m_local.ToStdString(), "timer"); CCCSData data(m_local, m_yourCall, CT_TERMINATE); data.setDestination(m_ccsAddress, CCS_PORT); diff --git a/Common/DCSHandler.cpp b/Common/DCSHandler.cpp index 2036267..483378e 100644 --- a/Common/DCSHandler.cpp +++ b/Common/DCSHandler.cpp @@ -291,7 +291,7 @@ void CDCSHandler::process(CConnectData& connect) // A new connect packet indicates the need for a new entry wxLogMessage(wxT("New incoming DCS link to %s from %s"), reflectorCallsign.c_str(), repeaterCallsign.c_str()); - WriteJSONLinking("dcs", "in", "network", repeaterCallsign, reflectorCallsign); + WriteJSONLinking(repeaterCallsign.ToStdString(), "network", "dcs", reflectorCallsign.ToStdString()); CDCSHandler* dcs = new CDCSHandler(handler, repeaterCallsign, reflectorCallsign, m_incoming, yourAddress, yourPort, DIR_INCOMING); @@ -361,7 +361,7 @@ void CDCSHandler::unlink(IReflectorCallback* handler, const wxString& callsign, if (exclude) { if (reflector->m_direction == DIR_OUTGOING && reflector->m_destination == handler && !reflector->m_reflector.IsSameAs(callsign)) { wxLogMessage(wxT("Removing outgoing DCS link %s, %s"), reflector->m_repeater.c_str(), reflector->m_reflector.c_str()); - WriteJSONUnlinked("dcs", "user", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "user"); if (reflector->m_linkState == DCS_LINKING || reflector->m_linkState == DCS_LINKED) { CConnectData connect(reflector->m_repeater, reflector->m_reflector, CT_UNLINK, reflector->m_yourAddress, reflector->m_yourPort); @@ -377,7 +377,7 @@ void CDCSHandler::unlink(IReflectorCallback* handler, const wxString& callsign, } else { if (reflector->m_destination == handler && reflector->m_reflector.IsSameAs(callsign)) { wxLogMessage(wxT("Removing DCS link %s, %s"), reflector->m_repeater.c_str(), reflector->m_reflector.c_str()); - WriteJSONUnlinked("dcs", "user", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "user"); if (reflector->m_linkState == DCS_LINKING || reflector->m_linkState == DCS_LINKED) { CConnectData connect(reflector->m_repeater, reflector->m_reflector, CT_UNLINK, reflector->m_yourAddress, reflector->m_yourPort); @@ -422,7 +422,7 @@ void CDCSHandler::unlink() if (reflector != NULL) { if (!reflector->m_repeater.IsEmpty()) { wxLogMessage(wxT("Unlinking from DCS reflector %s"), reflector->m_reflector.c_str()); - WriteJSONUnlinked("dcs", "user", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "user"); CConnectData connect(reflector->m_repeater, reflector->m_reflector, CT_UNLINK, reflector->m_yourAddress, reflector->m_yourPort); reflector->m_handler->writeConnect(connect); @@ -719,15 +719,15 @@ bool CDCSHandler::clockInt(unsigned int ms) switch (m_linkState) { case DCS_LINKING: wxLogMessage(wxT("DCS link to %s has failed to connect"), GET_DISP_REFLECTOR(this).c_str()); - WriteJSONUnlinked("dcs", "network", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "network"); break; case DCS_LINKED: wxLogMessage(wxT("DCS link to %s has failed (poll inactivity)"), GET_DISP_REFLECTOR(this).c_str()); - WriteJSONUnlinked("dcs", "timer", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "timer"); break; case DCS_UNLINKING: wxLogMessage(wxT("DCS link to %s has failed to disconnect cleanly"), GET_DISP_REFLECTOR(this).c_str()); - WriteJSONUnlinked("dcs", "network", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "network"); break; default: break; @@ -736,7 +736,7 @@ bool CDCSHandler::clockInt(unsigned int ms) if (m_direction == DIR_OUTGOING) { bool reconnect = m_destination->linkFailed(DP_DCS, GET_DISP_REFLECTOR(this), true); if (reconnect) { - WriteJSONLinking("dcs", "out", "user", m_repeater, m_reflector); + WriteJSONLinking(m_repeater.ToStdString(), "user", "dcs", m_reflector.ToStdString()); CConnectData reply(m_gatewayType, m_repeater, m_reflector, CT_LINK1, m_yourAddress, m_yourPort); m_handler->writeConnect(reply); m_linkState = DCS_LINKING; diff --git a/Common/DExtraHandler.cpp b/Common/DExtraHandler.cpp index 02b1de9..d74ac4e 100644 --- a/Common/DExtraHandler.cpp +++ b/Common/DExtraHandler.cpp @@ -298,7 +298,7 @@ void CDExtraHandler::process(const CPollData& poll) // An unmatched poll indicates the need for a new entry wxLogMessage(wxT("New incoming DExtra Dongle from %s"), reflector.c_str()); - WriteJSONLinking("dextra", "in", "network", m_callsign, reflector); + WriteJSONLinking(m_callsign.ToStdString(), "network", "dextra", reflector.ToStdString()); CDExtraHandler* handler = new CDExtraHandler(m_incoming, reflector, yourAddress, yourPort, DIR_INCOMING); @@ -374,7 +374,7 @@ void CDExtraHandler::process(CConnectData& connect) // A new connect packet indicates the need for a new entry wxLogMessage(wxT("New incoming DExtra link to %s from %s"), reflectorCallsign.c_str(), repeaterCallsign.c_str()); - WriteJSONLinking("dextra", "in", "network", repeaterCallsign, reflectorCallsign); + WriteJSONLinking(repeaterCallsign.ToStdString(), "network", "dextra", reflectorCallsign.ToStdString()); CDExtraHandler* dextra = new CDExtraHandler(handler, repeaterCallsign, reflectorCallsign, m_incoming, yourAddress, yourPort, DIR_INCOMING); @@ -442,7 +442,7 @@ void CDExtraHandler::unlink(IReflectorCallback* handler, const wxString& callsig if (exclude) { if (reflector->m_direction == DIR_OUTGOING && reflector->m_destination == handler && !reflector->m_reflector.IsSameAs(callsign)) { wxLogMessage(wxT("Removing outgoing DExtra link %s, %s"), reflector->m_repeater.c_str(), reflector->m_reflector.c_str()); - WriteJSONUnlinked("dextra", "network", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "network"); if (reflector->m_linkState == DEXTRA_LINKING || reflector->m_linkState == DEXTRA_LINKED) { CConnectData connect(reflector->m_repeater, reflector->m_yourAddress, reflector->m_yourPort); @@ -458,7 +458,7 @@ void CDExtraHandler::unlink(IReflectorCallback* handler, const wxString& callsig } else { if (reflector->m_destination == handler && reflector->m_reflector.IsSameAs(callsign)) { wxLogMessage(wxT("Removing DExtra link %s, %s"), reflector->m_repeater.c_str(), reflector->m_reflector.c_str()); - WriteJSONUnlinked("dextra", "network", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "network"); if (reflector->m_linkState == DEXTRA_LINKING || reflector->m_linkState == DEXTRA_LINKED) { CConnectData connect(reflector->m_repeater, reflector->m_yourAddress, reflector->m_yourPort); @@ -506,7 +506,7 @@ void CDExtraHandler::unlink() if (reflector != NULL) { if (!reflector->m_repeater.IsEmpty()) { wxLogMessage(wxT("Unlinking from DExtra reflector %s"), reflector->m_reflector.c_str()); - WriteJSONUnlinked("dextra", "user", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "user"); CConnectData connect(reflector->m_repeater, reflector->m_yourAddress, reflector->m_yourPort); reflector->m_handler->writeConnect(connect); @@ -815,15 +815,15 @@ bool CDExtraHandler::clockInt(unsigned int ms) switch (m_linkState) { case DEXTRA_LINKING: wxLogMessage(wxT("DExtra link to %s has failed to connect"), m_reflector.c_str()); - WriteJSONUnlinked("dextra", "network", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "network"); break; case DEXTRA_LINKED: wxLogMessage(wxT("DExtra link to %s has failed (poll inactivity)"), m_reflector.c_str()); - WriteJSONUnlinked("dextra", "timer", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "timer"); break; case DEXTRA_UNLINKING: wxLogMessage(wxT("DExtra link to %s has failed to disconnect cleanly"), m_reflector.c_str()); - WriteJSONUnlinked("dextra", "network", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "network"); break; default: break; @@ -832,7 +832,7 @@ bool CDExtraHandler::clockInt(unsigned int ms) if (m_direction == DIR_OUTGOING) { bool reconnect = m_destination->linkFailed(DP_DEXTRA, m_reflector, true); if (reconnect) { - WriteJSONLinking("dextra", "out", "user", m_repeater, m_reflector); + WriteJSONLinking(m_repeater.ToStdString(), "user", "dextra", m_reflector.ToStdString()); CConnectData reply(m_repeater, m_reflector, CT_LINK1, m_yourAddress, m_yourPort); m_handler->writeConnect(reply); m_linkState = DEXTRA_LINKING; diff --git a/Common/DPlusHandler.cpp b/Common/DPlusHandler.cpp index 2a4e207..d61c7ce 100644 --- a/Common/DPlusHandler.cpp +++ b/Common/DPlusHandler.cpp @@ -408,7 +408,7 @@ void CDPlusHandler::unlink(IReflectorCallback* handler, const wxString& callsign if (exclude) { if (reflector->m_direction == DIR_OUTGOING && reflector->m_destination == handler && !reflector->m_reflector.IsSameAs(callsign)) { wxLogMessage(wxT("Removing outgoing D-Plus link %s, %s"), reflector->m_repeater.c_str(), reflector->m_reflector.c_str()); - WriteJSONUnlinked("d-plus", "network", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "network"); if (reflector->m_linkState == DPLUS_LINKING || reflector->m_linkState == DPLUS_LINKED) { CConnectData connect(CT_UNLINK, reflector->m_yourAddress, DPLUS_PORT); @@ -427,7 +427,7 @@ void CDPlusHandler::unlink(IReflectorCallback* handler, const wxString& callsign } else { if (reflector->m_destination == handler && reflector->m_reflector.IsSameAs(callsign)) { wxLogMessage(wxT("Removing D-Plus link %s, %s"), reflector->m_repeater.c_str(), reflector->m_reflector.c_str()); - WriteJSONUnlinked("d-plus", "network", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "network"); if (reflector->m_linkState == DPLUS_LINKING || reflector->m_linkState == DPLUS_LINKED) { CConnectData connect(CT_UNLINK, reflector->m_yourAddress, DPLUS_PORT); @@ -474,7 +474,7 @@ void CDPlusHandler::unlink() if (reflector != NULL) { if (!reflector->m_reflector.IsEmpty()) { wxLogMessage(wxT("Unlinking from D-Plus reflector or dongle %s"), reflector->m_reflector.c_str()); - WriteJSONUnlinked("d-plus", "user", reflector->m_repeater, reflector->m_reflector); + WriteJSONUnlinked(reflector->m_repeater.ToStdString(), "user"); } CConnectData connect(CT_UNLINK, reflector->m_yourAddress, reflector->m_yourPort); @@ -771,15 +771,15 @@ bool CDPlusHandler::clockInt(unsigned int ms) switch (m_linkState) { case DPLUS_LINKING: wxLogMessage(wxT("D-Plus link to %s has failed to connect"), m_reflector.c_str()); - WriteJSONUnlinked("d-plus", "network", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "network"); break; case DPLUS_LINKED: wxLogMessage(wxT("D-Plus link to %s has failed (poll inactivity)"), m_reflector.c_str()); - WriteJSONUnlinked("d-plus", "timer", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "timer"); break; case DPLUS_UNLINKING: wxLogMessage(wxT("D-Plus link to %s has failed to disconnect cleanly"), m_reflector.c_str()); - WriteJSONUnlinked("d-plus", "network", m_repeater, m_reflector); + WriteJSONUnlinked(m_repeater.ToStdString(), "network"); break; default: break; @@ -789,7 +789,7 @@ bool CDPlusHandler::clockInt(unsigned int ms) if (m_direction == DIR_OUTGOING) { bool reconnect = m_destination->linkFailed(DP_DPLUS, m_reflector, true); if (reconnect) { - WriteJSONLinking("d-plus", "out", "user", m_repeater, m_reflector); + WriteJSONLinking(m_repeater.ToStdString(), "user", "d-plus", m_reflector.ToStdString()); CConnectData connect(CT_LINK1, m_yourAddress, DPLUS_PORT); m_handler->writeConnect(connect); m_linkState = DPLUS_LINKING; diff --git a/Common/IRCDDBGatewayConfig.cpp b/Common/IRCDDBGatewayConfig.cpp index e3ff595..37306fd 100644 --- a/Common/IRCDDBGatewayConfig.cpp +++ b/Common/IRCDDBGatewayConfig.cpp @@ -204,6 +204,10 @@ const wxString KEY_GPSD_PORT = wxT("gpsdPort"); const wxString KEY_MQTT_ADDRESS = wxT("mqttAddress"); const wxString KEY_MQTT_PORT = wxT("mqttPort"); const wxString KEY_MQTT_KEEPALIVE = wxT("mqttKeepalive"); +const wxString KEY_MQTT_AUTH = wxT("mqttAuth"); +const wxString KEY_MQTT_USERNAME = wxT("mqttUsername"); +const wxString KEY_MQTT_PASSWORD = wxT("mqttPassword"); +const wxString KEY_MQTT_NAME = wxT("mqttName"); const wxString KEY_WINDOW_X = wxT("windowX"); const wxString KEY_WINDOW_Y = wxT("windowY"); @@ -289,6 +293,10 @@ const wxString DEFAULT_GPSD_PORT = wxT("2947"); const wxString DEFAULT_MQTT_ADDRESS = wxT("127.0.0.1"); const unsigned short DEFAULT_MQTT_PORT = 1883U; const unsigned int DEFAULT_MQTT_KEEPALIVE = 60U; +const bool DEFAULT_MQTT_AUTH = false; +const wxString DEFAULT_MQTT_USERNAME = wxT("mmdvm"); +const wxString DEFAULT_MQTT_PASSWORD = wxT("mmdvm"); +const wxString DEFAULT_MQTT_NAME = wxT("ircddb-gateway"); const int DEFAULT_WINDOW_X = -1; const int DEFAULT_WINDOW_Y = -1; @@ -483,6 +491,10 @@ m_gpsdPort(DEFAULT_GPSD_PORT), m_mqttAddress(DEFAULT_MQTT_ADDRESS), m_mqttPort(DEFAULT_MQTT_PORT), m_mqttKeepalive(DEFAULT_MQTT_KEEPALIVE), +m_mqttAuth(DEFAULT_MQTT_AUTH), +m_mqttUsername(DEFAULT_MQTT_USERNAME), +m_mqttPassword(DEFAULT_MQTT_PASSWORD), +m_mqttName(DEFAULT_MQTT_NAME), m_x(DEFAULT_WINDOW_X), m_y(DEFAULT_WINDOW_Y) { @@ -906,6 +918,14 @@ m_y(DEFAULT_WINDOW_Y) m_config->Read(m_name + KEY_MQTT_KEEPALIVE, &temp, long(DEFAULT_MQTT_KEEPALIVE)); m_mqttKeepalive = (unsigned int)temp; + m_config->Read(m_name + KEY_MQTT_AUTH, &m_mqttAuth, DEFAULT_MQTT_AUTH); + + m_config->Read(m_name + KEY_MQTT_USERNAME, &m_mqttUsername, DEFAULT_MQTT_USERNAME); + + m_config->Read(m_name + KEY_MQTT_PASSWORD, &m_mqttPassword, DEFAULT_MQTT_PASSWORD); + + m_config->Read(m_name + KEY_MQTT_NAME, &m_mqttName, DEFAULT_MQTT_NAME); + m_config->Read(m_name + KEY_WINDOW_X, &temp, long(DEFAULT_WINDOW_X)); m_x = int(temp); @@ -1106,6 +1126,10 @@ m_gpsdPort(DEFAULT_GPSD_PORT), m_mqttAddress(DEFAULT_MQTT_ADDRESS), m_mqttPort(DEFAULT_MQTT_PORT), m_mqttKeepalive(DEFAULT_MQTT_KEEPALIVE), +m_mqttAuth(DEFAULT_MQTT_AUTH), +m_mqttUsername(DEFAULT_MQTT_USERNAME), +m_mqttPassword(DEFAULT_MQTT_PASSWORD), +m_mqttName(DEFAULT_MQTT_NAME), m_x(DEFAULT_WINDOW_X), m_y(DEFAULT_WINDOW_Y) { @@ -1135,7 +1159,7 @@ m_y(DEFAULT_WINDOW_Y) wxString str = file.GetFirstLine(); while (!file.Eof()) { - if (str.GetChar(0U) == wxT('#')) { + if (str.IsEmpty() || str.GetChar(0U) == wxT('#')) { str = file.GetNextLine(); continue; } @@ -1591,6 +1615,15 @@ m_y(DEFAULT_WINDOW_Y) } else if (key.IsSameAs(KEY_MQTT_KEEPALIVE)) { val.ToULong(&temp2); m_mqttKeepalive = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_MQTT_AUTH)) { + val.ToLong(&temp1); + m_mqttAuth = temp1 == 1L; + } else if (key.IsSameAs(KEY_MQTT_USERNAME)) { + m_mqttUsername = val; + } else if (key.IsSameAs(KEY_MQTT_PASSWORD)) { + m_mqttPassword = val; + } else if (key.IsSameAs(KEY_MQTT_NAME)) { + m_mqttName = val; } else if (key.IsSameAs(KEY_WINDOW_X)) { val.ToLong(&temp1); m_x = int(temp1); @@ -2218,11 +2251,15 @@ void CIRCDDBGatewayConfig::setGPSD(bool enabled, const wxString& address, const m_gpsdPort = port; } -void CIRCDDBGatewayConfig::getMQTT(wxString& address, unsigned short& port, unsigned int& keepalive) const +void CIRCDDBGatewayConfig::getMQTT(wxString& address, unsigned short& port, unsigned int& keepalive, bool& auth, wxString& username, wxString& password, wxString& name) const { address = m_mqttAddress; port = m_mqttPort; keepalive = m_mqttKeepalive; + auth = m_mqttAuth; + username = m_mqttUsername; + password = m_mqttPassword; + name = m_mqttName; } void CIRCDDBGatewayConfig::getPosition(int& x, int& y) const @@ -2467,6 +2504,10 @@ bool CIRCDDBGatewayConfig::write() m_config->Write(m_name + KEY_MQTT_ADDRESS, m_mqttAddress); m_config->Write(m_name + KEY_MQTT_PORT, long(m_mqttPort)); m_config->Write(m_name + KEY_MQTT_KEEPALIVE, long(m_mqttKeepalive)); + m_config->Write(m_name + KEY_MQTT_AUTH, m_mqttAuth); + m_config->Write(m_name + KEY_MQTT_USERNAME, m_mqttUsername); + m_config->Write(m_name + KEY_MQTT_PASSWORD, m_mqttPassword); + m_config->Write(m_name + KEY_MQTT_NAME, m_mqttName); m_config->Write(m_name + KEY_WINDOW_X, long(m_x)); m_config->Write(m_name + KEY_WINDOW_Y, long(m_y)); m_config->Flush(); @@ -2677,6 +2718,10 @@ bool CIRCDDBGatewayConfig::write() buffer.Printf(wxT("%s=%s"), KEY_MQTT_ADDRESS.c_str(), m_mqttAddress.c_str()); file.AddLine(buffer); buffer.Printf(wxT("%s=%u"), KEY_MQTT_PORT.c_str(), m_mqttPort); file.AddLine(buffer); buffer.Printf(wxT("%s=%u"), KEY_MQTT_KEEPALIVE.c_str(), m_mqttKeepalive); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_MQTT_AUTH.c_str(), m_mqttAuth ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_MQTT_USERNAME.c_str(), m_mqttUsername.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_MQTT_PASSWORD.c_str(), m_mqttPassword.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_MQTT_NAME.c_str(), m_mqttName.c_str()); file.AddLine(buffer); buffer.Printf(wxT("%s=%d"), KEY_WINDOW_X.c_str(), m_x); file.AddLine(buffer); buffer.Printf(wxT("%s=%d"), KEY_WINDOW_Y.c_str(), m_y); file.AddLine(buffer); diff --git a/Common/IRCDDBGatewayConfig.h b/Common/IRCDDBGatewayConfig.h index ff58809..fc8df75 100644 --- a/Common/IRCDDBGatewayConfig.h +++ b/Common/IRCDDBGatewayConfig.h @@ -118,7 +118,7 @@ class CIRCDDBGatewayConfig { void getPosition(int& x, int& y) const; void setPosition(int x, int y); - void getMQTT(wxString& address, unsigned short& port, unsigned int& keepalive) const; + void getMQTT(wxString& address, unsigned short& port, unsigned int& keepalive, bool& auth, wxString& username, wxString& password, wxString& name) const; bool write(); @@ -313,6 +313,10 @@ class CIRCDDBGatewayConfig { wxString m_mqttAddress; unsigned short m_mqttPort; unsigned int m_mqttKeepalive; + bool m_mqttAuth; + wxString m_mqttUsername; + wxString m_mqttPassword; + wxString m_mqttName; int m_x; int m_y; }; diff --git a/Common/MQTTConnection.cpp b/Common/MQTTConnection.cpp index c283fe7..9c11944 100644 --- a/Common/MQTTConnection.cpp +++ b/Common/MQTTConnection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022,2023 by Jonathan Naylor G4KLX + * Copyright (C) 2022,2023,2025 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,20 +22,29 @@ #include #include +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif -CMQTTConnection::CMQTTConnection(const wxString& host, unsigned short port, const wxString& name, const std::vector>& subs, unsigned int keepalive, MQTT_QOS qos) : + +CMQTTConnection::CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const bool authEnabled, const std::string& username, const std::string& password, const std::vector>& subs, unsigned int keepalive, MQTT_QOS qos) : m_host(host), m_port(port), m_name(name), +m_authEnabled(authEnabled), +m_username(username), +m_password(password), m_subs(subs), m_keepalive(keepalive), m_qos(qos), m_mosq(NULL), m_connected(false) { - assert(!host.IsEmpty()); + assert(!host.empty()); assert(port > 0U); - assert(!name.IsEmpty()); + assert(!name.empty()); assert(keepalive >= 5U); ::mosquitto_lib_init(); @@ -48,12 +57,24 @@ CMQTTConnection::~CMQTTConnection() bool CMQTTConnection::open() { - m_mosq = ::mosquitto_new(m_name.c_str(), true, this); - if (m_mosq == NULL){ + char name[50U]; +#if defined(_WIN32) || defined(_WIN64) + ::sprintf(name, "ircDDBGateway.%u", (unsigned)::_getpid()); +#else + ::sprintf(name, "ircDDBGateway.%u", (unsigned)::getpid()); +#endif + + ::fprintf(stdout, "ircDDBGateway (%s) connecting to MQTT as %s\n", m_name.c_str(), name); + + m_mosq = ::mosquitto_new(name, true, this); + if (m_mosq == NULL) { ::fprintf(stderr, "MQTT Error newing: Out of memory.\n"); return false; } + if (m_authEnabled) + ::mosquitto_username_pw_set(m_mosq, m_username.c_str(), m_password.c_str()); + ::mosquitto_connect_callback_set(m_mosq, onConnect); ::mosquitto_subscribe_callback_set(m_mosq, onSubscribe); ::mosquitto_message_callback_set(m_mosq, onMessage); @@ -84,14 +105,14 @@ bool CMQTTConnection::publish(const char* topic, const char* text) assert(topic != NULL); assert(text != NULL); - return publish(topic, (unsigned char*)text, ::strlen(text)); + return publish(topic, (unsigned char*)text, (unsigned int)::strlen(text)); } bool CMQTTConnection::publish(const char* topic, const std::string& text) { assert(topic != NULL); - return publish(topic, (unsigned char*)text.c_str(), text.size()); + return publish(topic, (unsigned char*)text.c_str(), (unsigned int)text.size()); } bool CMQTTConnection::publish(const char* topic, const unsigned char* data, unsigned int len) @@ -104,7 +125,7 @@ bool CMQTTConnection::publish(const char* topic, const unsigned char* data, unsi if (::strchr(topic, '/') == NULL) { char topicEx[100U]; - ::sprintf(topicEx, "%s/%s", (char*)m_name.char_str(), topic); + ::sprintf(topicEx, "%s/%s", m_name.c_str(), topic); int rc = ::mosquitto_publish(m_mosq, NULL, topicEx, len, data, static_cast(m_qos), false); if (rc != MOSQ_ERR_SUCCESS) { @@ -126,6 +147,7 @@ void CMQTTConnection::close() { if (m_mosq != NULL) { ::mosquitto_disconnect(m_mosq); + ::mosquitto_loop_stop(m_mosq, true); ::mosquitto_destroy(m_mosq); m_mosq = NULL; } @@ -145,12 +167,12 @@ void CMQTTConnection::onConnect(mosquitto* mosq, void* obj, int rc) CMQTTConnection* p = static_cast(obj); p->m_connected = true; - for (std::vector>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) { - wxString topic = (*it).first; + for (std::vector>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) { + std::string topic = (*it).first; if (topic.find_first_of('/') == std::string::npos) { char topicEx[100U]; - ::sprintf(topicEx, "%s/%s", (char*)p->m_name.char_str(), (char*)topic.char_str()); + ::sprintf(topicEx, "%s/%s", p->m_name.c_str(), topic.c_str()); rc = ::mosquitto_subscribe(mosq, NULL, topicEx, static_cast(p->m_qos)); if (rc != MOSQ_ERR_SUCCESS) { @@ -160,7 +182,7 @@ void CMQTTConnection::onConnect(mosquitto* mosq, void* obj, int rc) } else { rc = ::mosquitto_subscribe(mosq, NULL, topic.c_str(), static_cast(p->m_qos)); if (rc != MOSQ_ERR_SUCCESS) { - ::fprintf(stderr, "MQTT: error subscribing to %s - %s\n", (char*)topic.char_str(), ::mosquitto_strerror(rc)); + ::fprintf(stderr, "MQTT: error subscribing to %s - %s\n", topic.c_str(), ::mosquitto_strerror(rc)); ::mosquitto_disconnect(mosq); } } @@ -185,11 +207,11 @@ void CMQTTConnection::onMessage(mosquitto* mosq, void* obj, const mosquitto_mess CMQTTConnection* p = static_cast(obj); - for (std::vector>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) { - wxString topic = (*it).first; + for (std::vector>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) { + std::string topic = (*it).first; char topicEx[100U]; - ::sprintf(topicEx, "%s/%s", (char*)p->m_name.char_str(), (char*)topic.char_str()); + ::sprintf(topicEx, "%s/%s", p->m_name.c_str(), topic.c_str()); if (::strcmp(topicEx, message->topic) == 0) { (*it).second((unsigned char*)message->payload, message->payloadlen); @@ -208,4 +230,3 @@ void CMQTTConnection::onDisconnect(mosquitto* mosq, void* obj, int rc) CMQTTConnection* p = static_cast(obj); p->m_connected = false; } - diff --git a/Common/MQTTConnection.h b/Common/MQTTConnection.h index 8faf69e..624ec3a 100644 --- a/Common/MQTTConnection.h +++ b/Common/MQTTConnection.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022,2023 by Jonathan Naylor G4KLX + * Copyright (C) 2022,2023,2025 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,8 +19,6 @@ #if !defined(MQTTPUBLISHER_H) #define MQTTPUBLISHER_H -#include - #include #include @@ -34,7 +32,7 @@ enum MQTT_QOS { class CMQTTConnection { public: - CMQTTConnection(const wxString& host, unsigned short port, const wxString& name, const std::vector>& subs, unsigned int keepalive, MQTT_QOS qos = MQTT_QOS_EXACTLY_ONCE); + CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const bool authEnabled, const std::string& username, const std::string& password, const std::vector>& subs, unsigned int keepalive, MQTT_QOS qos = MQTT_QOS_EXACTLY_ONCE); ~CMQTTConnection(); bool open(); @@ -46,10 +44,13 @@ class CMQTTConnection { void close(); private: - wxString m_host; + std::string m_host; unsigned short m_port; - wxString m_name; - std::vector> m_subs; + std::string m_name; + bool m_authEnabled; + std::string m_username; + std::string m_password; + std::vector> m_subs; unsigned int m_keepalive; MQTT_QOS m_qos; mosquitto* m_mosq; @@ -62,4 +63,3 @@ class CMQTTConnection { }; #endif - diff --git a/Common/MQTTLog.cpp b/Common/MQTTLog.cpp index a90d379..0f864e0 100644 --- a/Common/MQTTLog.cpp +++ b/Common/MQTTLog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2020,2022,2023 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2020,2022,2023,2025 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,45 +52,62 @@ void WriteJSON(const std::string& topLevel, nlohmann::json& json) } } -void WriteJSONLinking(const std::string& protocol, const std::string& direction, const std::string& reason, const wxString& local, const wxString& remote) +void WriteJSONStatus(const std::string& status) { nlohmann::json json; json["timestamp"] = CUtils::createTimestamp(); + json["message"] = status; + + WriteJSON("status", json); +} + +void WriteJSONLinking(const std::string& repeater, const std::string& reason, const std::string& protocol, const std::string& reflector) +{ + nlohmann::json json; + + json["timestamp"] = CUtils::createTimestamp(); + json["repeater"] = repeater; json["action"] = "linking"; json["reason"] = reason; - json["remote"] = remote.char_str(); - json["local"] = local.char_str(); + json["reflector"] = reflector; json["protocol"] = protocol; - json["direction"] = direction; WriteJSON("link", json); } -void WriteJSONUnlinked(const std::string& protocol, const std::string& reason, const wxString& local, const wxString& remote) +void WriteJSONUnlinked(const std::string& repeater, const std::string& reason) { nlohmann::json json; json["timestamp"] = CUtils::createTimestamp(); + json["repeater"] = repeater; json["action"] = "unlinked"; json["reason"] = reason; - json["remote"] = remote.char_str(); - json["local"] = local.char_str(); - json["protocol"] = protocol; WriteJSON("link", json); } -void WriteJSONRelinking(const std::string& protocol, const wxString& local, const wxString& remote) +void WriteJSONFailed(const std::string& repeater) { nlohmann::json json; json["timestamp"] = CUtils::createTimestamp(); + json["repeater"] = repeater; + json["action"] = "failed"; + + WriteJSON("link", json); +} + +void WriteJSONRelinking(const std::string& repeater, const std::string& protocol, const std::string& reflector) +{ + nlohmann::json json; + + json["timestamp"] = CUtils::createTimestamp(); + json["repeater"] = repeater; json["action"] = "relinking"; - json["remote"] = remote.char_str(); - json["local"] = local.char_str(); + json["reflector"] = reflector; json["protocol"] = protocol; WriteJSON("link", json); } - diff --git a/Common/MQTTLog.h b/Common/MQTTLog.h index 1783bae..bcbbd47 100644 --- a/Common/MQTTLog.h +++ b/Common/MQTTLog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2020,2022,2023 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2020,2022,2023,2025 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +21,6 @@ #include "MQTTConnection.h" -#include - #include #include @@ -32,8 +30,10 @@ extern void MQTTLogFinalise(); extern void WriteJSON(const std::string& topLevel, nlohmann::json& json); -extern void WriteJSONLinking(const std::string& protocol, const std::string& direction, const std::string& reason, const wxString& local, const wxString& remote); -extern void WriteJSONUnlinked(const std::string& protocol, const std::string& reason, const wxString& local, const wxString& remote); -extern void WriteJSONRelinking(const std::string& protocol, const wxString& local, const wxString& remote); +extern void WriteJSONStatus(const std::string& status); +extern void WriteJSONLinking(const std::string& repeater, const std::string& reason, const std::string& protocol, const std::string& reflector); +extern void WriteJSONUnlinked(const std::string& repeater, const std::string& reason); +extern void WriteJSONFailed(const std::string& repeater); +extern void WriteJSONRelinking(const std::string& repeater, const std::string& protocol, const std::string& reflector); #endif diff --git a/MQTT.md b/MQTT.md new file mode 100644 index 0000000..1a384d7 --- /dev/null +++ b/MQTT.md @@ -0,0 +1,193 @@ +# MQTT Support for ircDDBGateway + +ircDDBGateway publishes log messages and link status events to an MQTT +broker, providing live telemetry for dashboards, home-automation systems, +or any MQTT client. + +MQTT support is always compiled in. The `libmosquitto-dev` package is a +build dependency. To disable MQTT at runtime, leave `mqttAddress` empty +in the configuration file — no messages will be published and no +connection to the broker will be attempted. + +## Building + +Install `libmosquitto-dev`: + +``` +sudo apt-get install libmosquitto-dev +``` + +Then build as normal: + +``` +make +sudo make install +``` + +## Configuration + +Add the following keys to your `ircddbgateway` configuration file +(the same flat key=value format used by all other settings): + +``` +mqttAddress=127.0.0.1 +mqttPort=1883 +mqttAuth=0 +mqttUsername=mmdvm +mqttPassword=mmdvm +mqttKeepalive=60 +mqttName=ircddb-gateway +``` + +| Key | Default | Description | +|-----------------|-------------------|----------------------------------------------------| +| `mqttAddress` | `127.0.0.1` | MQTT broker hostname or IP address | +| `mqttPort` | `1883` | MQTT broker port | +| `mqttAuth` | `0` | Enable authentication (`0` = off, `1` = on) | +| `mqttUsername` | `mmdvm` | Username when `mqttAuth=1` | +| `mqttPassword` | `mmdvm` | Password when `mqttAuth=1` | +| `mqttKeepalive` | `60` | Keepalive interval in seconds | +| `mqttName` | `ircddb-gateway` | Client name; also used as the MQTT topic prefix | + +If `mqttAddress` is left empty, MQTT is disabled at runtime even when +compiled in. + +## MQTT Topics + +All topics are automatically prefixed with the value of `mqttName`. +For example, with the default name `ircddb-gateway`: + +### `ircddb-gateway/log` + +Timestamped log messages. The format matches the log file output: + +``` +M: 2025-03-08 14:22:01: Starting ircDDBGateway +M: 2025-03-08 14:22:01: Callsign set to "GB7XX G" +I: 2025-03-08 14:22:01: MQTT connected to 127.0.0.1:1883 as ircddb-gateway +M: 2025-03-08 14:22:01: Starting the ircDDB Gateway thread +``` + +### `ircddb-gateway/json` + +Event-driven JSON messages published when link status changes. These use +the same JSON structure as [DStarGateway](https://github.com/F4FXL/DStarGateway), +making it possible to use the same dashboard code for both gateways. + +All JSON messages are wrapped in a top-level object with one key. The +value is always an object containing a `timestamp` field. + +#### Status Events + +Published at gateway startup and shutdown: + +```json +{"status":{"timestamp":"2025-03-08T14:22:01Z","message":"ircDDBGateway is starting"}} +``` + +```json +{"status":{"timestamp":"2025-03-08T18:00:00Z","message":"ircDDBGateway is stopping"}} +``` + +#### Linking Events + +Published when a repeater module links to a reflector: + +```json +{"link":{"timestamp":"2025-03-08T14:23:15Z","repeater":"GB7XX B","action":"linking","reason":"network","reflector":"REF001 C","protocol":"dextra"}} +``` + +**Fields:** + +| Field | Description | +|--------------|----------------------------------------------------------| +| `timestamp` | ISO 8601 timestamp of the event | +| `repeater` | Repeater callsign with module letter (8 chars) | +| `action` | `linking` — connection being established | +| `reason` | `network`, `user`, or `timer` — what triggered the link | +| `reflector` | Reflector callsign with module letter (8 chars) | +| `protocol` | `dextra`, `d-plus`, `dcs`, or `ccs` | + +#### Relinking Events + +Published when a repeater re-establishes a link after a timeout: + +```json +{"link":{"timestamp":"2025-03-08T14:30:00Z","repeater":"GB7XX B","action":"relinking","reflector":"REF001 C","protocol":"ccs"}} +``` + +#### Unlinked Events + +Published when a repeater module unlinks from a reflector: + +```json +{"link":{"timestamp":"2025-03-08T14:25:00Z","repeater":"GB7XX B","action":"unlinked","reason":"user"}} +``` + +**Reason values:** + +| Reason | Description | +|-----------|--------------------------------------------------| +| `user` | User-initiated unlink (DTMF or UR call command) | +| `network` | Network disconnection or protocol error | +| `timer` | Inactivity or reconnect timer expired | + +#### Failed Events + +Published when a link attempt fails: + +```json +{"link":{"timestamp":"2025-03-08T14:24:00Z","repeater":"GB7XX B","action":"failed"}} +``` + +### `ircddb-gateway/aprs-gateway/aprs` + +APRS position data from the DPRS-to-APRS gateway, published as raw APRS +ASCII strings. Only present when APRS gateway functionality is enabled. + +## Subscribing to MQTT Output + +Use any MQTT client to subscribe. For example, with `mosquitto_sub`: + +```bash +# Follow all ircDDBGateway topics: +mosquitto_sub -h 127.0.0.1 -t "ircddb-gateway/#" + +# Log messages only: +mosquitto_sub -h 127.0.0.1 -t "ircddb-gateway/log" + +# Link status events only: +mosquitto_sub -h 127.0.0.1 -t "ircddb-gateway/json" + +# Pretty-print link events with jq: +mosquitto_sub -h 127.0.0.1 -t "ircddb-gateway/json" | jq . +``` + +## Comparison with DStarGateway and MMDVMHost + +This implementation follows the same conventions as the wider MMDVM +ecosystem: + +- Same `CMQTTConnection` class wrapping `libmosquitto` +- Same topic-prefix convention (`{name}/topic`) +- Same PID-based client ID scheme (avoids the `time_t` truncation + issue on 32-bit ARM) +- Same configuration key names (`mqttAddress`, `mqttPort`, etc.) +- Same QoS default (EXACTLY_ONCE / QoS 2) +- JSON link events use the same field names as DStarGateway + (`repeater`, `reflector`, `protocol`, `reason`, `action`) + +ircDDBGateway is a **publish-only** MQTT client — it does not subscribe +to any MQTT topics or accept remote commands via MQTT. + +## Troubleshooting + +**"Unable to start MQTT connection" in logs:** +- Check that Mosquitto (or another MQTT broker) is running on the + configured host and port +- Verify credentials if `mqttAuth=1` +- Check firewall rules if connecting to a remote broker + +**No messages appearing:** +- Check that `mqttAddress` is not empty in the config file +- Verify the broker is accessible: `mosquitto_pub -h 127.0.0.1 -t test -m hello` diff --git a/README.md b/README.md index c510b54..24e099a 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,125 @@ -This is the ircDDB Gateway. It allows a D-Star Repeater to interface into callsign routing via ircDDB and all of the different reflector types. It includes many facilities, including: - -* Supports Icom stacks. -* Supports homebrew repeaters. -* Icom DD mode under Linux with Internet access. -* Callsign routing via ircDDB. -* D-Plus REF reflectors. -* DExtra XRF reflectors. -* DCS reflectors. -* XLX reflectors. -* CCS7 routing. -* D-RATS data transfers. -* Gateway DPRS data to APRS-IS. -* Full multi lingual text and voice announcements. -* DTMF or UR call control. -* Remote control interface. -* StarNet server. -* Ability to set policies for reflector usage. - -There are many external programs that allow for inserting voice or text messages, as well as remote control operation. - -They all build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2022 on x86 and x64. +# ircDDBGateway -This software is licenced under the GPL v2. +> **Note:** For new installations, consider using [DStarGateway](https://github.com/F4FXL/DStarGateway) instead. DStarGateway is an actively developed, wxWidgets-free rewrite of ircDDBGateway with additional features such as NAT traversal, RS-MS1A message forwarding, and automatic host file downloads. ircDDBGateway remains fully functional and is still in use at many sites, but DStarGateway is the recommended path forward for new deployments. + +ircDDBGateway allows a D-Star repeater to interface into callsign routing via ircDDB and all of the different reflector types. It works with both [DStarRepeater](https://github.com/g4klx/DStarRepeater) (homebrew hardware) and Icom repeater stacks (ID-RP2C). + +## Features + +- Callsign routing via ircDDB (up to 4 networks simultaneously) +- D-Plus REF reflectors +- DExtra XRF reflectors +- DCS reflectors +- XLX reflectors +- CCS7 routing +- Supports Icom repeater stacks and homebrew repeaters +- Up to 4 repeater modules per gateway +- Icom DD mode under Linux with Internet access +- D-RATS data transfers +- DPRS to APRS-IS gateway +- Full multi-lingual text and voice announcements +- DTMF or UR call control +- Remote control interface +- STARnet digital voice server (up to 5 groups) +- Reflector usage policies +- MQTT telemetry publishing (log messages, link status events) + +## Building -# Build and installing -## Regular build -```shell +Builds on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2022 (x86 and x64). + +### Standard Build + +``` +make +sudo make install +``` + +### With GUI Programs + +``` +make -f MakefileGUI +``` + +### MQTT Dependency + +MQTT telemetry is always compiled in. Install `libmosquitto-dev` before building: + +``` +sudo apt-get install libmosquitto-dev make -make -f MakefileGUI #only required if you want to build the GUI programs sudo make install ``` -## Drop-in replacement for dl5di OpenDV packages -This will conpile the program to be used as a drop in replacement for the no longer maintained DL5DI OpenDV packages. Systemd files to run ircddbgatewayd as daemon will also be installed. -```shell + +To disable MQTT at runtime, leave `mqttAddress` empty in the config file. See [MQTT.md](MQTT.md) for full configuration details, topic structure, and JSON format. + +### Drop-in Replacement for dl5di OpenDV Packages + +This compiles the program to be used as a drop-in replacement for the no longer maintained DL5DI OpenDV packages. Systemd unit files for running `ircddbgatewayd` as a daemon are also installed. + +``` export TARGET=opendv make -make -f MakefileGUI #only required if you want to build the GUI programs sudo make install ``` -Note that you may need to use `sudo -E make install` to pass the environment variables, or just `sudo TARGET=opendv make install` to correctly create the opendv user/directories, depending on your system configuration. -Now you should edit the configuration in the file /etc/ircddbgateway to match your needs. +You may need `sudo -E make install` or `sudo TARGET=opendv make install` to pass the environment variable, depending on your system configuration. -When building in TARGET=opendv mode, the necessary systemd unit files will be installed. To ensure ircddbgatewayd starts at system boot, and then immediately start it, use: -```shell +Enable and start the service: + +``` sudo systemctl enable ircddbgatewayd.service -sudo service ircddbgatewayd start +sudo systemctl start ircddbgatewayd.service ``` -If you wish to remove the manually installed files you may use `sudo make uninstall`. -Note that this will remove the systemd unit, but will not first stop or disable the service. To do so, first use: -```shell -sudo systemctl disable --now ircddbgateway.service +To uninstall: + +``` +sudo systemctl disable --now ircddbgatewayd.service +sudo make uninstall ``` -It will also not remove your /etc/ircddbgateway configuration file. -# Setup +The uninstall will not remove your `/etc/ircddbgateway` configuration file. -Only the ircddbgatewayd service is required for basic usage, all other compiled applications are optional. +## Configuration -## ICOM repeater configuration -If using an ICOM ID-RP2C you will need to use the ICOM utility to configure the repeater. By default the ID-RP2C uses the IP address 172.16.0.1, and expects the device controlling it (either your computer, or the computer running ircddbgatewayd) to be using the IP address 172.16.0.20, and uses the default password "PASSWORD". It may not be advisable to change this password as performing a hardware reset is not a documented feature. Ideally the ID-RP2C is connected directly to a secondary ethernet port on the device running ircddbgatewayd and configured with a static IP address, other configurations may prove problematic. +Edit `/etc/ircddbgateway` (or `/etc/ircddbgateway_` when using the `-name` option) to match your setup. An example configuration is provided in [linux/ircddbgateway.example](linux/ircddbgateway.example). -When configuring the repeater using the ICOM utility you will need to change the port in the "Communication Settings" section to use the same port as is used in the "Gateway" section (20000 by default), and then go into the Options/Network Setup menu of the ICOM app to change the port to match what you put into the "Communication Settings" section. +See [CONFIGURATION.md](CONFIGURATION.md) for a complete reference of all settings, defaults, and allowed values. -Once both of those fields are set to the same port number, ensure that the "Local RPT" section is set correctly for whichever physical plug your ID-RP2D is plugged into on the back of the ID-RP2C. Whichever number you have plugged in to should be set to "Voice", and have the correct band entered in the text box. ("B" for 70cm, for example.) +## ICOM Repeater Setup + +If using an ICOM ID-RP2C, configure the repeater using the ICOM utility. By default the ID-RP2C uses IP address `172.16.0.1` and expects the controlling device at `172.16.0.20` with the default password `PASSWORD`. Ideally the ID-RP2C is connected directly to a secondary ethernet port on the device running `ircddbgatewayd`. + +In the ICOM utility, set the port in "Communication Settings" to match the "Gateway" section port (20000 by default), then match it in Options > Network Setup. + +Example configuration for a 70cm repeater on Repeater 1: -In your /etc/ircddbgateway config file the following options are important: ``` +gatewayType=0 icomAddress=172.16.0.20 icomPort=20000 -repeaterCall1=REPEATERCALLSIGN +repeaterCall1=GB7XX repeaterBand1=B repeaterType1=1 -repeaterAddress=172.16.0.1 +repeaterAddress1=172.16.0.1 repeaterPort1=20000 ``` -Note that this assumes you're configuring "Repeater 1". -The repeaterAdress is the address of the ID-RP2C controller, and "icomAddress" is the address of the device running ircddbgatewayd. The "Type" being 1 indicates an ICOM repeater. The Band should be set to whichever band you entered in the "Local RPT" section of the ICOM configuration software, and the callsign should match the call used in in the ICOM software. + +## Companion Programs + +Only `ircddbgatewayd` is required for basic operation. The following companion programs are also included: + +| Program | Description | +|---------|-------------| +| `ircddbgatewayconfig` | GUI configuration editor | +| `texttransmit` | Send a text message via the gateway | +| `voicetransmit` | Send a voice announcement via the gateway | +| `timercontrol` | Scheduled reflector linking control | +| `timeserver` | Transmit time announcements | +| `remotecontrol` | Remote control client | +| `aprstransmit` | Send APRS data via the gateway | + +## Licence + +This software is licenced under the GPL v2. diff --git a/TimeServer/TimeServerConfig.cpp b/TimeServer/TimeServerConfig.cpp index 17a71a1..9c1885d 100644 --- a/TimeServer/TimeServerConfig.cpp +++ b/TimeServer/TimeServerConfig.cpp @@ -147,7 +147,7 @@ m_y(DEFAULT_WINDOW_Y) wxString str = file.GetFirstLine(); while (!file.Eof()) { - if (str.GetChar(0U) == wxT('#')) { + if (str.IsEmpty() || str.GetChar(0U) == wxT('#')) { str = file.GetNextLine(); continue; } diff --git a/TimerControl/TimerControlConfig.cpp b/TimerControl/TimerControlConfig.cpp index 47ad5de..eb45695 100644 --- a/TimerControl/TimerControlConfig.cpp +++ b/TimerControl/TimerControlConfig.cpp @@ -109,7 +109,7 @@ m_y(DEFAULT_WINDOW_Y) wxString str = file.GetFirstLine(); while (!file.Eof()) { - if (str.GetChar(0U) == wxT('#')) { + if (str.IsEmpty() || str.GetChar(0U) == wxT('#')) { str = file.GetNextLine(); continue; } diff --git a/ircDDBGateway/IRCDDBGatewayApp.cpp b/ircDDBGateway/IRCDDBGatewayApp.cpp index 78243c0..be7b892 100644 --- a/ircDDBGateway/IRCDDBGatewayApp.cpp +++ b/ircDDBGateway/IRCDDBGatewayApp.cpp @@ -24,6 +24,7 @@ #include "IRCDDBGatewayDefs.h" #include "IRCDDBGatewayApp.h" #include "MQTTConnection.h" +#include "MQTTLog.h" #include "CallsignList.h" #include "APRSWriter.h" #include "Version.h" @@ -90,12 +91,14 @@ bool CIRCDDBGatewayApp::OnInit() m_config = new CIRCDDBGatewayConfig(m_confDir, CONFIG_FILE_NAME, m_name); #endif - std::string mqttAddress; + wxString mqttAddress; unsigned short mqttPort; unsigned int mqttKeepalive; - m_config->getMQTT(mqttAddress, mqttPort, mqttKeepalive); - std::vector> subscriptions; - m_mqtt = new CMQTTConnection(mqttAddress, mqttPort, "ircddb-gateway", subscriptions, mqttKeepalive);m_conf.getMQTTKeepalive()); + bool mqttAuth; + wxString mqttUsername, mqttPassword, mqttName; + m_config->getMQTT(mqttAddress, mqttPort, mqttKeepalive, mqttAuth, mqttUsername, mqttPassword, mqttName); + std::vector> subscriptions; + m_mqtt = new CMQTTConnection(std::string(mqttAddress.mb_str()), mqttPort, std::string(mqttName.mb_str()), mqttAuth, std::string(mqttUsername.mb_str()), std::string(mqttPassword.mb_str()), subscriptions, mqttKeepalive); bool ret = m_mqtt->open(); if (!ret) return false; @@ -186,6 +189,9 @@ int CIRCDDBGatewayApp::OnExit() wxLogInfo(APPLICATION_NAME + wxT(" is exiting")); m_thread->kill(); + + MQTTLogFinalise(); + wxWindow * topWin = wxGetApp().GetTopWindow(); if (topWin != NULL) topWin->Close(); diff --git a/ircDDBGateway/IRCDDBGatewayAppD.cpp b/ircDDBGateway/IRCDDBGatewayAppD.cpp index e990ad0..67538ae 100644 --- a/ircDDBGateway/IRCDDBGatewayAppD.cpp +++ b/ircDDBGateway/IRCDDBGatewayAppD.cpp @@ -23,6 +23,7 @@ #include "IRCDDBGatewayAppD.h" #include "IRCDDBGatewayDefs.h" #include "MQTTConnection.h" +#include "MQTTLog.h" #include "CallsignList.h" #include "APRSWriter.h" #include "Version.h" @@ -194,9 +195,11 @@ bool CIRCDDBGatewayAppD::init() wxString mqttAddress; unsigned short mqttPort; unsigned int mqttKeepalive; - config.getMQTT(mqttAddress, mqttPort, mqttKeepalive); - std::vector> subscriptions; - m_mqtt = new CMQTTConnection(mqttAddress, mqttPort, "ircddb-gateway", subscriptions, mqttKeepalive); + bool mqttAuth; + wxString mqttUsername, mqttPassword, mqttName; + config.getMQTT(mqttAddress, mqttPort, mqttKeepalive, mqttAuth, mqttUsername, mqttPassword, mqttName); + std::vector> subscriptions; + m_mqtt = new CMQTTConnection(std::string(mqttAddress.mb_str()), mqttPort, std::string(mqttName.mb_str()), mqttAuth, std::string(mqttUsername.mb_str()), std::string(mqttPassword.mb_str()), subscriptions, mqttKeepalive); bool ret = m_mqtt->open(); if (!ret) return false; @@ -264,6 +267,8 @@ void CIRCDDBGatewayAppD::run() delete m_checker; wxLogInfo(APPLICATION_NAME + wxT(" is exiting")); + + MQTTLogFinalise(); } bool CIRCDDBGatewayAppD::createThread() diff --git a/ircDDBGateway/IRCDDBGatewayThread.cpp b/ircDDBGateway/IRCDDBGatewayThread.cpp index b715924..43988e4 100644 --- a/ircDDBGateway/IRCDDBGatewayThread.cpp +++ b/ircDDBGateway/IRCDDBGatewayThread.cpp @@ -1430,11 +1430,6 @@ void CIRCDDBGatewayThread::readStatusFile(const wxString& filename, unsigned int void CIRCDDBGatewayThread::writeJSONStatus(const std::string& status) { - nlohmann::json json; - - json["timestamp"] = CUtils::createTimestamp(); - json["message"] = status; - - WriteJSON("status", json); + WriteJSONStatus(status); } diff --git a/linux/ircddbgateway.example b/linux/ircddbgateway.example new file mode 100644 index 0000000..9fd28ab --- /dev/null +++ b/linux/ircddbgateway.example @@ -0,0 +1,232 @@ +# ircDDBGateway example configuration +# See CONFIGURATION.md for a full reference of all settings. +# +# Gateway +gatewayType=0 +gatewayCallsign= +gatewayAddress= +icomAddress=172.16.0.20 +icomPort=20000 +hbAddress=127.0.0.1 +hbPort=20010 +latitude=0.000000 +longitude=0.000000 +description1= +description2= +url= +# +# Repeater 1 +repeaterCall1= +repeaterBand1= +repeaterType1=0 +repeaterAddress1=127.0.0.1 +repeaterPort1=20011 +reflector1= +atStartup1=0 +reconnect1=0 +frequency1=0.00000 +offset1=0.0000 +rangeKms1=0.000 +latitude1=0.000000 +longitude1=0.000000 +agl1=0.000 +description1_1= +description1_2= +url1= +band1_1=0 +band1_2=0 +band1_3=0 +# +# Repeater 2 +repeaterCall2= +repeaterBand2= +repeaterType2=0 +repeaterAddress2=127.0.0.1 +repeaterPort2=20012 +reflector2= +atStartup2=0 +reconnect2=0 +frequency2=0.00000 +offset2=0.0000 +rangeKms2=0.000 +latitude2=0.000000 +longitude2=0.000000 +agl2=0.000 +description2_1= +description2_2= +url2= +band2_1=0 +band2_2=0 +band2_3=0 +# +# Repeater 3 +repeaterCall3= +repeaterBand3= +repeaterType3=0 +repeaterAddress3=127.0.0.1 +repeaterPort3=20013 +reflector3= +atStartup3=0 +reconnect3=0 +frequency3=0.00000 +offset3=0.0000 +rangeKms3=0.000 +latitude3=0.000000 +longitude3=0.000000 +agl3=0.000 +description3_1= +description3_2= +url3= +band3_1=0 +band3_2=0 +band3_3=0 +# +# Repeater 4 +repeaterCall4= +repeaterBand4= +repeaterType4=0 +repeaterAddress4=127.0.0.1 +repeaterPort4=20014 +reflector4= +atStartup4=0 +reconnect4=0 +frequency4=0.00000 +offset4=0.0000 +rangeKms4=0.000 +latitude4=0.000000 +longitude4=0.000000 +agl4=0.000 +description4_1= +description4_2= +url4= +band4_1=0 +band4_2=0 +band4_3=0 +# +# ircDDB Network 1 +ircddbEnabled=1 +ircddbHostname=group1-irc.ircddb.net +ircddbUsername= +ircddbPassword= +# +# ircDDB Network 2 +ircddbEnabled2=1 +ircddbHostname2=rr.openquad.net +ircddbUsername2= +ircddbPassword2= +# +# ircDDB Network 3 +ircddbEnabled3=0 +ircddbHostname3= +ircddbUsername3= +ircddbPassword3= +# +# ircDDB Network 4 +ircddbEnabled4=0 +ircddbHostname4= +ircddbUsername4= +ircddbPassword4= +# +# Protocol and Service Settings +aprsEnabled=0 +dextraEnabled=1 +dextraMaxDongles=5 +dplusEnabled=0 +dplusMaxDongles=5 +dplusLogin= +dcsEnabled=1 +ccsEnabled=1 +ccsHost=CCS704 +xlxEnabled=1 +xlxHostsFileUrl=http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster +# +# STARnet Group 1 +starNetBand1= +starNetCallsign1= +starNetLogoff1= +starNetInfo1= +starNetPermanent1= +starNetUserTimeout1=300 +starNetGroupTimeout1=300 +starNetCallsignSwitch1=0 +starNetTXMsgSwitch1=1 +starNetReflector1= +# +# STARnet Group 2 +starNetBand2= +starNetCallsign2= +starNetLogoff2= +starNetInfo2= +starNetPermanent2= +starNetUserTimeout2=300 +starNetGroupTimeout2=300 +starNetCallsignSwitch2=0 +starNetTXMsgSwitch2=1 +starNetReflector2= +# +# STARnet Group 3 +starNetBand3= +starNetCallsign3= +starNetLogoff3= +starNetInfo3= +starNetPermanent3= +starNetUserTimeout3=300 +starNetGroupTimeout3=300 +starNetCallsignSwitch3=0 +starNetTXMsgSwitch3=1 +starNetReflector3= +# +# STARnet Group 4 +starNetBand4= +starNetCallsign4= +starNetLogoff4= +starNetInfo4= +starNetPermanent4= +starNetUserTimeout4=300 +starNetGroupTimeout4=300 +starNetCallsignSwitch4=0 +starNetTXMsgSwitch4=1 +starNetReflector4= +# +# STARnet Group 5 +starNetBand5= +starNetCallsign5= +starNetLogoff5= +starNetInfo5= +starNetPermanent5= +starNetUserTimeout5=300 +starNetGroupTimeout5=300 +starNetCallsignSwitch5=0 +starNetTXMsgSwitch5=1 +starNetReflector5= +# +# Remote Control +remoteEnabled=0 +remotePassword= +remotePort=0 +# +# Miscellaneous +language=0 +infoEnabled=1 +echoEnabled=1 +logEnabled=0 +dratsEnabled=0 +dtmfEnabled=1 +# +# GPSD +gpsdEnabled=0 +gpsdAddress=127.0.0.1 +gpsdPort=2947 +# +# MQTT (leave mqttAddress empty to disable) +mqttAddress=127.0.0.1 +mqttPort=1883 +mqttKeepalive=60 +mqttAuth=0 +mqttUsername=mmdvm +mqttPassword=mmdvm +mqttName=ircddb-gateway +# +# Window Position (GUI only) +windowX=-1 +windowY=-1 diff --git a/linux/timercontrol.example b/linux/timercontrol.example new file mode 100644 index 0000000..8d2c8b8 --- /dev/null +++ b/linux/timercontrol.example @@ -0,0 +1,14 @@ +# TimerControl example configuration +# See CONFIGURATION.md for a full reference of all settings. +# +# Gateway connection +address= +port=0 +password= +# +# Delay +delay=0 +# +# Window Position (GUI only) +windowX=-1 +windowY=-1 diff --git a/linux/timeserver.example b/linux/timeserver.example new file mode 100644 index 0000000..74c859b --- /dev/null +++ b/linux/timeserver.example @@ -0,0 +1,20 @@ +# TimeServer example configuration +# See CONFIGURATION.md for a full reference of all settings. +# +# Gateway +callsign= +sendA=0 +sendB=0 +sendC=0 +sendD=0 +sendE=0 +address=127.0.0.1 +# +# Announcements +language=0 +format=0 +interval=0 +# +# Window Position (GUI only) +windowX=-1 +windowY=-1