Add busy lockout to remote mode handling.
This commit is contained in:
parent
caea12741d
commit
335b56f4bd
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX
|
* Copyright (C) 2015-2019 Jonathan Naylor, G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -122,3 +122,11 @@ void CDMRControl::clock()
|
||||||
m_slot1.clock();
|
m_slot1.clock();
|
||||||
m_slot2.clock();
|
m_slot2.clock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CDMRControl::isBusy() const
|
||||||
|
{
|
||||||
|
if (m_slot1.isBusy())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return m_slot2.isBusy();
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX
|
* Copyright (C) 2015-2019 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -44,6 +44,8 @@ public:
|
||||||
|
|
||||||
void clock();
|
void clock();
|
||||||
|
|
||||||
|
bool isBusy() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned int m_colorCode;
|
unsigned int m_colorCode;
|
||||||
CModem* m_modem;
|
CModem* m_modem;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX
|
* Copyright (C) 2015-2019 Jonathan Naylor, G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -2091,3 +2091,8 @@ void CDMRSlot::insertSilence(unsigned int count)
|
||||||
n = (n + 1U) % 6U;
|
n = (n + 1U) % 6U;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CDMRSlot::isBusy() const
|
||||||
|
{
|
||||||
|
return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX
|
* Copyright (C) 2015-2019 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -58,6 +58,8 @@ public:
|
||||||
|
|
||||||
void clock();
|
void clock();
|
||||||
|
|
||||||
|
bool isBusy() const;
|
||||||
|
|
||||||
static void init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, CModem* modem, CDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter);
|
static void init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, CModem* modem, CDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX
|
* Copyright (C) 2015-2019 Jonathan Naylor, G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1222,3 +1222,7 @@ void CDStarControl::sendError()
|
||||||
writeQueueEOTRF();
|
writeQueueEOTRF();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CDStarControl::isBusy() const
|
||||||
|
{
|
||||||
|
return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2015-2019 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -46,6 +46,8 @@ public:
|
||||||
|
|
||||||
void clock();
|
void clock();
|
||||||
|
|
||||||
|
bool isBusy() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned char* m_callsign;
|
unsigned char* m_callsign;
|
||||||
unsigned char* m_gateway;
|
unsigned char* m_gateway;
|
||||||
|
|
230
MMDVMHost.cpp
230
MMDVMHost.cpp
|
@ -22,13 +22,7 @@
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
#include "StopWatch.h"
|
#include "StopWatch.h"
|
||||||
#include "Defines.h"
|
#include "Defines.h"
|
||||||
#include "DStarControl.h"
|
|
||||||
#include "DMRControl.h"
|
|
||||||
#include "YSFControl.h"
|
|
||||||
#include "P25Control.h"
|
|
||||||
#include "NXDNControl.h"
|
|
||||||
#include "POCSAGControl.h"
|
#include "POCSAGControl.h"
|
||||||
#include "RemoteControl.h"
|
|
||||||
#include "Thread.h"
|
#include "Thread.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "GitVersion.h"
|
#include "GitVersion.h"
|
||||||
|
@ -120,6 +114,11 @@ int main(int argc, char** argv)
|
||||||
CMMDVMHost::CMMDVMHost(const std::string& confFile) :
|
CMMDVMHost::CMMDVMHost(const std::string& confFile) :
|
||||||
m_conf(confFile),
|
m_conf(confFile),
|
||||||
m_modem(NULL),
|
m_modem(NULL),
|
||||||
|
m_dstar(NULL),
|
||||||
|
m_dmr(NULL),
|
||||||
|
m_ysf(NULL),
|
||||||
|
m_p25(NULL),
|
||||||
|
m_nxdn(NULL),
|
||||||
m_dstarNetwork(NULL),
|
m_dstarNetwork(NULL),
|
||||||
m_dmrNetwork(NULL),
|
m_dmrNetwork(NULL),
|
||||||
m_ysfNetwork(NULL),
|
m_ysfNetwork(NULL),
|
||||||
|
@ -159,7 +158,8 @@ m_id(0U),
|
||||||
m_cwCallsign(),
|
m_cwCallsign(),
|
||||||
m_lockFileEnabled(false),
|
m_lockFileEnabled(false),
|
||||||
m_lockFileName(),
|
m_lockFileName(),
|
||||||
m_mobileGPS(NULL)
|
m_mobileGPS(NULL),
|
||||||
|
m_remoteControl(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,7 +397,6 @@ int CMMDVMHost::run()
|
||||||
CStopWatch stopWatch;
|
CStopWatch stopWatch;
|
||||||
stopWatch.start();
|
stopWatch.start();
|
||||||
|
|
||||||
CDStarControl* dstar = NULL;
|
|
||||||
if (m_dstarEnabled) {
|
if (m_dstarEnabled) {
|
||||||
std::string module = m_conf.getDStarModule();
|
std::string module = m_conf.getDStarModule();
|
||||||
bool selfOnly = m_conf.getDStarSelfOnly();
|
bool selfOnly = m_conf.getDStarSelfOnly();
|
||||||
|
@ -422,13 +421,12 @@ int CMMDVMHost::run()
|
||||||
if (blackList.size() > 0U)
|
if (blackList.size() > 0U)
|
||||||
LogInfo(" Black List: %u", blackList.size());
|
LogInfo(" Black List: %u", blackList.size());
|
||||||
|
|
||||||
dstar = new CDStarControl(m_callsign, module, selfOnly, ackReply, ackTime, ackMessage, errorReply, blackList, m_dstarNetwork, m_display, m_timeout, m_duplex, remoteGateway, rssi);
|
m_dstar = new CDStarControl(m_callsign, module, selfOnly, ackReply, ackTime, ackMessage, errorReply, blackList, m_dstarNetwork, m_display, m_timeout, m_duplex, remoteGateway, rssi);
|
||||||
}
|
}
|
||||||
|
|
||||||
CTimer dmrBeaconIntervalTimer(1000U);
|
CTimer dmrBeaconIntervalTimer(1000U);
|
||||||
CTimer dmrBeaconDurationTimer(1000U);
|
CTimer dmrBeaconDurationTimer(1000U);
|
||||||
|
|
||||||
CDMRControl* dmr = NULL;
|
|
||||||
if (m_dmrEnabled) {
|
if (m_dmrEnabled) {
|
||||||
unsigned int id = m_conf.getDMRId();
|
unsigned int id = m_conf.getDMRId();
|
||||||
unsigned int colorCode = m_conf.getDMRColorCode();
|
unsigned int colorCode = m_conf.getDMRColorCode();
|
||||||
|
@ -491,12 +489,11 @@ int CMMDVMHost::run()
|
||||||
dmrBeaconIntervalTimer.start();
|
dmrBeaconIntervalTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
dmr = new CDMRControl(id, colorCode, callHang, selfOnly, embeddedLCOnly, dumpTAData, prefixes, blackList, whiteList, slot1TGWhiteList, slot2TGWhiteList, m_timeout, m_modem, m_dmrNetwork, m_display, m_duplex, m_dmrLookup, rssi, jitter);
|
m_dmr = new CDMRControl(id, colorCode, callHang, selfOnly, embeddedLCOnly, dumpTAData, prefixes, blackList, whiteList, slot1TGWhiteList, slot2TGWhiteList, m_timeout, m_modem, m_dmrNetwork, m_display, m_duplex, m_dmrLookup, rssi, jitter);
|
||||||
|
|
||||||
m_dmrTXTimer.setTimeout(txHang);
|
m_dmrTXTimer.setTimeout(txHang);
|
||||||
}
|
}
|
||||||
|
|
||||||
CYSFControl* ysf = NULL;
|
|
||||||
if (m_ysfEnabled) {
|
if (m_ysfEnabled) {
|
||||||
bool lowDeviation = m_conf.getFusionLowDeviation();
|
bool lowDeviation = m_conf.getFusionLowDeviation();
|
||||||
bool remoteGateway = m_conf.getFusionRemoteGateway();
|
bool remoteGateway = m_conf.getFusionRemoteGateway();
|
||||||
|
@ -516,11 +513,10 @@ int CMMDVMHost::run()
|
||||||
LogInfo(" DSQ Value: %u", sql);
|
LogInfo(" DSQ Value: %u", sql);
|
||||||
LogInfo(" Mode Hang: %us", m_ysfRFModeHang);
|
LogInfo(" Mode Hang: %us", m_ysfRFModeHang);
|
||||||
|
|
||||||
ysf = new CYSFControl(m_callsign, selfOnly, m_ysfNetwork, m_display, m_timeout, m_duplex, lowDeviation, remoteGateway, rssi);
|
m_ysf = new CYSFControl(m_callsign, selfOnly, m_ysfNetwork, m_display, m_timeout, m_duplex, lowDeviation, remoteGateway, rssi);
|
||||||
ysf->setSQL(sqlEnabled, sql);
|
m_ysf->setSQL(sqlEnabled, sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
CP25Control* p25 = NULL;
|
|
||||||
if (m_p25Enabled) {
|
if (m_p25Enabled) {
|
||||||
unsigned int id = m_conf.getP25Id();
|
unsigned int id = m_conf.getP25Id();
|
||||||
unsigned int nac = m_conf.getP25NAC();
|
unsigned int nac = m_conf.getP25NAC();
|
||||||
|
@ -537,10 +533,9 @@ int CMMDVMHost::run()
|
||||||
LogInfo(" Remote Gateway: %s", remoteGateway ? "yes" : "no");
|
LogInfo(" Remote Gateway: %s", remoteGateway ? "yes" : "no");
|
||||||
LogInfo(" Mode Hang: %us", m_p25RFModeHang);
|
LogInfo(" Mode Hang: %us", m_p25RFModeHang);
|
||||||
|
|
||||||
p25 = new CP25Control(nac, id, selfOnly, uidOverride, m_p25Network, m_display, m_timeout, m_duplex, m_dmrLookup, remoteGateway, rssi);
|
m_p25 = new CP25Control(nac, id, selfOnly, uidOverride, m_p25Network, m_display, m_timeout, m_duplex, m_dmrLookup, remoteGateway, rssi);
|
||||||
}
|
}
|
||||||
|
|
||||||
CNXDNControl* nxdn = NULL;
|
|
||||||
if (m_nxdnEnabled) {
|
if (m_nxdnEnabled) {
|
||||||
std::string lookupFile = m_conf.getNXDNIdLookupFile();
|
std::string lookupFile = m_conf.getNXDNIdLookupFile();
|
||||||
unsigned int reloadTime = m_conf.getNXDNIdLookupTime();
|
unsigned int reloadTime = m_conf.getNXDNIdLookupTime();
|
||||||
|
@ -566,7 +561,7 @@ int CMMDVMHost::run()
|
||||||
LogInfo(" Remote Gateway: %s", remoteGateway ? "yes" : "no");
|
LogInfo(" Remote Gateway: %s", remoteGateway ? "yes" : "no");
|
||||||
LogInfo(" Mode Hang: %us", m_nxdnRFModeHang);
|
LogInfo(" Mode Hang: %us", m_nxdnRFModeHang);
|
||||||
|
|
||||||
nxdn = new CNXDNControl(ran, id, selfOnly, m_nxdnNetwork, m_display, m_timeout, m_duplex, remoteGateway, m_nxdnLookup, rssi);
|
m_nxdn = new CNXDNControl(ran, id, selfOnly, m_nxdnNetwork, m_display, m_timeout, m_duplex, remoteGateway, m_nxdnLookup, rssi);
|
||||||
}
|
}
|
||||||
|
|
||||||
CTimer pocsagTimer(1000U, 30U);
|
CTimer pocsagTimer(1000U, 30U);
|
||||||
|
@ -583,7 +578,6 @@ int CMMDVMHost::run()
|
||||||
pocsagTimer.start();
|
pocsagTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
CRemoteControl* remoteControl = NULL;
|
|
||||||
bool remoteControlEnabled = m_conf.getRemoteControlEnabled();
|
bool remoteControlEnabled = m_conf.getRemoteControlEnabled();
|
||||||
if (remoteControlEnabled) {
|
if (remoteControlEnabled) {
|
||||||
unsigned int port = m_conf.getRemoteControlPort();
|
unsigned int port = m_conf.getRemoteControlPort();
|
||||||
|
@ -591,12 +585,12 @@ int CMMDVMHost::run()
|
||||||
LogInfo("Remote Control Parameters");
|
LogInfo("Remote Control Parameters");
|
||||||
LogInfo(" Port; %u", port);
|
LogInfo(" Port; %u", port);
|
||||||
|
|
||||||
remoteControl = new CRemoteControl(port);
|
m_remoteControl = new CRemoteControl(port);
|
||||||
|
|
||||||
ret = remoteControl->open();
|
ret = m_remoteControl->open();
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
delete remoteControl;
|
delete m_remoteControl;
|
||||||
remoteControl = NULL;
|
m_remoteControl = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,15 +627,15 @@ int CMMDVMHost::run()
|
||||||
bool ret;
|
bool ret;
|
||||||
|
|
||||||
len = m_modem->readDStarData(data);
|
len = m_modem->readDStarData(data);
|
||||||
if (dstar != NULL && len > 0U) {
|
if (m_star != NULL && len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
bool ret = dstar->writeModem(data, len);
|
bool ret = m_dstar->writeModem(data, len);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modeTimer.setTimeout(m_dstarRFModeHang);
|
m_modeTimer.setTimeout(m_dstarRFModeHang);
|
||||||
setMode(MODE_DSTAR);
|
setMode(MODE_DSTAR);
|
||||||
}
|
}
|
||||||
} else if (m_mode == MODE_DSTAR) {
|
} else if (m_mode == MODE_DSTAR) {
|
||||||
dstar->writeModem(data, len);
|
m_dstar->writeModem(data, len);
|
||||||
m_modeTimer.start();
|
m_modeTimer.start();
|
||||||
} else if (m_mode != MODE_LOCKOUT) {
|
} else if (m_mode != MODE_LOCKOUT) {
|
||||||
LogWarning("D-Star modem data received when in mode %u", m_mode);
|
LogWarning("D-Star modem data received when in mode %u", m_mode);
|
||||||
|
@ -649,10 +643,10 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
len = m_modem->readDMRData1(data);
|
len = m_modem->readDMRData1(data);
|
||||||
if (dmr != NULL && len > 0U) {
|
if (m_dmr != NULL && len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
if (m_duplex) {
|
if (m_duplex) {
|
||||||
bool ret = dmr->processWakeup(data);
|
bool ret = m_dmr->processWakeup(data);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
||||||
setMode(MODE_DMR);
|
setMode(MODE_DMR);
|
||||||
|
@ -661,18 +655,18 @@ int CMMDVMHost::run()
|
||||||
} else {
|
} else {
|
||||||
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
||||||
setMode(MODE_DMR);
|
setMode(MODE_DMR);
|
||||||
dmr->writeModemSlot1(data, len);
|
m_dmr->writeModemSlot1(data, len);
|
||||||
dmrBeaconDurationTimer.stop();
|
dmrBeaconDurationTimer.stop();
|
||||||
}
|
}
|
||||||
} else if (m_mode == MODE_DMR) {
|
} else if (m_mode == MODE_DMR) {
|
||||||
if (m_duplex && !m_modem->hasTX()) {
|
if (m_duplex && !m_modem->hasTX()) {
|
||||||
bool ret = dmr->processWakeup(data);
|
bool ret = m_dmr->processWakeup(data);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modem->writeDMRStart(true);
|
m_modem->writeDMRStart(true);
|
||||||
m_dmrTXTimer.start();
|
m_dmrTXTimer.start();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bool ret = dmr->writeModemSlot1(data, len);
|
bool ret = m_dmr->writeModemSlot1(data, len);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dmrBeaconDurationTimer.stop();
|
dmrBeaconDurationTimer.stop();
|
||||||
m_modeTimer.start();
|
m_modeTimer.start();
|
||||||
|
@ -686,10 +680,10 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
len = m_modem->readDMRData2(data);
|
len = m_modem->readDMRData2(data);
|
||||||
if (dmr != NULL && len > 0U) {
|
if (m_dmr != NULL && len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
if (m_duplex) {
|
if (m_duplex) {
|
||||||
bool ret = dmr->processWakeup(data);
|
bool ret = m_dmr->processWakeup(data);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
||||||
setMode(MODE_DMR);
|
setMode(MODE_DMR);
|
||||||
|
@ -698,18 +692,18 @@ int CMMDVMHost::run()
|
||||||
} else {
|
} else {
|
||||||
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
m_modeTimer.setTimeout(m_dmrRFModeHang);
|
||||||
setMode(MODE_DMR);
|
setMode(MODE_DMR);
|
||||||
dmr->writeModemSlot2(data, len);
|
m_dmr->writeModemSlot2(data, len);
|
||||||
dmrBeaconDurationTimer.stop();
|
dmrBeaconDurationTimer.stop();
|
||||||
}
|
}
|
||||||
} else if (m_mode == MODE_DMR) {
|
} else if (m_mode == MODE_DMR) {
|
||||||
if (m_duplex && !m_modem->hasTX()) {
|
if (m_duplex && !m_modem->hasTX()) {
|
||||||
bool ret = dmr->processWakeup(data);
|
bool ret = m_dmr->processWakeup(data);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modem->writeDMRStart(true);
|
m_modem->writeDMRStart(true);
|
||||||
m_dmrTXTimer.start();
|
m_dmrTXTimer.start();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bool ret = dmr->writeModemSlot2(data, len);
|
bool ret = m_dmr->writeModemSlot2(data, len);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dmrBeaconDurationTimer.stop();
|
dmrBeaconDurationTimer.stop();
|
||||||
m_modeTimer.start();
|
m_modeTimer.start();
|
||||||
|
@ -723,15 +717,15 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
len = m_modem->readYSFData(data);
|
len = m_modem->readYSFData(data);
|
||||||
if (ysf != NULL && len > 0U) {
|
if (m_ysf != NULL && len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
bool ret = ysf->writeModem(data, len);
|
bool ret = m_ysf->writeModem(data, len);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modeTimer.setTimeout(m_ysfRFModeHang);
|
m_modeTimer.setTimeout(m_ysfRFModeHang);
|
||||||
setMode(MODE_YSF);
|
setMode(MODE_YSF);
|
||||||
}
|
}
|
||||||
} else if (m_mode == MODE_YSF) {
|
} else if (m_mode == MODE_YSF) {
|
||||||
ysf->writeModem(data, len);
|
m_ysf->writeModem(data, len);
|
||||||
m_modeTimer.start();
|
m_modeTimer.start();
|
||||||
} else if (m_mode != MODE_LOCKOUT) {
|
} else if (m_mode != MODE_LOCKOUT) {
|
||||||
LogWarning("System Fusion modem data received when in mode %u", m_mode);
|
LogWarning("System Fusion modem data received when in mode %u", m_mode);
|
||||||
|
@ -739,15 +733,15 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
len = m_modem->readP25Data(data);
|
len = m_modem->readP25Data(data);
|
||||||
if (p25 != NULL && len > 0U) {
|
if (m_p25 != NULL && len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
bool ret = p25->writeModem(data, len);
|
bool ret = m_p25->writeModem(data, len);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modeTimer.setTimeout(m_p25RFModeHang);
|
m_modeTimer.setTimeout(m_p25RFModeHang);
|
||||||
setMode(MODE_P25);
|
setMode(MODE_P25);
|
||||||
}
|
}
|
||||||
} else if (m_mode == MODE_P25) {
|
} else if (m_mode == MODE_P25) {
|
||||||
p25->writeModem(data, len);
|
m_p25->writeModem(data, len);
|
||||||
m_modeTimer.start();
|
m_modeTimer.start();
|
||||||
} else if (m_mode != MODE_LOCKOUT) {
|
} else if (m_mode != MODE_LOCKOUT) {
|
||||||
LogWarning("P25 modem data received when in mode %u", m_mode);
|
LogWarning("P25 modem data received when in mode %u", m_mode);
|
||||||
|
@ -755,15 +749,15 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
len = m_modem->readNXDNData(data);
|
len = m_modem->readNXDNData(data);
|
||||||
if (nxdn != NULL && len > 0U) {
|
if (m_nxdn != NULL && len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
bool ret = nxdn->writeModem(data, len);
|
bool ret = m_nxdn->writeModem(data, len);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
m_modeTimer.setTimeout(m_nxdnRFModeHang);
|
m_modeTimer.setTimeout(m_nxdnRFModeHang);
|
||||||
setMode(MODE_NXDN);
|
setMode(MODE_NXDN);
|
||||||
}
|
}
|
||||||
} else if (m_mode == MODE_NXDN) {
|
} else if (m_mode == MODE_NXDN) {
|
||||||
nxdn->writeModem(data, len);
|
m_nxdn->writeModem(data, len);
|
||||||
m_modeTimer.start();
|
m_modeTimer.start();
|
||||||
} else if (m_mode != MODE_LOCKOUT) {
|
} else if (m_mode != MODE_LOCKOUT) {
|
||||||
LogWarning("NXDN modem data received when in mode %u", m_mode);
|
LogWarning("NXDN modem data received when in mode %u", m_mode);
|
||||||
|
@ -777,10 +771,10 @@ int CMMDVMHost::run()
|
||||||
if (m_modeTimer.isRunning() && m_modeTimer.hasExpired())
|
if (m_modeTimer.isRunning() && m_modeTimer.hasExpired())
|
||||||
setMode(MODE_IDLE);
|
setMode(MODE_IDLE);
|
||||||
|
|
||||||
if (dstar != NULL) {
|
if (m_star != NULL) {
|
||||||
ret = m_modem->hasDStarSpace();
|
ret = m_modem->hasDStarSpace();
|
||||||
if (ret) {
|
if (ret) {
|
||||||
len = dstar->readModem(data);
|
len = m_dstar->readModem(data);
|
||||||
if (len > 0U) {
|
if (len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
m_modeTimer.setTimeout(m_dstarNetModeHang);
|
m_modeTimer.setTimeout(m_dstarNetModeHang);
|
||||||
|
@ -796,10 +790,10 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dmr != NULL) {
|
if (m_dmr != NULL) {
|
||||||
ret = m_modem->hasDMRSpace1();
|
ret = m_modem->hasDMRSpace1();
|
||||||
if (ret) {
|
if (ret) {
|
||||||
len = dmr->readModemSlot1(data);
|
len = m_dmr->readModemSlot1(data);
|
||||||
if (len > 0U) {
|
if (len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
m_modeTimer.setTimeout(m_dmrNetModeHang);
|
m_modeTimer.setTimeout(m_dmrNetModeHang);
|
||||||
|
@ -821,7 +815,7 @@ int CMMDVMHost::run()
|
||||||
|
|
||||||
ret = m_modem->hasDMRSpace2();
|
ret = m_modem->hasDMRSpace2();
|
||||||
if (ret) {
|
if (ret) {
|
||||||
len = dmr->readModemSlot2(data);
|
len = m_dmr->readModemSlot2(data);
|
||||||
if (len > 0U) {
|
if (len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
m_modeTimer.setTimeout(m_dmrNetModeHang);
|
m_modeTimer.setTimeout(m_dmrNetModeHang);
|
||||||
|
@ -842,10 +836,10 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ysf != NULL) {
|
if (m_ysf != NULL) {
|
||||||
ret = m_modem->hasYSFSpace();
|
ret = m_modem->hasYSFSpace();
|
||||||
if (ret) {
|
if (ret) {
|
||||||
len = ysf->readModem(data);
|
len = m_ysf->readModem(data);
|
||||||
if (len > 0U) {
|
if (len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
m_modeTimer.setTimeout(m_ysfNetModeHang);
|
m_modeTimer.setTimeout(m_ysfNetModeHang);
|
||||||
|
@ -861,10 +855,10 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p25 != NULL) {
|
if (m_p25 != NULL) {
|
||||||
ret = m_modem->hasP25Space();
|
ret = m_modem->hasP25Space();
|
||||||
if (ret) {
|
if (ret) {
|
||||||
len = p25->readModem(data);
|
len = m_p25->readModem(data);
|
||||||
if (len > 0U) {
|
if (len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
m_modeTimer.setTimeout(m_p25NetModeHang);
|
m_modeTimer.setTimeout(m_p25NetModeHang);
|
||||||
|
@ -880,10 +874,10 @@ int CMMDVMHost::run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nxdn != NULL) {
|
if (m_nxdn != NULL) {
|
||||||
ret = m_modem->hasNXDNSpace();
|
ret = m_modem->hasNXDNSpace();
|
||||||
if (ret) {
|
if (ret) {
|
||||||
len = nxdn->readModem(data);
|
len = m_nxdn->readModem(data);
|
||||||
if (len > 0U) {
|
if (len > 0U) {
|
||||||
if (m_mode == MODE_IDLE) {
|
if (m_mode == MODE_IDLE) {
|
||||||
m_modeTimer.setTimeout(m_nxdnNetModeHang);
|
m_modeTimer.setTimeout(m_nxdnNetModeHang);
|
||||||
|
@ -926,34 +920,7 @@ int CMMDVMHost::run()
|
||||||
m_modem->writeTransparentData(data, len);
|
m_modem->writeTransparentData(data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remoteControl != NULL) {
|
remoteControl();
|
||||||
REMOTE_COMMAND command = remoteControl->getCommand();
|
|
||||||
switch(command) {
|
|
||||||
case RCD_MODE_IDLE:
|
|
||||||
setMode(MODE_IDLE);
|
|
||||||
break;
|
|
||||||
case RCD_MODE_LOCKOUT:
|
|
||||||
setMode(MODE_LOCKOUT);
|
|
||||||
break;
|
|
||||||
case RCD_MODE_DSTAR:
|
|
||||||
setMode(MODE_DSTAR);
|
|
||||||
break;
|
|
||||||
case RCD_MODE_DMR:
|
|
||||||
setMode(MODE_DMR);
|
|
||||||
break;
|
|
||||||
case RCD_MODE_YSF:
|
|
||||||
setMode(MODE_YSF);
|
|
||||||
break;
|
|
||||||
case RCD_MODE_P25:
|
|
||||||
setMode(MODE_P25);
|
|
||||||
break;
|
|
||||||
case RCD_MODE_NXDN:
|
|
||||||
setMode(MODE_NXDN);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int ms = stopWatch.elapsed();
|
unsigned int ms = stopWatch.elapsed();
|
||||||
stopWatch.start();
|
stopWatch.start();
|
||||||
|
@ -963,16 +930,16 @@ int CMMDVMHost::run()
|
||||||
m_modem->clock(ms);
|
m_modem->clock(ms);
|
||||||
m_modeTimer.clock(ms);
|
m_modeTimer.clock(ms);
|
||||||
|
|
||||||
if (dstar != NULL)
|
if (m_dstar != NULL)
|
||||||
dstar->clock();
|
m_dstar->clock();
|
||||||
if (dmr != NULL)
|
if (m_dmr != NULL)
|
||||||
dmr->clock();
|
m_dmr->clock();
|
||||||
if (ysf != NULL)
|
if (m_ysf != NULL)
|
||||||
ysf->clock(ms);
|
m_ysf->clock(ms);
|
||||||
if (p25 != NULL)
|
if (m_p25 != NULL)
|
||||||
p25->clock(ms);
|
m_p25->clock(ms);
|
||||||
if (nxdn != NULL)
|
if (m_nxdn != NULL)
|
||||||
nxdn->clock(ms);
|
m_nxdn->clock(ms);
|
||||||
if (pocsag != NULL)
|
if (pocsag != NULL)
|
||||||
pocsag->clock(ms);
|
pocsag->clock(ms);
|
||||||
|
|
||||||
|
@ -1098,16 +1065,16 @@ int CMMDVMHost::run()
|
||||||
delete transparentSocket;
|
delete transparentSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remoteControl != NULL) {
|
if (m_remoteControl != NULL) {
|
||||||
remoteControl->close();
|
m_remoteControl->close();
|
||||||
delete remoteControl;
|
delete m_remoteControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete dstar;
|
delete m_dstar;
|
||||||
delete dmr;
|
delete m_dmr;
|
||||||
delete ysf;
|
delete m_ysf;
|
||||||
delete p25;
|
delete m_p25;
|
||||||
delete nxdn;
|
delete m_nxdn;
|
||||||
delete pocsag;
|
delete pocsag;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1690,3 +1657,62 @@ void CMMDVMHost::removeLockFile() const
|
||||||
::remove(m_lockFileName.c_str());
|
::remove(m_lockFileName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CMMDVMHost::isBusy() const
|
||||||
|
{
|
||||||
|
if (m_dstar != NULL && m_dstar->isBusy())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_dmr != NULL && m_dmr->isBusy())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_ysf != NULL && m_ysf->isBusy())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_p25 != NULL && m_p25->isBusy())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_nxdn != NULL && m_nxdn->isBusy())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMMDVMHost::remoteControl()
|
||||||
|
{
|
||||||
|
if (m_remoteControl != NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
REMOTE_COMMAND command = m_remoteControl->getCommand();
|
||||||
|
switch(command) {
|
||||||
|
case RCD_MODE_IDLE:
|
||||||
|
if (!isBusy())
|
||||||
|
setMode(MODE_IDLE);
|
||||||
|
break;
|
||||||
|
case RCD_MODE_LOCKOUT:
|
||||||
|
if (!isBusy())
|
||||||
|
setMode(MODE_LOCKOUT);
|
||||||
|
break;
|
||||||
|
case RCD_MODE_DSTAR:
|
||||||
|
if (!isBusy())
|
||||||
|
setMode(MODE_DSTAR);
|
||||||
|
break;
|
||||||
|
case RCD_MODE_DMR:
|
||||||
|
if (!isBusy())
|
||||||
|
setMode(MODE_DMR);
|
||||||
|
break;
|
||||||
|
case RCD_MODE_YSF:
|
||||||
|
if (!isBusy())
|
||||||
|
setMode(MODE_YSF);
|
||||||
|
break;
|
||||||
|
case RCD_MODE_P25:
|
||||||
|
if (!isBusy())
|
||||||
|
setMode(MODE_P25);
|
||||||
|
break;
|
||||||
|
case RCD_MODE_NXDN:
|
||||||
|
if (!isBusy())
|
||||||
|
setMode(MODE_NXDN);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
17
MMDVMHost.h
17
MMDVMHost.h
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX
|
* Copyright (C) 2015-2019 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,9 +19,15 @@
|
||||||
#if !defined(MMDVMHOST_H)
|
#if !defined(MMDVMHOST_H)
|
||||||
#define MMDVMHOST_H
|
#define MMDVMHOST_H
|
||||||
|
|
||||||
|
#include "RemoteControl.h"
|
||||||
#include "POCSAGNetwork.h"
|
#include "POCSAGNetwork.h"
|
||||||
#include "DStarNetwork.h"
|
#include "DStarNetwork.h"
|
||||||
#include "NXDNNetwork.h"
|
#include "NXDNNetwork.h"
|
||||||
|
#include "DStarControl.h"
|
||||||
|
#include "DMRControl.h"
|
||||||
|
#include "YSFControl.h"
|
||||||
|
#include "P25Control.h"
|
||||||
|
#include "NXDNControl.h"
|
||||||
#include "NXDNLookup.h"
|
#include "NXDNLookup.h"
|
||||||
#include "YSFNetwork.h"
|
#include "YSFNetwork.h"
|
||||||
#include "P25Network.h"
|
#include "P25Network.h"
|
||||||
|
@ -49,6 +55,11 @@ public:
|
||||||
private:
|
private:
|
||||||
CConf m_conf;
|
CConf m_conf;
|
||||||
CModem* m_modem;
|
CModem* m_modem;
|
||||||
|
CDStarControl* m_dstar;
|
||||||
|
CDMRControl* m_dmr;
|
||||||
|
CYSFControl* m_ysf;
|
||||||
|
CP25Control* m_p25;
|
||||||
|
CNXDNControl* m_nxdn;
|
||||||
CDStarNetwork* m_dstarNetwork;
|
CDStarNetwork* m_dstarNetwork;
|
||||||
CDMRNetwork* m_dmrNetwork;
|
CDMRNetwork* m_dmrNetwork;
|
||||||
CYSFNetwork* m_ysfNetwork;
|
CYSFNetwork* m_ysfNetwork;
|
||||||
|
@ -89,6 +100,7 @@ private:
|
||||||
bool m_lockFileEnabled;
|
bool m_lockFileEnabled;
|
||||||
std::string m_lockFileName;
|
std::string m_lockFileName;
|
||||||
CMobileGPS* m_mobileGPS;
|
CMobileGPS* m_mobileGPS;
|
||||||
|
CRemoteControl* m_remoteControl;
|
||||||
|
|
||||||
void readParams();
|
void readParams();
|
||||||
bool createModem();
|
bool createModem();
|
||||||
|
@ -99,6 +111,9 @@ private:
|
||||||
bool createNXDNNetwork();
|
bool createNXDNNetwork();
|
||||||
bool createPOCSAGNetwork();
|
bool createPOCSAGNetwork();
|
||||||
|
|
||||||
|
bool isBusy() const;
|
||||||
|
void remoteControl();
|
||||||
|
|
||||||
void setMode(unsigned char mode);
|
void setMode(unsigned char mode);
|
||||||
|
|
||||||
void createLockFile(const char* mode) const;
|
void createLockFile(const char* mode) const;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX
|
* Copyright (C) 2015-2019 Jonathan Naylor, G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1081,3 +1081,8 @@ void CNXDNControl::closeFile()
|
||||||
m_fp = NULL;
|
m_fp = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CNXDNControl::isBusy() const
|
||||||
|
{
|
||||||
|
return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX
|
* Copyright (C) 2015-2019 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -45,6 +45,8 @@ public:
|
||||||
|
|
||||||
void clock(unsigned int ms);
|
void clock(unsigned int ms);
|
||||||
|
|
||||||
|
bool isBusy() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned int m_ran;
|
unsigned int m_ran;
|
||||||
unsigned int m_id;
|
unsigned int m_id;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX
|
* Copyright (C) 2016-2019 by Jonathan Naylor G4KLX
|
||||||
* Copyright (C) 2018 by Bryan Biedenkapp <gatekeep@gmail.com>
|
* Copyright (C) 2018 by Bryan Biedenkapp <gatekeep@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
@ -1155,3 +1155,8 @@ void CP25Control::closeFile()
|
||||||
m_fp = NULL;
|
m_fp = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CP25Control::isBusy() const
|
||||||
|
{
|
||||||
|
return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX
|
* Copyright (C) 2016-2019 by Jonathan Naylor G4KLX
|
||||||
* Copyright (C) 2018 by Bryan Biedenkapp <gatekeep@gmail.com>
|
* Copyright (C) 2018 by Bryan Biedenkapp <gatekeep@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
@ -46,6 +46,8 @@ public:
|
||||||
|
|
||||||
void clock(unsigned int ms);
|
void clock(unsigned int ms);
|
||||||
|
|
||||||
|
bool isBusy() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned int m_nac;
|
unsigned int m_nac;
|
||||||
unsigned int m_id;
|
unsigned int m_id;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX
|
* Copyright (C) 2015-2019 Jonathan Naylor, G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1318,3 +1318,8 @@ void CYSFControl::processNetCallsigns(const unsigned char* data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CYSFControl::isBusy() const
|
||||||
|
{
|
||||||
|
return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2015-2019 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -46,6 +46,8 @@ public:
|
||||||
|
|
||||||
void clock(unsigned int ms);
|
void clock(unsigned int ms);
|
||||||
|
|
||||||
|
bool isBusy() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned char* m_callsign;
|
unsigned char* m_callsign;
|
||||||
unsigned char* m_selfCallsign;
|
unsigned char* m_selfCallsign;
|
||||||
|
|
Loading…
Reference in a new issue