diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..3377d11 --- /dev/null +++ b/install.sh @@ -0,0 +1,119 @@ +#!/bin/bash +# D-Star Site Install Script +# For Raspberry Pi 3 running Raspberry Pi OS Lite +# Usage: sudo bash install.sh + +set -e + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MMDVM_SOURCE_DIR="/home/pi/MMDVM-Host" +MMDVM_REPO="https://github.com/g4klx/MMDVMHost.git" +ZEROTIER_NETWORK="c5029fdad4ccf9c5" + +echo "================================================" +echo " D-Star Site Installer" +echo "================================================" +echo "" + +# Select site +echo "Available sites:" +echo " 1) ORNG - Orange Portable" +echo " 2) ORNG2 - Orange TXRX" +echo "" +read -p "Select site [1-2]: " SITE_CHOICE + +case $SITE_CHOICE in + 1) SITE="ORNG" ;; + 2) SITE="ORNG2" ;; + *) echo "Invalid selection"; exit 1 ;; +esac + +echo "" +echo "Installing site: $SITE" +echo "" + +# Load site config +source "$REPO_DIR/sites/$SITE/site.conf" + +# Install dependencies +echo "Installing dependencies..." +apt-get update -qq +apt-get install -y git build-essential nlohmann-json3-dev libssl-dev python3 + +# Install ZeroTier +echo "Installing ZeroTier..." +curl -s https://install.zerotier.com | bash +zerotier-cli join $ZEROTIER_NETWORK +echo "ZeroTier installed and joined network $ZEROTIER_NETWORK" +echo "NOTE: Approve this device in ZeroTier Central before continuing" +echo "" +read -p "Press ENTER once device is approved in ZeroTier Central..." + +# Enable serial port +echo "Configuring serial port..." +raspi-config nonint do_serial_hw 0 +raspi-config nonint do_serial_cons 1 + +# Add pi user to dialout +usermod -a -G dialout pi + +# Clone MMDVMHost if not present +if [ ! -d "$MMDVM_SOURCE_DIR" ]; then + echo "Cloning MMDVMHost..." + git clone "$MMDVM_REPO" "$MMDVM_SOURCE_DIR" +fi + +# Copy patched source files +echo "Applying patches..." +cp "$REPO_DIR/src/DStarNetwork.cpp" "$MMDVM_SOURCE_DIR/" +cp "$REPO_DIR/src/DStarNetwork.h" "$MMDVM_SOURCE_DIR/" +cp "$REPO_DIR/src/Conf.h" "$MMDVM_SOURCE_DIR/" + +cd "$MMDVM_SOURCE_DIR" +python3 "$REPO_DIR/src/patch_conf_cpp.py" +python3 "$REPO_DIR/src/patch_mmdvm_host_cpp.py" + +# Compile +echo "Compiling MMDVMHost..." +make -j$(nproc) + +# Install binary +echo "Installing binary..." +cp MMDVM-Host /usr/local/bin/MMDVM-Host +chmod 755 /usr/local/bin/MMDVM-Host + +# Generate config from template +echo "Generating config for $SITE..." +sed \ + -e "s/%%CALLSIGN%%/$CALLSIGN/g" \ + -e "s/%%MODULE%%/$MODULE/g" \ + -e "s/%%SITE_NAME%%/$SITE_NAME/g" \ + -e "s/%%GATEWAY_ADDRESS%%/$GATEWAY_ADDRESS/g" \ + -e "s/%%GATEWAY_PORT%%/$GATEWAY_PORT/g" \ + -e "s/%%LOCAL_PORT%%/$LOCAL_PORT/g" \ + -e "s/%%MODEM_PORT%%/$MODEM_PORT/g" \ + -e "s/%%MODEM_SPEED%%/$MODEM_SPEED/g" \ + "$REPO_DIR/sites/mmdvmhost.template" > /etc/mmdvmhost + +echo "Config written to /etc/mmdvmhost" + +# Install service +echo "Installing systemd service..." +cp "$REPO_DIR/services/mmdvmhost.service" /etc/systemd/system/ +systemctl daemon-reload +systemctl enable mmdvmhost +systemctl start mmdvmhost + +echo "" +echo "================================================" +echo " Installation complete for site: $SITE" +echo "================================================" +echo "" +echo "ZeroTier status:" +zerotier-cli status +echo "" +echo "Service status:" +systemctl status mmdvmhost --no-pager +echo "" +echo "Logs:" +echo " journalctl -u mmdvmhost -f" diff --git a/services/mmdvmhost.service b/services/mmdvmhost.service new file mode 100644 index 0000000..ab220d6 --- /dev/null +++ b/services/mmdvmhost.service @@ -0,0 +1,14 @@ +[Unit] +Description=MMDVMHost D-Star Site Service +After=network.target +After=time-sync.target + +[Service] +Type=forking +ExecStart=/usr/local/bin/MMDVM-Host /etc/mmdvmhost +Restart=on-failure +RestartSec=10 +User=root + +[Install] +WantedBy=multi-user.target diff --git a/sites/ORNG/site.conf b/sites/ORNG/site.conf new file mode 100644 index 0000000..5870499 --- /dev/null +++ b/sites/ORNG/site.conf @@ -0,0 +1,9 @@ +# ORANGE-PORTABLE Site Configuration +SITE_NAME=ORNG +CALLSIGN=N8MFN +MODULE=B +GATEWAY_ADDRESS=10.121.15.78 +GATEWAY_PORT=20013 +LOCAL_PORT=20016 +MODEM_PORT=/dev/ttyAMA0 +MODEM_SPEED=115200 diff --git a/sites/ORNG2/site.conf b/sites/ORNG2/site.conf new file mode 100644 index 0000000..ed23c44 --- /dev/null +++ b/sites/ORNG2/site.conf @@ -0,0 +1,9 @@ +# ORANGE-TXRX Site Configuration +SITE_NAME=ORNG2 +CALLSIGN=N8MFN +MODULE=B +GATEWAY_ADDRESS=10.121.15.78 +GATEWAY_PORT=20013 +LOCAL_PORT=20017 +MODEM_PORT=/dev/ttyAMA0 +MODEM_SPEED=115200 diff --git a/sites/mmdvmhost.template b/sites/mmdvmhost.template new file mode 100644 index 0000000..7649c57 --- /dev/null +++ b/sites/mmdvmhost.template @@ -0,0 +1,109 @@ +[General] +Callsign=%%CALLSIGN%% +Id=0 +Timeout=180 +Duplex=1 +ModeHang=20 +Daemon=1 + +[Info] +RXFrequency=444000000 +TXFrequency=449000000 +Power=1 +Latitude=0.0 +Longitude=0.0 +Height=0 +Location= +Description= +URL= + +[Log] +MQTTLevel=0 +DisplayLevel=2 + +[MQTT] +Host=10.0.2.147 +Port=1883 +Keepalive=60 +Name=%%SITE_NAME%% +Auth=0 + +[CW Id] +Enable=0 +Time=10 +Callsign=%%CALLSIGN%% + +[Modem] +Protocol=uart +UARTPort=%%MODEM_PORT%% +UARTSpeed=%%MODEM_SPEED%% +RXInvert=0 +TXInvert=0 +PTTInvert=0 +TXDelay=100 +RXOffset=0 +TXOffset=0 +RXDCOffset=0 +TXDCOffset=0 +RFLevel=100 +RXLevel=50 +TXLevel=50 +CWIdTXLevel=50 +D-StarTXLevel=50 +DMRTXLevel=50 +YSFTXLevel=50 +P25TXLevel=50 +NXDNTXLevel=50 +POCSAGTXLevel=50 +FMTXLevel=50 +UseCOSAsLockout=0 +Trace=0 +Debug=0 + +[D-Star] +Enable=1 +Module=%%MODULE%% +SelfOnly=0 +AckReply=1 +AckTime=750 +AckMessage=0 +ErrorReply=1 +RemoteGateway=0 +ModeHang=20 + +[DMR] +Enable=0 + +[System Fusion] +Enable=0 + +[P25] +Enable=0 + +[NXDN] +Enable=0 + +[POCSAG] +Enable=0 + +[FM] +Enable=0 + +[D-Star Network] +Enable=1 +GatewayAddress=%%GATEWAY_ADDRESS%% +GatewayPort=%%GATEWAY_PORT%% +LocalAddress=0.0.0.0 +LocalPort=%%LOCAL_PORT%% +Name=%%SITE_NAME%% +ModeHang=20 +Debug=0 + +[DMR Network] +Enable=0 + +[Lock File] +Enable=0 + +[Remote Control] +Enable=0 diff --git a/src/Conf.h b/src/Conf.h new file mode 100644 index 0000000..984c89f --- /dev/null +++ b/src/Conf.h @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2015-2026 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(CONF_H) +#define CONF_H + +#include +#include + +#include + +class CConf +{ +public: + CConf(const std::string& file); + ~CConf(); + + bool read(); + + // The General section + std::string getCallsign() const; + unsigned int getId() const; + unsigned int getTimeout() const; + bool getDuplex() const; + bool getDaemon() const; + + // The Info section + unsigned int getRXFrequency() const; + unsigned int getTXFrequency() const; + unsigned int getPower() const; + float getLatitude() const; + float getLongitude() const; + int getHeight() const; + std::string getLocation() const; + std::string getDescription() const; + std::string getURL() const; + + // The Log section + unsigned int getLogMQTTLevel() const; + unsigned int getLogDisplayLevel() const; + + // The MQTT section + std::string getMQTTHost() const; + unsigned short getMQTTPort() const; + unsigned int getMQTTKeepalive() const; + std::string getMQTTName() const; + bool getMQTTAuthEnabled() const; + std::string getMQTTUsername() const; + std::string getMQTTPassword() const; + + // The CW ID section + bool getCWIdEnabled() const; + unsigned int getCWIdTime() const; + std::string getCWIdCallsign() const; + +#if defined(USE_DMR) || defined(USE_P25) + // The DMR Id section + std::string getDMRIdLookupFile() const; + unsigned int getDMRIdLookupTime() const; +#endif + +#if defined(USE_NXDN) + // The NXDN Id section + std::string getNXDNIdLookupFile() const; + unsigned int getNXDNIdLookupTime() const; +#endif + + // The Modem section + std::string getModemProtocol() const; + std::string getModemUARTPort() const; + unsigned int getModemUARTSpeed() const; + std::string getModemI2CPort() const; + unsigned int getModemI2CAddress() const; + std::string getModemModemAddress() const; + unsigned short getModemModemPort() const; + std::string getModemLocalAddress() const; + unsigned short getModemLocalPort() const; + bool getModemRXInvert() const; + bool getModemTXInvert() const; + bool getModemPTTInvert() const; + unsigned int getModemTXDelay() const; +#if defined(USE_DMR) + unsigned int getModemDMRDelay() const; +#endif + int getModemTXOffset() const; + int getModemRXOffset() const; + int getModemRXDCOffset() const; + int getModemTXDCOffset() const; + float getModemRFLevel() const; + float getModemRXLevel() const; + float getModemCWIdTXLevel() const; +#if defined(USE_DSTAR) + float getModemDStarTXLevel() const; +#endif +#if defined(USE_DMR) + float getModemDMRTXLevel() const; +#endif +#if defined(USE_YSF) + float getModemYSFTXLevel() const; +#endif +#if defined(USE_P25) + float getModemP25TXLevel() const; +#endif +#if defined(USE_NXDN) + float getModemNXDNTXLevel() const; +#endif +#if defined(USE_POCSAG) + float getModemPOCSAGTXLevel() const; +#endif +#if defined(USE_FM) + float getModemFMTXLevel() const; +#endif + std::string getModemRSSIMappingFile() const; + bool getModemUseCOSAsLockout() const; + bool getModemTrace() const; + bool getModemDebug() const; + + // The Transparent Data section + bool getTransparentEnabled() const; + std::string getTransparentRemoteAddress() const; + unsigned short getTransparentRemotePort() const; + unsigned short getTransparentLocalPort() const; + unsigned int getTransparentSendFrameType() const; + +#if defined(USE_DSTAR) + // The D-Star section + bool getDStarEnabled() const; + std::string getDStarModule() const; + bool getDStarSelfOnly() const; + std::vector getDStarBlackList() const; + std::vector getDStarWhiteList() const; + bool getDStarAckReply() const; + unsigned int getDStarAckTime() const; + DSTAR_ACK getDStarAckMessage() const; + bool getDStarErrorReply() const; + bool getDStarRemoteGateway() const; + unsigned int getDStarModeHang() const; +#endif + +#if defined(USE_DMR) + // The DMR section + bool getDMREnabled() const; + DMR_BEACONS getDMRBeacons() const; + unsigned int getDMRBeaconInterval() const; + unsigned int getDMRBeaconDuration() const; + unsigned int getDMRId() const; + unsigned int getDMRColorCode() const; + bool getDMREmbeddedLCOnly() const; + bool getDMRDumpTAData() const; + bool getDMRSelfOnly() const; + std::vector getDMRPrefixes() const; + std::vector getDMRBlackList() const; + std::vector getDMRWhiteList() const; + std::vector getDMRSlot1TGWhiteList() const; + std::vector getDMRSlot2TGWhiteList() const; + unsigned int getDMRCallHang() const; + unsigned int getDMRTXHang() const; + unsigned int getDMRModeHang() const; + DMR_OVCM getDMROVCM() const; + bool getDMRProtect() const; +#endif + +#if defined(USE_YSF) + // The System Fusion section + bool getFusionEnabled() const; + bool getFusionLowDeviation() const; + bool getFusionRemoteGateway() const; + bool getFusionSelfOnly() const; + unsigned int getFusionTXHang() const; + unsigned int getFusionModeHang() const; +#endif + +#if defined(USE_P25) + // The P25 section + bool getP25Enabled() const; + unsigned int getP25Id() const; + unsigned int getP25NAC() const; + bool getP25SelfOnly() const; + bool getP25OverrideUID() const; + bool getP25RemoteGateway() const; + unsigned int getP25TXHang() const; + unsigned int getP25ModeHang() const; +#endif + +#if defined(USE_NXDN) + // The NXDN section + bool getNXDNEnabled() const; + unsigned int getNXDNId() const; + unsigned int getNXDNRAN() const; + bool getNXDNSelfOnly() const; + bool getNXDNRemoteGateway() const; + unsigned int getNXDNTXHang() const; + unsigned int getNXDNModeHang() const; +#endif + +#if defined(USE_POCSAG) + // The POCSAG section + bool getPOCSAGEnabled() const; + unsigned int getPOCSAGFrequency() const; +#endif + +#if defined(USE_FM) + // The FM Section + bool getFMEnabled() const; + std::string getFMCallsign() const; + unsigned int getFMCallsignSpeed() const; + unsigned int getFMCallsignFrequency() const; + unsigned int getFMCallsignTime() const; + unsigned int getFMCallsignHoldoff() const; + float getFMCallsignHighLevel() const; + float getFMCallsignLowLevel() const; + bool getFMCallsignAtStart() const; + bool getFMCallsignAtEnd() const; + bool getFMCallsignAtLatch() const; + std::string getFMRFAck() const; + std::string getFMExtAck() const; + unsigned int getFMAckSpeed() const; + unsigned int getFMAckFrequency() const; + unsigned int getFMAckMinTime() const; + unsigned int getFMAckDelay() const; + float getFMAckLevel() const; + unsigned int getFMTimeout() const; + float getFMTimeoutLevel() const; + float getFMCTCSSFrequency() const; + unsigned int getFMCTCSSHighThreshold() const; + unsigned int getFMCTCSSLowThreshold() const; + float getFMCTCSSLevel() const; + unsigned int getFMKerchunkTime() const; + unsigned int getFMHangTime() const; + unsigned int getFMAccessMode() const; + bool getFMLinkMode() const; + bool getFMCOSInvert() const; + bool getFMNoiseSquelch() const; + unsigned int getFMSquelchHighThreshold() const; + unsigned int getFMSquelchLowThreshold() const; + unsigned int getFMRFAudioBoost() const; + float getFMMaxDevLevel() const; + unsigned int getFMExtAudioBoost() const; + unsigned int getFMModeHang() const; +#endif + +#if defined(USE_DSTAR) + // The D-Star Network section + bool getDStarNetworkEnabled() const; + std::string getDStarGatewayAddress() const; + unsigned short getDStarGatewayPort() const; + std::string getDStarLocalAddress() const; + unsigned short getDStarLocalPort() const; + std::string getDStarNetworkName() const; + unsigned int getDStarNetworkModeHang() const; + bool getDStarNetworkDebug() const; +#endif + +#if defined(USE_DMR) + // The DMR Network section + bool getDMRNetworkEnabled() const; + std::string getDMRNetworkGatewayAddress() const; + unsigned short getDMRNetworkGatewayPort() const; + std::string getDMRNetworkLocalAddress() const; + unsigned short getDMRNetworkLocalPort() const; + bool getDMRNetworkDebug() const; + unsigned int getDMRNetworkJitter() const; + bool getDMRNetworkSlot1() const; + bool getDMRNetworkSlot2() const; + unsigned int getDMRNetworkModeHang() const; +#endif + +#if defined(USE_YSF) + // The System Fusion Network section + bool getFusionNetworkEnabled() const; + std::string getFusionNetworkLocalAddress() const; + unsigned short getFusionNetworkLocalPort() const; + std::string getFusionNetworkGatewayAddress() const; + unsigned short getFusionNetworkGatewayPort() const; + unsigned int getFusionNetworkModeHang() const; + bool getFusionNetworkDebug() const; +#endif + +#if defined(USE_P25) + // The P25 Network section + bool getP25NetworkEnabled() const; + std::string getP25GatewayAddress() const; + unsigned short getP25GatewayPort() const; + std::string getP25LocalAddress() const; + unsigned short getP25LocalPort() const; + unsigned int getP25NetworkModeHang() const; + bool getP25NetworkDebug() const; +#endif + +#if defined(USE_NXDN) + // The NXDN Network section + bool getNXDNNetworkEnabled() const; + std::string getNXDNNetworkProtocol() const; + std::string getNXDNGatewayAddress() const; + unsigned short getNXDNGatewayPort() const; + std::string getNXDNLocalAddress() const; + unsigned short getNXDNLocalPort() const; + unsigned int getNXDNNetworkModeHang() const; + bool getNXDNNetworkDebug() const; +#endif + +#if defined(USE_POCSAG) + // The POCSAG Network section + bool getPOCSAGNetworkEnabled() const; + std::string getPOCSAGGatewayAddress() const; + unsigned short getPOCSAGGatewayPort() const; + std::string getPOCSAGLocalAddress() const; + unsigned short getPOCSAGLocalPort() const; + unsigned int getPOCSAGNetworkModeHang() const; + bool getPOCSAGNetworkDebug() const; +#endif + +#if defined(USE_FM) + // The FM Network section + bool getFMNetworkEnabled() const; + std::string getFMGatewayAddress() const; + unsigned short getFMGatewayPort() const; + std::string getFMLocalAddress() const; + unsigned short getFMLocalPort() const; + bool getFMPreEmphasis() const; + bool getFMDeEmphasis() const; + float getFMTXAudioGain() const; + float getFMRXAudioGain() const; + unsigned int getFMNetworkModeHang() const; + bool getFMNetworkDebug() const; +#endif + + // The Lock File section + bool getLockFileEnabled() const; + std::string getLockFileName() const; + + // The Remote Control section + bool getRemoteControlEnabled() const; + +private: + std::string m_file; + std::string m_callsign; + unsigned int m_id; + unsigned int m_timeout; + bool m_duplex; + bool m_daemon; + + unsigned int m_rxFrequency; + unsigned int m_txFrequency; + unsigned int m_power; + float m_latitude; + float m_longitude; + int m_height; + std::string m_location; + std::string m_description; + std::string m_url; + + unsigned int m_logMQTTLevel; + unsigned int m_logDisplayLevel; + + std::string m_mqttHost; + unsigned short m_mqttPort; + unsigned int m_mqttKeepalive; + std::string m_mqttName; + bool m_mqttAuthEnabled; + std::string m_mqttUsername; + std::string m_mqttPassword; + + bool m_cwIdEnabled; + unsigned int m_cwIdTime; + std::string m_cwIdCallsign; + +#if defined(USE_DMR) || defined(USE_P25) + std::string m_dmrIdLookupFile; + unsigned int m_dmrIdLookupTime; +#endif + +#if defined(USE_NXDN) + std::string m_nxdnIdLookupFile; + unsigned int m_nxdnIdLookupTime; +#endif + + std::string m_modemProtocol; + std::string m_modemUARTPort; + unsigned int m_modemUARTSpeed; + std::string m_modemI2CPort; + unsigned int m_modemI2CAddress; + std::string m_modemModemAddress; + unsigned short m_modemModemPort; + std::string m_modemLocalAddress; + unsigned short m_modemLocalPort; + bool m_modemRXInvert; + bool m_modemTXInvert; + bool m_modemPTTInvert; + unsigned int m_modemTXDelay; +#if defined(USE_DMR) + unsigned int m_modemDMRDelay; +#endif + int m_modemTXOffset; + int m_modemRXOffset; + int m_modemRXDCOffset; + int m_modemTXDCOffset; + float m_modemRFLevel; + float m_modemRXLevel; + float m_modemCWIdTXLevel; + float m_modemDStarTXLevel; + float m_modemDMRTXLevel; + float m_modemYSFTXLevel; + float m_modemP25TXLevel; + float m_modemNXDNTXLevel; + float m_modemPOCSAGTXLevel; + float m_modemFMTXLevel; + std::string m_modemRSSIMappingFile; + bool m_modemUseCOSAsLockout; + bool m_modemTrace; + bool m_modemDebug; + + bool m_transparentEnabled; + std::string m_transparentRemoteAddress; + unsigned short m_transparentRemotePort; + unsigned short m_transparentLocalPort; + unsigned int m_transparentSendFrameType; + +#if defined(USE_DSTAR) + bool m_dstarEnabled; + std::string m_dstarModule; + bool m_dstarSelfOnly; + std::vector m_dstarBlackList; + std::vector m_dstarWhiteList; + bool m_dstarAckReply; + unsigned int m_dstarAckTime; + DSTAR_ACK m_dstarAckMessage; + bool m_dstarErrorReply; + bool m_dstarRemoteGateway; +#endif + unsigned int m_dstarModeHang; + +#if defined(USE_DMR) + bool m_dmrEnabled; + DMR_BEACONS m_dmrBeacons; + unsigned int m_dmrBeaconInterval; + unsigned int m_dmrBeaconDuration; +#endif + unsigned int m_dmrId; +#if defined(USE_DMR) + unsigned int m_dmrColorCode; + bool m_dmrSelfOnly; + bool m_dmrEmbeddedLCOnly; + bool m_dmrDumpTAData; + std::vector m_dmrPrefixes; + std::vector m_dmrBlackList; + std::vector m_dmrWhiteList; + std::vector m_dmrSlot1TGWhiteList; + std::vector m_dmrSlot2TGWhiteList; + unsigned int m_dmrCallHang; + unsigned int m_dmrTXHang; +#endif + unsigned int m_dmrModeHang; +#if defined(USE_DMR) + DMR_OVCM m_dmrOVCM; + bool m_dmrProtect; +#endif + +#if defined(USE_YSF) + bool m_fusionEnabled; + bool m_fusionLowDeviation; + bool m_fusionRemoteGateway; + bool m_fusionSelfOnly; + unsigned int m_fusionTXHang; +#endif + unsigned int m_fusionModeHang; + +#if defined(USE_P25) + bool m_p25Enabled; +#endif + unsigned int m_p25Id; +#if defined(USE_P25) + unsigned int m_p25NAC; + bool m_p25SelfOnly; + bool m_p25OverrideUID; + bool m_p25RemoteGateway; + unsigned int m_p25TXHang; +#endif + unsigned int m_p25ModeHang; + +#if defined(USE_NXDN) + bool m_nxdnEnabled; + unsigned int m_nxdnId; + unsigned int m_nxdnRAN; + bool m_nxdnSelfOnly; + bool m_nxdnRemoteGateway; + unsigned int m_nxdnTXHang; +#endif + unsigned int m_nxdnModeHang; + +#if defined(USE_POCSAG) + bool m_pocsagEnabled; +#endif + unsigned int m_pocsagFrequency; + +#if defined(USE_FM) + bool m_fmEnabled; +#endif + std::string m_fmCallsign; +#if defined(USE_FM) + unsigned int m_fmCallsignSpeed; + unsigned int m_fmCallsignFrequency; + unsigned int m_fmCallsignTime; + unsigned int m_fmCallsignHoldoff; + float m_fmCallsignHighLevel; + float m_fmCallsignLowLevel; + bool m_fmCallsignAtStart; + bool m_fmCallsignAtEnd; + bool m_fmCallsignAtLatch; + std::string m_fmRFAck; + std::string m_fmExtAck; + unsigned int m_fmAckSpeed; + unsigned int m_fmAckFrequency; + unsigned int m_fmAckMinTime; + unsigned int m_fmAckDelay; + float m_fmAckLevel; +#endif + unsigned int m_fmTimeout; +#if defined(USE_FM) + float m_fmTimeoutLevel; + float m_fmCTCSSFrequency; + unsigned int m_fmCTCSSHighThreshold; + unsigned int m_fmCTCSSLowThreshold; + float m_fmCTCSSLevel; + unsigned int m_fmKerchunkTime; + unsigned int m_fmHangTime; + unsigned int m_fmAccessMode; + bool m_fmLinkMode; + bool m_fmCOSInvert; + bool m_fmNoiseSquelch; + unsigned int m_fmSquelchHighThreshold; + unsigned int m_fmSquelchLowThreshold; + unsigned int m_fmRFAudioBoost; + float m_fmMaxDevLevel; + unsigned int m_fmExtAudioBoost; +#endif + unsigned int m_fmModeHang; + +#if defined(USE_DSTAR) + bool m_dstarNetworkEnabled; + std::string m_dstarGatewayAddress; + unsigned short m_dstarGatewayPort; + std::string m_dstarLocalAddress; + unsigned short m_dstarLocalPort; + std::string m_dstarNetworkName; +#endif + unsigned int m_dstarNetworkModeHang; +#if defined(USE_DSTAR) + bool m_dstarNetworkDebug; +#endif + +#if defined(USE_DMR) + bool m_dmrNetworkEnabled; + std::string m_dmrNetworkGatewayAddress; + unsigned short m_dmrNetworkGatewayPort; + std::string m_dmrNetworkLocalAddress; + unsigned short m_dmrNetworkLocalPort; + bool m_dmrNetworkDebug; + unsigned int m_dmrNetworkJitter; + bool m_dmrNetworkSlot1; + bool m_dmrNetworkSlot2; +#endif + unsigned int m_dmrNetworkModeHang; + +#if defined(USE_YSF) + bool m_fusionNetworkEnabled; + std::string m_fusionNetworkLocalAddress; + unsigned short m_fusionNetworkLocalPort; + std::string m_fusionNetworkGatewayAddress; + unsigned short m_fusionNetworkGatewayPort; +#endif + unsigned int m_fusionNetworkModeHang; +#if defined(USE_YSF) + bool m_fusionNetworkDebug; +#endif + +#if defined(USE_P25) + bool m_p25NetworkEnabled; + std::string m_p25GatewayAddress; + unsigned short m_p25GatewayPort; + std::string m_p25LocalAddress; + unsigned short m_p25LocalPort; +#endif + unsigned int m_p25NetworkModeHang; +#if defined(USE_P25) + bool m_p25NetworkDebug; +#endif + +#if defined(USE_NXDN) + bool m_nxdnNetworkEnabled; + std::string m_nxdnNetworkProtocol; + std::string m_nxdnGatewayAddress; + unsigned short m_nxdnGatewayPort; + std::string m_nxdnLocalAddress; + unsigned short m_nxdnLocalPort; +#endif + unsigned int m_nxdnNetworkModeHang; +#if defined(USE_NXDN) + bool m_nxdnNetworkDebug; +#endif + +#if defined(USE_POCSAG) + bool m_pocsagNetworkEnabled; + std::string m_pocsagGatewayAddress; + unsigned short m_pocsagGatewayPort; + std::string m_pocsagLocalAddress; + unsigned short m_pocsagLocalPort; + unsigned int m_pocsagNetworkModeHang; + bool m_pocsagNetworkDebug; +#endif + +#if defined(USE_FM) + bool m_fmNetworkEnabled; + std::string m_fmGatewayAddress; + unsigned short m_fmGatewayPort; + std::string m_fmLocalAddress; + unsigned short m_fmLocalPort; + bool m_fmPreEmphasis; + bool m_fmDeEmphasis; + float m_fmTXAudioGain; + float m_fmRXAudioGain; +#endif + unsigned int m_fmNetworkModeHang; +#if defined(USE_FM) + bool m_fmNetworkDebug; +#endif + + bool m_lockFileEnabled; + std::string m_lockFileName; + + bool m_remoteControlEnabled; +}; + +#endif diff --git a/src/DStarNetwork.cpp b/src/DStarNetwork.cpp new file mode 100644 index 0000000..12cdd9e --- /dev/null +++ b/src/DStarNetwork.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2009-2014,2016,2019,2020,2021,2023,2024,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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarDefines.h" +#include "DStarNetwork.h" +#include "StopWatch.h" +#include "Defines.h" +#include "Utils.h" +#include "Log.h" + +#if defined(USE_DSTAR) + +#include +#include +#include +#include + +const unsigned int BUFFER_LENGTH = 100U; + +CDStarNetwork::CDStarNetwork(const std::string& gatewayAddress, unsigned short gatewayPort, const std::string& localAddress, unsigned short localPort, bool duplex, const char* version, const std::string& name, bool debug) : +m_socket(localAddress, localPort), +m_addr(), +m_addrLen(0U), +m_duplex(duplex), +m_version(version), +m_name(name), +m_debug(debug), +m_enabled(false), +m_outId(0U), +m_outSeq(0U), +m_inId(0U), +m_buffer(1000U, "D-Star Network"), +m_pollTimer(1000U, 60U), +m_linkStatus(LINK_STATUS::NONE), +m_linkReflector(nullptr), +m_random() +{ + if (CUDPSocket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0) + m_addrLen = 0U; + + m_linkReflector = new unsigned char[DSTAR_LONG_CALLSIGN_LENGTH]; + ::memset(m_linkReflector, 0, DSTAR_LONG_CALLSIGN_LENGTH); + + std::random_device rd; + std::mt19937 mt(rd()); + m_random = mt; +} + +CDStarNetwork::~CDStarNetwork() +{ + delete[] m_linkReflector; +} + +bool CDStarNetwork::open() +{ + if (m_addrLen == 0U) { + LogError("Unable to resolve the address of the ircDDB Gateway"); + return false; + } + + LogMessage("Opening D-Star network connection"); + + m_pollTimer.start(); + + bool ret = m_socket.open(m_addr); + if (!ret) + return false; + + // Send registration with site name if configured + if (!m_name.empty()) { + LogMessage("D-Star registering with split controller as \"%s\"", m_name.c_str()); + writeRegister(); + } + + return true; +} + +bool CDStarNetwork::writeHeader(const unsigned char* header, unsigned int length, bool busy) +{ + assert(header != nullptr); + + unsigned char buffer[50U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = busy ? 0x22U : 0x20U; + + // Create a random id for this transmission + std::uniform_int_distribution dist(0x0001, 0xfffe); + m_outId = dist(m_random); + + buffer[5] = m_outId / 256U; // Unique session id + buffer[6] = m_outId % 256U; + + buffer[7] = 0U; + + ::memcpy(buffer + 8U, header, length); + + m_outSeq = 0U; + + if (m_debug) + CUtils::dump(1U, "D-Star Network Header Sent", buffer, 49U); + + for (unsigned int i = 0U; i < 2U; i++) { + bool ret = m_socket.write(buffer, 49U, m_addr, m_addrLen); + if (!ret) + return false; + } + + return true; +} + +bool CDStarNetwork::writeData(const unsigned char* data, unsigned int length, unsigned int errors, bool end, bool busy) +{ + assert(data != nullptr); + + unsigned char buffer[30U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = busy ? 0x23U : 0x21U; + + buffer[5] = m_outId / 256U; // Unique session id + buffer[6] = m_outId % 256U; + + // If this is a data sync, reset the sequence to zero + if (data[9] == 0x55 && data[10] == 0x2D && data[11] == 0x16) + m_outSeq = 0U; + + buffer[7] = m_outSeq; + if (end) + buffer[7] |= 0x40U; // End of data marker + + buffer[8] = errors; + + m_outSeq++; + if (m_outSeq > 0x14U) + m_outSeq = 0U; + + ::memcpy(buffer + 9U, data, length); + + if (m_debug) + CUtils::dump(1U, "D-Star Network Data Sent", buffer, length + 9U); + + return m_socket.write(buffer, length + 9U, m_addr, m_addrLen); +} + +bool CDStarNetwork::writePoll(const char* text) +{ + assert(text != nullptr); + + unsigned char buffer[40U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x0AU; // Poll with text + + unsigned int length = (unsigned int)::strlen(text); + + // Include the nul at the end also + ::memcpy(buffer + 5U, text, length + 1U); + + // if (m_debug) + // CUtils::dump(1U, "D-Star Network Poll Sent", buffer, 6U + length); + + return m_socket.write(buffer, 6U + length, m_addr, m_addrLen); +} + +bool CDStarNetwork::writeRegister() +{ + unsigned char buffer[40U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x0BU; // NETWORK_REGISTER with name + + unsigned int length = (unsigned int)m_name.length(); + if (length > 34U) length = 34U; // buffer[40] - offset[5] - null[1] + + for (unsigned int i = 0U; i < length; i++) + buffer[5U + i] = m_name[i]; + buffer[5U + length] = 0x00U; + + if (m_debug) + CUtils::dump(1U, "D-Star Network Register Sent", buffer, 6U + length); + + return m_socket.write(buffer, 6U + length, m_addr, m_addrLen); +} + +void CDStarNetwork::clock(unsigned int ms) +{ + m_pollTimer.clock(ms); + if (m_pollTimer.hasExpired()) { + char text[60U]; +#if defined(_WIN32) || defined(_WIN64) + if (m_duplex) + ::sprintf(text, "win_mmdvm-%s", m_version); + else + ::sprintf(text, "win_mmdvm-dvmega-%s", m_version); +#else + if (m_duplex) + ::sprintf(text, "linux_mmdvm-%s", m_version); + else + ::sprintf(text, "linux_mmdvm-dvmega-%s", m_version); +#endif + writePoll(text); + + // Send registration keepalive if name is configured + if (!m_name.empty()) + writeRegister(); + + m_pollTimer.start(); + } + + unsigned char buffer[BUFFER_LENGTH]; + + sockaddr_storage address; + unsigned int addrLen; + int length = m_socket.read(buffer, BUFFER_LENGTH, address, addrLen); + if (length <= 0) + return; + + if (!CUDPSocket::match(m_addr, address)) { + LogMessage("D-Star, packet received from an invalid source"); + return; + } + + // Invalid packet type? + if (::memcmp(buffer, "DSRP", 4U) != 0) + return; + + switch (buffer[4]) { + case 0x00U: // NETWORK_TEXT; + if (m_debug) + CUtils::dump(1U, "D-Star Network Status Received", buffer, length); + + m_linkStatus = LINK_STATUS(buffer[25U]); + ::memcpy(m_linkReflector, buffer + 26U, DSTAR_LONG_CALLSIGN_LENGTH); + LogMessage("D-Star link status set to \"%20.20s\"", buffer + 5U); + return; + + case 0x01U: // NETWORK_TEMPTEXT; + case 0x04U: // NETWORK_STATUS1..5 + case 0x0AU: // POLL + case 0x24U: // NETWORK_DD_DATA + return; + + case 0x20U: // NETWORK_HEADER + if (m_inId == 0U && m_enabled) { + if (m_debug) + CUtils::dump(1U, "D-Star Network Header Received", buffer, length); + + m_inId = buffer[5] * 256U + buffer[6]; + + unsigned char c = length - 7U; + m_buffer.addData(&c, 1U); + + c = TAG_HEADER; + m_buffer.addData(&c, 1U); + + m_buffer.addData(buffer + 8U, length - 8U); + } + break; + + case 0x21U: // NETWORK_DATA + if (m_enabled) { + if (m_debug) + CUtils::dump(1U, "D-Star Network Data Received", buffer, length); + + uint16_t id = buffer[5] * 256U + buffer[6]; + + // Check that the stream id matches the valid header, reject otherwise + if (id == m_inId && m_enabled) { + unsigned char ctrl[3U]; + + ctrl[0U] = length - 7U; + + // Is this the last packet in the stream? + if ((buffer[7] & 0x40) == 0x40) { + m_inId = 0U; + ctrl[1U] = TAG_EOT; + } else { + ctrl[1U] = TAG_DATA; + } + + ctrl[2U] = buffer[7] & 0x3FU; + + m_buffer.addData(ctrl, 3U); + + m_buffer.addData(buffer + 9U, length - 9U); + } + } + break; + + default: + CUtils::dump("Unknown D-Star packet from the Gateway", buffer, length); + break; + } +} + +unsigned int CDStarNetwork::read(unsigned char* data, unsigned int length) +{ + assert(data != nullptr); + + if (m_buffer.isEmpty()) + return 0U; + + unsigned char c = 0U; + m_buffer.getData(&c, 1U); + + assert(c <= 100U); + assert(c <= length); + + unsigned char buffer[100U]; + m_buffer.getData(buffer, c); + + switch (buffer[0U]) { + case TAG_HEADER: + case TAG_DATA: + case TAG_EOT: + ::memcpy(data, buffer, c); + return c; + + default: + return 0U; + } +} + +void CDStarNetwork::reset() +{ + m_inId = 0U; +} + +bool CDStarNetwork::isConnected() const +{ + return (m_addrLen != 0); +} + +void CDStarNetwork::close() +{ + m_socket.close(); + + LogMessage("Closing D-Star network connection"); +} + +void CDStarNetwork::enable(bool enabled) +{ + if (enabled && !m_enabled) + reset(); + else if (!enabled && m_enabled) + m_buffer.clear(); + + m_enabled = enabled; +} + +void CDStarNetwork::getStatus(LINK_STATUS& status, unsigned char* reflector) +{ + assert(reflector != nullptr); + + status = m_linkStatus; + + ::memcpy(reflector, m_linkReflector, DSTAR_LONG_CALLSIGN_LENGTH); +} + +#endif diff --git a/src/DStarNetwork.h b/src/DStarNetwork.h new file mode 100644 index 0000000..4dd462d --- /dev/null +++ b/src/DStarNetwork.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2009-2014,2016,2020,2021,2023 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarNetwork_H +#define DStarNetwork_H + +#include "DStarDefines.h" +#include "RingBuffer.h" +#include "UDPSocket.h" +#include "Defines.h" +#include "Timer.h" + +#if defined(USE_DSTAR) + +#include +#include +#include + +class CDStarNetwork { +public: + CDStarNetwork(const std::string& gatewayAddress, unsigned short gatewayPort, const std::string& localAddress, unsigned short localPort, bool duplex, const char* version, const std::string& name, bool debug); + ~CDStarNetwork(); + + bool open(); + + void enable(bool enabled); + + bool writeHeader(const unsigned char* header, unsigned int length, bool busy); + bool writeData(const unsigned char* data, unsigned int length, unsigned int errors, bool end, bool busy); + + void getStatus(LINK_STATUS& status, unsigned char* reflector); + + unsigned int read(unsigned char* data, unsigned int length); + + void reset(); + + bool isConnected() const; + + void close(); + + void clock(unsigned int ms); + +private: + CUDPSocket m_socket; + sockaddr_storage m_addr; + unsigned int m_addrLen; + bool m_duplex; + const char* m_version; + std::string m_name; + bool m_debug; + bool m_enabled; + uint16_t m_outId; + uint8_t m_outSeq; + uint16_t m_inId; + CRingBuffer m_buffer; + CTimer m_pollTimer; + LINK_STATUS m_linkStatus; + unsigned char* m_linkReflector; + std::mt19937 m_random; + + bool writePoll(const char* text); + bool writeRegister(); +}; + +#endif + +#endif diff --git a/src/patch_conf_cpp.py b/src/patch_conf_cpp.py new file mode 100644 index 0000000..38a545a --- /dev/null +++ b/src/patch_conf_cpp.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Patches Conf.cpp to add DStarNetworkName support""" +import sys + +with open('Conf.cpp', 'r') as f: + content = f.read() + +# 1. Add initializer after m_dstarLocalPort(0U), +old = 'm_dstarLocalPort(0U),\n#endif\nm_dstarNetworkModeHang(3U),' +new = 'm_dstarLocalPort(0U),\nm_dstarNetworkName(),\n#endif\nm_dstarNetworkModeHang(3U),' +if old not in content: + print("ERROR: Could not find initializer insertion point") + sys.exit(1) +content = content.replace(old, new, 1) +print("1. Initializer added OK") + +# 2. Add Name= parser - single tabs +old = '\t\t\telse if (::strcmp(key, "LocalPort") == 0)\n\t\t\t\tm_dstarLocalPort = (unsigned short)::atoi(value);\n\t\t\telse if (::strcmp(key, "ModeHang") == 0)\n\t\t\t\tm_dstarNetworkModeHang = (unsigned int)::atoi(value);\n\t\t\telse if (::strcmp(key, "Debug") == 0)\n\t\t\t\tm_dstarNetworkDebug = ::atoi(value) == 1;\n#endif\n#if defined(USE_DMR)\n\t\t} else if (section == SECTION::DMR_NETWORK) {' +new = '\t\t\telse if (::strcmp(key, "LocalPort") == 0)\n\t\t\t\tm_dstarLocalPort = (unsigned short)::atoi(value);\n\t\t\telse if (::strcmp(key, "Name") == 0)\n\t\t\t\tm_dstarNetworkName = value;\n\t\t\telse if (::strcmp(key, "ModeHang") == 0)\n\t\t\t\tm_dstarNetworkModeHang = (unsigned int)::atoi(value);\n\t\t\telse if (::strcmp(key, "Debug") == 0)\n\t\t\t\tm_dstarNetworkDebug = ::atoi(value) == 1;\n#endif\n#if defined(USE_DMR)\n\t\t} else if (section == SECTION::DMR_NETWORK) {' +if old not in content: + print("ERROR: Could not find Name= parser insertion point") + sys.exit(1) +content = content.replace(old, new, 1) +print("2. Name= parser added OK") + +# 3. Add getDStarNetworkName() getter - try tabs first then spaces +old = 'unsigned int CConf::getDStarNetworkModeHang() const\n{\n\treturn m_dstarNetworkModeHang;\n}' +new = 'std::string CConf::getDStarNetworkName() const\n{\n\treturn m_dstarNetworkName;\n}\n\nunsigned int CConf::getDStarNetworkModeHang() const\n{\n\treturn m_dstarNetworkModeHang;\n}' +if old not in content: + old = 'unsigned int CConf::getDStarNetworkModeHang() const\n{\n return m_dstarNetworkModeHang;\n}' + new = 'std::string CConf::getDStarNetworkName() const\n{\n return m_dstarNetworkName;\n}\n\nunsigned int CConf::getDStarNetworkModeHang() const\n{\n return m_dstarNetworkModeHang;\n}' + if old not in content: + print("ERROR: Could not find getter insertion point") + sys.exit(1) +content = content.replace(old, new, 1) +print("3. getDStarNetworkName() getter added OK") + +with open('Conf.cpp', 'w') as f: + f.write(content) + +print("Conf.cpp patched successfully") diff --git a/src/patch_mmdvm_host_cpp.py b/src/patch_mmdvm_host_cpp.py new file mode 100644 index 0000000..9e6c469 --- /dev/null +++ b/src/patch_mmdvm_host_cpp.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Patches MMDVM-Host.cpp to add DStarNetworkName support""" +import sys + +with open('MMDVM-Host.cpp', 'r') as f: + content = f.read() + +old = '\tbool debug = m_conf.getDStarNetworkDebug();\n\tm_dstarNetModeHang = m_conf.getDStarNetworkModeHang();\n\n\tLogInfo("D-Star Network Parameters");\n\tLogInfo(" Gateway Address: %s", gatewayAddress.c_str());\n\tLogInfo(" Gateway Port: %hu", gatewayPort);\n\tLogInfo(" Local Address: %s", localAddress.c_str());\n\tLogInfo(" Local Port: %hu", localPort);\n\tLogInfo(" Mode Hang: %us", m_dstarNetModeHang);\n\n\tm_dstarNetwork = new CDStarNetwork(gatewayAddress, gatewayPort, localAddress, localPort, m_duplex, VERSION, debug);' + +new = '\tbool debug = m_conf.getDStarNetworkDebug();\n\tstd::string name = m_conf.getDStarNetworkName();\n\tm_dstarNetModeHang = m_conf.getDStarNetworkModeHang();\n\n\tLogInfo("D-Star Network Parameters");\n\tLogInfo(" Gateway Address: %s", gatewayAddress.c_str());\n\tLogInfo(" Gateway Port: %hu", gatewayPort);\n\tLogInfo(" Local Address: %s", localAddress.c_str());\n\tLogInfo(" Local Port: %hu", localPort);\n\tLogInfo(" Mode Hang: %us", m_dstarNetModeHang);\n\tif (!name.empty())\n\t\tLogInfo(" Name: %s", name.c_str());\n\n\tm_dstarNetwork = new CDStarNetwork(gatewayAddress, gatewayPort, localAddress, localPort, m_duplex, VERSION, name, debug);' + +if old not in content: + print("ERROR: Could not find createDStarNetwork insertion point") + sys.exit(1) + +content = content.replace(old, new, 1) +print("createDStarNetwork() patched OK") + +with open('MMDVM-Host.cpp', 'w') as f: + f.write(content) + +print("MMDVM-Host.cpp patched successfully") diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..db077bd --- /dev/null +++ b/update.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# D-Star Site Update Script +# Pulls latest patches and rebuilds MMDVMHost +# Usage: sudo bash update.sh + +set -e + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MMDVM_SOURCE_DIR="/home/pi/MMDVM-Host" + +echo "================================================" +echo " D-Star Site Updater" +echo "================================================" + +# Pull latest repo +echo "Pulling latest from Gitea..." +cd "$REPO_DIR" +git pull + +# Pull latest MMDVMHost source +echo "Pulling latest MMDVMHost source..." +cd "$MMDVM_SOURCE_DIR" +git pull + +# Restore original files before patching +echo "Restoring originals..." +git checkout DStarNetwork.cpp DStarNetwork.h Conf.h Conf.cpp MMDVM-Host.cpp + +# Apply patches +echo "Applying patches..." +cp "$REPO_DIR/src/DStarNetwork.cpp" ./ +cp "$REPO_DIR/src/DStarNetwork.h" ./ +cp "$REPO_DIR/src/Conf.h" ./ +python3 "$REPO_DIR/src/patch_conf_cpp.py" +python3 "$REPO_DIR/src/patch_mmdvm_host_cpp.py" + +# Recompile +echo "Compiling..." +make -j$(nproc) + +# Install +echo "Installing..." +systemctl stop mmdvmhost +cp MMDVM-Host /usr/local/bin/MMDVM-Host +systemctl start mmdvmhost + +echo "" +echo "Update complete" +systemctl status mmdvmhost --no-pager