756 lines
24 KiB
C++
Executable File
756 lines
24 KiB
C++
Executable File
/**
|
|
* CombatNetRadioHandler - reads CnrChannel and CnrConfig data from the database and publishes the data to dds,
|
|
* reacts to CnrConfig related Function topics
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* Created by Hannes Baeuerle, SLSE, 2017-08-15
|
|
*/
|
|
|
|
#include "cnr_handler.h"
|
|
#include "avm_dts.h"
|
|
#include "dds_handler.h"
|
|
#include "exercise_context.h"
|
|
#include "frequency_planning_handler.h"
|
|
#include "active_exercises.h"
|
|
#include "av_helper.h"
|
|
#include "settings.h"
|
|
|
|
#include <InfrastructureManagement/ism_dtc.h>
|
|
#include <LtsUtilsCore/json_object.h>
|
|
#include <ngldb/dbtransaction.h>
|
|
#include <QStringList>
|
|
#include <algorithm>
|
|
|
|
namespace AudioVideoManagement
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
bool isRadioStation(const InfrastructureManagement::MainStructureElement &mse)
|
|
{
|
|
return mse.structureElementTypeId() == InfrastructureManagement::StructureElementType::RadioStation;
|
|
}
|
|
|
|
QString hostnameForRadioStation(const InfrastructureManagement::MainStructureElement &mse)
|
|
{
|
|
LtsUtils::JsonObject properties = LtsUtils::JsonObject::fromJson(mse.properties());
|
|
return properties.valueOr("hostname", QString());
|
|
}
|
|
|
|
template<class T>
|
|
dds::core::optional<T> toDdsOptional(const QVariant &variant)
|
|
{
|
|
dds::core::optional<T> optional;
|
|
if (!variant.isNull())
|
|
optional = variant.value<T>();
|
|
return optional;
|
|
}
|
|
|
|
CnrChannel toDds(const Database::AvmCnrChannel &channel)
|
|
{
|
|
return CnrChannel(channel.id,
|
|
channel.name.toStdString(),
|
|
channel.ssrc,
|
|
channel.addacChannel,
|
|
channel.pttControl,
|
|
static_cast<FrequencyBand::inner_enum>(channel.frequencyBand));
|
|
}
|
|
|
|
ModifyCnrConfigRequest toRequest(const Database::AvmCnrConfig &dbData, const boost::optional<int32_t> &requestId = boost::none)
|
|
{
|
|
return ModifyCnrConfigRequest(requestId.get_value_or(dbData.id),
|
|
dbData.cnrChannelId,
|
|
toDdsOptional<int>(dbData.exerciseId),
|
|
dbData.radioCircuit.toStdString(),
|
|
dbData.recording,
|
|
toDdsOptional<int>(dbData.unitId),
|
|
dbData.partyId,
|
|
dbData.callSign.toStdString());
|
|
}
|
|
|
|
ModifyCnrConfigRequest toRequest(const CnrConfig &cnrConfig)
|
|
{
|
|
return ModifyCnrConfigRequest(cnrConfig.id(),
|
|
cnrConfig.cnrChannelId(),
|
|
cnrConfig.exerciseId(),
|
|
cnrConfig.radioCircuit(),
|
|
cnrConfig.recording(),
|
|
cnrConfig.unitId(),
|
|
cnrConfig.partyId(),
|
|
cnrConfig.callSign());
|
|
}
|
|
|
|
Database::AvmCnrConfig toDb(const ModifyCnrConfigRequest &config)
|
|
{
|
|
Database::AvmCnrConfig result;
|
|
|
|
result.id = config.id();
|
|
result.cnrChannelId = config.cnrChannelId();
|
|
result.radioCircuit = QString::fromStdString(config.radioCircuit());
|
|
result.callSign = QString::fromStdString(config.callSign());
|
|
result.partyId = config.partyId();
|
|
if (config.unitId().is_set())
|
|
result.unitId = config.unitId().get();
|
|
result.recording = config.recording();
|
|
if (config.exerciseId().is_set())
|
|
result.exerciseId = config.exerciseId().get();
|
|
|
|
return result;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
/**
|
|
* Internal helper class responsible for inserting, updating and removing device_config entries.
|
|
*/
|
|
class CnrHandler::DeviceConfig
|
|
{
|
|
public:
|
|
explicit DeviceConfig(CnrHandler *handler, DatabaseInterface *db, LOGGER_T logger)
|
|
: m_handler(handler)
|
|
, m_db(db)
|
|
, m_logger(logger)
|
|
{
|
|
Q_ASSERT(handler);
|
|
Q_ASSERT(db);
|
|
}
|
|
|
|
void create(const CnrConfig &config)
|
|
{
|
|
Q_ASSERT(!m_cache.contains(config.id()));
|
|
|
|
Database::AvmDeviceConfig deviceConfig = m_handler->toDeviceConfig(config);
|
|
bool status = m_db->insert(deviceConfig);
|
|
if (!status)
|
|
ERROR_LOG(m_logger) << "Error while inserting device config";
|
|
|
|
m_cache.insert(config.id(), deviceConfig);
|
|
emit m_handler->avDbReloadNeeded();
|
|
}
|
|
|
|
void update(const CnrConfig &config)
|
|
{
|
|
Q_ASSERT(m_cache.contains(config.id()));
|
|
|
|
Database::AvmDeviceConfig oldDeviceConfig = m_cache.value(config.id());
|
|
Database::AvmDeviceConfig newDeviceConfig = m_handler->toDeviceConfig(config);
|
|
|
|
newDeviceConfig.id = oldDeviceConfig.id;
|
|
bool status = m_db->update(newDeviceConfig);
|
|
if (!status)
|
|
ERROR_LOG(m_logger) << "Error while updating device config";
|
|
|
|
m_cache[config.id()] = newDeviceConfig;
|
|
emit m_handler->avDbReloadNeeded();
|
|
}
|
|
|
|
void createOrUpdate(const CnrConfig &config)
|
|
{
|
|
if (m_cache.contains(config.id()))
|
|
update(config);
|
|
else
|
|
create(config);
|
|
}
|
|
|
|
void tryRemove(int32_t configId)
|
|
{
|
|
if (m_cache.contains(configId))
|
|
remove(configId);
|
|
}
|
|
|
|
void remove(int32_t configId)
|
|
{
|
|
Q_ASSERT(m_cache.contains(configId));
|
|
|
|
bool status = m_db->removeAvmDeviceConfig(m_cache.value(configId).id);
|
|
if (!status)
|
|
ERROR_LOG(m_logger) << "Error while removing device config";
|
|
|
|
m_cache.remove(configId);
|
|
emit m_handler->avDbReloadNeeded();
|
|
}
|
|
|
|
private:
|
|
CnrHandler *m_handler;
|
|
DatabaseInterface *m_db;
|
|
QHash<int32_t, Database::AvmDeviceConfig> m_cache; //!< cnrConfig-Id -> AvmDeviceConfig
|
|
LOGGER_T m_logger;
|
|
};
|
|
|
|
|
|
CnrHandler::CnrHandler(Interface::Dts *dts, Interface::Fts *fts, InfrastructureManagement::Interface::Dtc *ismDtc, FrequencyPlanningHandler *frequencyPlanningHandler, DatabaseInterface *db, ActiveExercises *activeExercises)
|
|
: ModifyCnrConfigRequestHandler(nullptr)
|
|
, RemoveCnrConfigRequestHandler(nullptr)
|
|
, m_dts(dts)
|
|
, m_fts(fts)
|
|
, m_frequencyPlanningHandler(frequencyPlanningHandler)
|
|
, m_db(db)
|
|
, m_activeExercises(activeExercises)
|
|
, m_logger(getLogger("CombatNetRadioHandler"))
|
|
, m_deviceConfig(new DeviceConfig(this, db, m_logger))
|
|
{
|
|
Q_ASSERT(dts);
|
|
Q_ASSERT(fts);
|
|
Q_ASSERT(ismDtc);
|
|
// frequencyPlanningHandler may be null
|
|
Q_ASSERT(db);
|
|
Q_ASSERT(activeExercises);
|
|
|
|
m_fts->setModifyCnrConfigRequestHandler(this);
|
|
m_fts->setRemoveCnrConfigRequestHandler(this);
|
|
|
|
connect(m_activeExercises, &ActiveExercises::contextActivated, this, &CnrHandler::onContextActivated);
|
|
connect(m_activeExercises, &ActiveExercises::contextDeactivated, this, &CnrHandler::onContextDeactivated);
|
|
if (m_frequencyPlanningHandler)
|
|
connect(m_frequencyPlanningHandler, &FrequencyPlanningHandler::planningChanged, this, &CnrHandler::onFrequencyPlanningChanged);
|
|
|
|
connect(m_db, &DatabaseInterface::notifyDeleteAvmCnrConfig, this, &CnrHandler::onDbDeleteAvmCnrConfig);
|
|
|
|
connect(ismDtc, &InfrastructureManagement::Interface::Dtc::mainStructureElementChanged, this, &CnrHandler::onMainStructureElementChanged);
|
|
// No need to query the DTC here as it is not yet activated.
|
|
// But explicitly check that the DTC (i.e. the contained DataReader) is not enabled yet.
|
|
|
|
//Q_ASSERT(!ismDtc->isMainStructureElementEnabled());
|
|
}
|
|
|
|
CnrHandler::~CnrHandler()
|
|
{
|
|
m_fts->setModifyCnrConfigRequestHandler(nullptr);
|
|
m_fts->setRemoveCnrConfigRequestHandler(nullptr);
|
|
}
|
|
|
|
bool CnrHandler::init()
|
|
{
|
|
using AvmCnrChannel = Database::AvmCnrChannel;
|
|
using AvmCnrConfig = Database::AvmCnrConfig;
|
|
|
|
m_channels.clear();
|
|
|
|
if (!m_db->select(m_channels))
|
|
{
|
|
ERROR_LOG(m_logger) << "Error while selecting cnr channels from db";
|
|
return false;
|
|
}
|
|
|
|
for (const AvmCnrChannel ¤tChannel : qAsConst(m_channels))
|
|
m_dts->publish(::AudioVideoManagement::toDds(currentChannel));
|
|
|
|
// Don't use select with callback here as addConfig() uses the DB, too.
|
|
QVector<AvmCnrConfig> cnrConfigs;
|
|
if (!m_db->select(cnrConfigs))
|
|
{
|
|
ERROR_LOG(m_logger) << "Error while selecting CNR configs from db";
|
|
return false;
|
|
}
|
|
|
|
for (const AvmCnrConfig ¤tConfig : qAsConst(cnrConfigs))
|
|
{
|
|
auto reply = createCnrConfig(toRequest(currentConfig, 0), currentConfig.id);
|
|
if (reply.error())
|
|
{
|
|
ERROR_LOG((m_logger)) << QStringLiteral("Invalid CnrConfig in DB with id %1 => removing; reason:").arg(currentConfig.id) << reply();
|
|
m_db->removeAvmCnrConfig(currentConfig.id);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CnrHandler::hasConfig(int32_t configId) const
|
|
{
|
|
return m_cache.contains(configId);
|
|
}
|
|
|
|
const CnrConfig &CnrHandler::config(int32_t configId) const
|
|
{
|
|
auto found = m_cache.constFind(configId);
|
|
Q_ASSERT(found != m_cache.constEnd());
|
|
return *found;
|
|
}
|
|
|
|
void CnrHandler::onRemoveCnrConfigRequest(const rti::core::SampleIdentity &cookie,
|
|
const Core::RemoveElementRequest &request)
|
|
{
|
|
auto ret = removeCnrConfig(request);
|
|
if (ret.error())
|
|
WARNING_LOG(m_logger) << ret();
|
|
m_fts->removeCnrConfigReply(cookie, ret);
|
|
}
|
|
|
|
void CnrHandler::onModifyCnrConfigRequest(const rti::core::SampleIdentity &cookie,
|
|
const ModifyCnrConfigRequest &request)
|
|
{
|
|
auto ret = (request.id() < 1 ? createCnrConfig(request) : modifyCnrConfig(request));
|
|
if (ret.error())
|
|
WARNING_LOG(m_logger) << ret();
|
|
m_fts->modifyCnrConfigReply(cookie, ret);
|
|
}
|
|
|
|
void CnrHandler::onRadioStationAdded(int id, const QString &name, const QString &hostname)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Adding radio station with id " << id << " (" << name << ") and hostname '" << hostname << "'";
|
|
auto it = m_cnrRadioStations.find(id);
|
|
if (it != m_cnrRadioStations.end() && it.value() == hostname)
|
|
return;
|
|
if (hostname.isEmpty())
|
|
{
|
|
WARNING_LOG(m_logger) << "Radio station with id " << id << " (" << name << ") has an empty hostname - ignoring!";
|
|
return;
|
|
}
|
|
m_cnrRadioStations[id] = hostname;
|
|
updateDeviceConfig();
|
|
}
|
|
|
|
void CnrHandler::onRadioStationRemoved(int id)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Removing radio station with id " << id;
|
|
auto it = m_cnrRadioStations.find(id);
|
|
if (it == m_cnrRadioStations.end())
|
|
return;
|
|
m_cnrRadioStations.erase(it);
|
|
updateDeviceConfig();
|
|
}
|
|
|
|
void CnrHandler::onContextActivated(const ExerciseContext &context)
|
|
{
|
|
Q_UNUSED(context);
|
|
reevalutate();
|
|
}
|
|
|
|
void CnrHandler::onContextDeactivated(const ExerciseContext &context)
|
|
{
|
|
Q_UNUSED(context);
|
|
reevalutate();
|
|
}
|
|
|
|
void CnrHandler::onFrequencyPlanningChanged()
|
|
{
|
|
reevalutate();
|
|
}
|
|
|
|
/**
|
|
* Will be called by the DB after an AvmCnrConfig entry was removed.
|
|
* This can happen when
|
|
* - we have just removed the entry ourselves
|
|
* - it was implicitly removed by the DB as a result of a cascading delete
|
|
* - someone removed the entry by hand
|
|
*/
|
|
void CnrHandler::onDbDeleteAvmCnrConfig(const QVariant &id)
|
|
{
|
|
bool couldConvert = false;
|
|
int configId = id.toInt(&couldConvert);
|
|
Q_ASSERT(couldConvert);
|
|
Q_ASSERT(configId > 0);
|
|
|
|
if (!m_cache.contains(configId))
|
|
return; // the notification was a result of our own removal of the entry
|
|
|
|
DEBUG_LOG(m_logger) << "DB deleted AvmCnrConfig with id: " << configId;
|
|
|
|
removeCnrConfig(Core::RemoveElementRequest(configId));
|
|
}
|
|
|
|
void CnrHandler::onMainStructureElementChanged(const InfrastructureManagement::MainStructureElement &data, DataModification modType)
|
|
{
|
|
if (isRadioStation(data))
|
|
onRadioStationChanged(data, modType);
|
|
}
|
|
|
|
void CnrHandler::onRadioStationChanged(const InfrastructureManagement::MainStructureElement &data, DataModification modType)
|
|
{
|
|
switch (modType)
|
|
{
|
|
case DataModification::eAdded:
|
|
case DataModification::eModified:
|
|
onRadioStationAdded(data.id(), QString::fromStdString(data.name()), hostnameForRadioStation(data));
|
|
break;
|
|
case DataModification::eRemoved:
|
|
onRadioStationRemoved(data.id());
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reevalutate which CnrConfigs are active.
|
|
*/
|
|
void CnrHandler::reevalutate()
|
|
{
|
|
DBTransaction trans(m_db->database());
|
|
|
|
// Work on a copy here as the loop might modify m_cache.
|
|
const QList<CnrConfig> cacheCopy = m_cache.values();
|
|
for (const CnrConfig &cnrConfig : cacheCopy)
|
|
{
|
|
auto reply = modifyCnrConfig(toRequest(cnrConfig));
|
|
if (reply.error())
|
|
ERROR_LOG(m_logger) << reply();
|
|
}
|
|
|
|
trans.commit();
|
|
}
|
|
|
|
/**
|
|
* Update the DeviceConfig-entries for the currently *active* CnrConfigs.
|
|
*/
|
|
void CnrHandler::updateDeviceConfig()
|
|
{
|
|
DBTransaction trans(m_db->database());
|
|
|
|
for (const auto &config : qAsConst(m_cache))
|
|
if (config.isActive())
|
|
m_deviceConfig->update(config);
|
|
|
|
trans.commit();
|
|
}
|
|
|
|
CnrConfig CnrHandler::toDds(const Database::AvmCnrConfig &config) const
|
|
{
|
|
CnrConfig cnrConfig(config.id,
|
|
config.cnrChannelId,
|
|
toDdsOptional<int>(config.exerciseId),
|
|
config.radioCircuit.toStdString(),
|
|
config.partyId,
|
|
toDdsOptional<int>(config.unitId),
|
|
config.recording,
|
|
config.callSign.toStdString(),
|
|
false);
|
|
|
|
if (needDeviceConfig(cnrConfig))
|
|
cnrConfig.isActive(true);
|
|
|
|
return cnrConfig;
|
|
}
|
|
|
|
Database::AvmDeviceConfig CnrHandler::toDeviceConfig(const CnrConfig &cnrConfig) const
|
|
{
|
|
static const QString recordingServer = QStringLiteral("EXCAVS01");
|
|
|
|
Database::AvmDeviceConfig deviceConfig;
|
|
const Database::AvmCnrChannel channel = channelById(cnrConfig.cnrChannelId());
|
|
deviceConfig.id = 0;
|
|
deviceConfig.context = exerciseList(cnrConfig);
|
|
deviceConfig.type = channel.addacChannel == 0 ? QStringLiteral("CNR_GUEZ_RT_CHANNEL") : QStringLiteral("ADDAC_RT_CHANNEL");
|
|
deviceConfig.name = QString::fromStdString(cnrConfig.radioCircuit());
|
|
deviceConfig.controller = stationList(channel.addacChannel);
|
|
deviceConfig.definition = QStringLiteral("mode=1;ssrc=%1").arg(channel.ssrc);
|
|
//for UAE we need pttc controll to!
|
|
#ifndef GUEZ_MODE
|
|
if (channel.addacChannel)
|
|
deviceConfig.definition += QStringLiteral(";pttcontrol=true");
|
|
#endif
|
|
if (channel.addacChannel)
|
|
deviceConfig.definition += QStringLiteral(";channel=%1").arg(normalizedAddacChannel(channel.addacChannel));
|
|
if (cnrConfig.recording())
|
|
deviceConfig.definition.append(QStringLiteral(";recording=ECRS@%1").arg(recordingServer));
|
|
deviceConfig.link = 0;
|
|
|
|
return deviceConfig;
|
|
}
|
|
|
|
/**
|
|
* request can be either a create- or a modify-requst.
|
|
*/
|
|
Core::FunctionReplyObject CnrHandler::validateRequest(const ModifyCnrConfigRequest &request)
|
|
{
|
|
Core::FunctionReplyObject reply;
|
|
|
|
if (request.id() > 0)
|
|
{
|
|
reply.setId(request.id());
|
|
if (!m_cache.contains(request.id()))
|
|
return reply.setStr(tr_lts("dds", "Could not modify CNR config because id %1 is unknown")).arg(request.id());
|
|
}
|
|
|
|
if (!isValidChannel(request.cnrChannelId()))
|
|
return reply.setStr(tr_lts("dds", "Could not create/modify CNR config because the channel with id %1 is invalid")).arg(request.cnrChannelId());
|
|
|
|
if (!isStreamNameValid(request.radioCircuit()))
|
|
return reply.setStr(tr_lts("dds", "Could not create/modify CNR config because the circuit name is invalid"));
|
|
|
|
if (!isNameUnique(request))
|
|
return reply.setStr(tr_lts("dds", "Could not create/modify CNR config because the circuit name is already in use"));
|
|
|
|
return reply;
|
|
}
|
|
|
|
/**
|
|
* Helper function for isFreeChannel() and isNameUnique().
|
|
*/
|
|
bool CnrHandler::checkConstraint(const std::function<bool (const CnrConfig &)> &constraint, const boost::optional<int> &exerciseId, const boost::optional<int32_t> &excludeId) const
|
|
{
|
|
for (const CnrConfig &config : qAsConst(m_cache))
|
|
{
|
|
if (excludeId == config.id())
|
|
continue;
|
|
|
|
// If both configs are local the constraint must hold if they are in the same exercise
|
|
// or when both exercises are currently active.
|
|
// If one is global and one local the constraint must always hold if the local exercise is active
|
|
// Otherwise the constraint must always hold
|
|
bool bothLocal = config.exerciseId().is_set() && exerciseId.is_initialized();
|
|
if (bothLocal)
|
|
{
|
|
bool sameExercise = config.exerciseId().get() == *exerciseId;
|
|
bool bothActive = isActiveExercise(config.exerciseId().get()) && isActiveExercise(*exerciseId);
|
|
|
|
if (!sameExercise && !bothActive)
|
|
continue;
|
|
}
|
|
else if(config.exerciseId().is_set() && !exerciseId.is_initialized())
|
|
{
|
|
if(!isActiveExercise(config.exerciseId().get()))
|
|
continue;
|
|
}
|
|
else if(exerciseId.is_initialized() && !config.exerciseId().is_set())
|
|
{
|
|
if(!isActiveExercise(*exerciseId))
|
|
continue;
|
|
}
|
|
|
|
if (!constraint(config))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CnrHandler::isValidChannel(int64_t channelId) const
|
|
{
|
|
auto withId = [channelId] (const Database::AvmCnrChannel &channel) { return channel.id == channelId; };
|
|
auto found = std::find_if(m_channels.cbegin(), m_channels.cend(), withId);
|
|
return found != m_channels.cend();
|
|
}
|
|
|
|
bool CnrHandler::isFreeChannel(const ModifyCnrConfigRequest &request) const
|
|
{
|
|
boost::optional<int> exerciseId = toBoost(request.exerciseId());
|
|
|
|
if (request.id() <= 0)
|
|
return isFreeChannel(request.cnrChannelId(), exerciseId);
|
|
return isFreeChannel(request.cnrChannelId(), exerciseId, request.id());
|
|
}
|
|
|
|
bool CnrHandler::isFreeChannel(int32_t channelId, const boost::optional<int> &exerciseId, const boost::optional<int32_t> &excludeId) const
|
|
{
|
|
auto constraint = [&channelId] (const CnrConfig &config) { return config.cnrChannelId() != channelId; };
|
|
return checkConstraint(constraint, exerciseId, excludeId);
|
|
}
|
|
|
|
bool CnrHandler::isNameUnique(const ModifyCnrConfigRequest &request)
|
|
{
|
|
boost::optional<int> exerciseId = toBoost(request.exerciseId());
|
|
|
|
if (request.id() <= 0)
|
|
return isNameUnique(request.radioCircuit(), exerciseId);
|
|
return isNameUnique(request.radioCircuit(), exerciseId, request.id());
|
|
}
|
|
|
|
bool CnrHandler::isNameUnique(const std::string &name, const boost::optional<int> &exerciseId, const boost::optional<int32_t> &excludeId) const
|
|
{
|
|
auto constraint = [&name] (const CnrConfig &config) { return config.radioCircuit() != name; };
|
|
return checkConstraint(constraint, exerciseId, excludeId);
|
|
}
|
|
|
|
/**
|
|
* @param dbId Is set when called from init().
|
|
*/
|
|
Core::FunctionReplyObject CnrHandler::createCnrConfig(const ModifyCnrConfigRequest &request, const boost::optional<int32_t> &dbId)
|
|
{
|
|
DEBUG_LOG(m_logger) << "CreateCnrConfigRequest: " << request;
|
|
|
|
auto reply = validateRequest(request);
|
|
if (reply.error())
|
|
return reply;
|
|
|
|
Database::AvmCnrConfig dbConfigData = toDb(request);
|
|
|
|
if (dbId)
|
|
dbConfigData.id = *dbId;
|
|
else
|
|
{
|
|
if (!m_db->insert(dbConfigData))
|
|
return Core::FunctionReplyObject(tr_lts("dds", "Could not insert new CNR config data to database"));
|
|
}
|
|
|
|
CnrConfig cnrConfig = toDds(dbConfigData);
|
|
m_cache.insert(cnrConfig.id(), cnrConfig);
|
|
m_dts->publish(cnrConfig);
|
|
|
|
if (cnrConfig.isActive())
|
|
m_deviceConfig->create(cnrConfig);
|
|
|
|
return Core::FunctionReplyObject(dbConfigData.id);
|
|
}
|
|
|
|
Core::FunctionReplyObject CnrHandler::modifyCnrConfig(const ModifyCnrConfigRequest &request)
|
|
{
|
|
DEBUG_LOG(m_logger) << "ModifyCnrConfigRequest: " << request;
|
|
|
|
auto reply = validateRequest(request);
|
|
if (reply.error())
|
|
return reply;
|
|
|
|
auto found = m_cache.find(request.id());
|
|
Q_ASSERT(found != m_cache.end());
|
|
|
|
const CnrConfig &oldCnrConfig = *found;
|
|
|
|
// We do not allow changing the CnrChannel as this could cause problems when the CnrConfig is
|
|
// already referenced by a FrequencyPlan. Changing the CnrChannel is not possible through the HMI.
|
|
// Hint: In a FrequencyPlanning a CnrConfig has to be assigned to an entry from the FrequencySet.
|
|
// But not every entry from the FrequencySet can be assigned, e.g. a CnrConfig of (channel-)type VHF cannot
|
|
// be assigned to a FrequencySet-entry of type HF.
|
|
// Thus changing the CnrChannel would possibly invalidate those assignments in the FrequencyPlanning.
|
|
if (request.cnrChannelId() != oldCnrConfig.cnrChannelId())
|
|
return reply.setStr(tr_lts("dds", "Changing the CNR channel is not allowed"));
|
|
|
|
Database::AvmCnrConfig dbConfigData = toDb(request);
|
|
CnrConfig newCnrConfig = toDds(dbConfigData);
|
|
|
|
if (newCnrConfig.isActive())
|
|
m_deviceConfig->createOrUpdate(newCnrConfig);
|
|
else
|
|
m_deviceConfig->tryRemove(newCnrConfig.id());
|
|
|
|
if (newCnrConfig == oldCnrConfig)
|
|
return reply;
|
|
|
|
if (!m_db->update(dbConfigData))
|
|
return reply.setStr(tr_lts("dds", "Error updating CNR config data with id %1 in database")).arg(request.id());
|
|
|
|
m_cache[newCnrConfig.id()] = newCnrConfig;
|
|
|
|
m_dts->publish(newCnrConfig);
|
|
|
|
emit cnrConfigChanged(newCnrConfig);
|
|
return reply;
|
|
}
|
|
|
|
Core::FunctionReplyObject CnrHandler::removeCnrConfig(const Core::RemoveElementRequest &request)
|
|
{
|
|
DEBUG_LOG(m_logger) << "RemoveCnrConfigRequest: " << request;
|
|
|
|
int32_t configId = request.id();
|
|
Core::FunctionReplyObject reply(configId);
|
|
|
|
auto found = m_cache.find(configId);
|
|
if (found == m_cache.end())
|
|
return reply.setStr(tr_lts("dds", "Could not remove CNR config because id %1 is unknown")).arg(configId);
|
|
|
|
m_cache.erase(found);
|
|
|
|
m_deviceConfig->tryRemove(configId);
|
|
|
|
m_dts->removeCnrConfigByKey(configId);
|
|
|
|
if (!m_db->removeAvmCnrConfig(configId))
|
|
return reply.setStr(tr_lts("dds", "Could not remove CNR config data with id %1")).arg(request.id());
|
|
|
|
emit cnrConfigRemoved(configId);
|
|
|
|
return reply;
|
|
}
|
|
|
|
bool CnrHandler::isActiveExercise(int exerciseId) const
|
|
{
|
|
return m_activeExercises->isActive(ExerciseContext(exerciseId));
|
|
}
|
|
|
|
bool CnrHandler::needDeviceConfig(const CnrConfig &config) const
|
|
{
|
|
// For global, we always need the config
|
|
if (!config.exerciseId().is_set())
|
|
return true;
|
|
|
|
int32_t exerciseId = config.exerciseId().get();
|
|
// Not for inactive exercises
|
|
if (!isActiveExercise(exerciseId))
|
|
return false;
|
|
|
|
// Not for exercises without planning
|
|
if (!haveActiveFrequencyPlanning())
|
|
return false;
|
|
|
|
// Config needs to be referenced. (Wonder if its possible to have a non-referenced one...)
|
|
if (m_frequencyPlanningHandler->isReferencedByActivePlan(exerciseId, config.id()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether any of the active exercises has an active FrequencyPlanning.
|
|
*/
|
|
bool CnrHandler::haveActiveFrequencyPlanning() const
|
|
{
|
|
if (!m_frequencyPlanningHandler)
|
|
return false;
|
|
|
|
for (const auto &context : m_activeExercises->allContexts())
|
|
if (m_frequencyPlanningHandler->hasActivePlan(context.id()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return The list of exercises for the given config. The returned string
|
|
* is used in the definition of the device_config.
|
|
*/
|
|
QString CnrHandler::exerciseList(const CnrConfig &config) const
|
|
{
|
|
// Local
|
|
if (config.exerciseId().is_set())
|
|
return ExerciseContext(config.exerciseId().get()).toString();
|
|
|
|
// Global - all active exercises, no matter if they have active plannings or not
|
|
QStringList exerciseList;
|
|
for (const auto &context : qAsConst(m_activeExercises->allContexts()))
|
|
exerciseList.append(context.toString());
|
|
return exerciseList.join(QLatin1Char(','));
|
|
}
|
|
|
|
/**
|
|
* @return The list of RadioStations for the given config. The returned string
|
|
* is used in the controller-column of the device_config.
|
|
*/
|
|
QString CnrHandler::stationList(int addacChannel) const
|
|
{
|
|
if (addacChannel)
|
|
{
|
|
const int pps = Settings::addacPortsPerServer();
|
|
const int serverNum = ((addacChannel - 1) / pps) + 1;
|
|
const QString hostname = Settings::addacServerNamePrefix() + QString::asprintf(("%02d"), serverNum);
|
|
return hostname;
|
|
}
|
|
if (m_cnrRadioStations.isEmpty())
|
|
return QStringLiteral("EXCAVS01"); // fallback when there are no dedicated radio stations
|
|
|
|
const QStringList radioStations = m_cnrRadioStations.values();
|
|
return radioStations.join(QLatin1Char(','));
|
|
}
|
|
|
|
int CnrHandler::normalizedAddacChannel(int addacChannel) const
|
|
{
|
|
if (!addacChannel)
|
|
return addacChannel;
|
|
const int pps = Settings::addacPortsPerServer();
|
|
const int ret = (addacChannel % pps);
|
|
return ret == 0 ? pps : ret;
|
|
}
|
|
|
|
|
|
Database::AvmCnrChannel CnrHandler::channelById(int32_t id) const
|
|
{
|
|
for (const Database::AvmCnrChannel &channel : qAsConst(m_channels))
|
|
if (channel.id == id)
|
|
return channel;
|
|
|
|
Q_ASSERT(false);
|
|
return Database::AvmCnrChannel();
|
|
}
|
|
|
|
} // namespace AudioVideoManagement
|