Skip to content

Commit

Permalink
Support Analog/Binary Output Statuses
Browse files Browse the repository at this point in the history
  • Loading branch information
neilstephens committed Aug 1, 2024
1 parent a1c43e5 commit 9ab8770
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 51 deletions.
58 changes: 33 additions & 25 deletions Code/Ports/DNP3Port/DNP3MasterPort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,18 @@ void DNP3MasterPort::RePublishEvents()
if(ev->HasPayload())
PublishEvent(ev);
}
for (auto index : pConf->pPointConf->AnalogOutputStatusIndexes)
{
auto ev = pDB->Get(EventType::AnalogOutputStatus,index);
if(ev->HasPayload())
PublishEvent(ev);
}
for (auto index : pConf->pPointConf->BinaryOutputStatusIndexes)
{
auto ev = pDB->Get(EventType::BinaryOutputStatus,index);
if(ev->HasPayload())
PublishEvent(ev);
}
if (pConf->pPointConf->mCommsPoint.first.flags.IsSet(opendnp3::BinaryQuality::ONLINE))
{
auto ev = pDB->Get(EventType::Binary,pConf->pPointConf->mCommsPoint.second);
Expand Down Expand Up @@ -202,34 +214,30 @@ void DNP3MasterPort::SetCommsFailed()
if(auto log = odc::spdlog_get("DNP3Port"))
log->debug("{}: Setting {}, clearing {}, point quality.", Name, ToString(pConf->pPointConf->FlagsToSetOnLinkStatus), ToString(pConf->pPointConf->FlagsToClearOnLinkStatus));

for (auto index : pConf->pPointConf->BinaryIndexes)
{
auto last_event = pDB->Get(EventType::Binary,index);
auto new_qual = (last_event->GetQuality() | pConf->pPointConf->FlagsToSetOnLinkStatus) & ~pConf->pPointConf->FlagsToClearOnLinkStatus;

auto event = std::make_shared<EventInfo>(EventType::BinaryQuality,index,Name);
event->SetPayload<EventType::BinaryQuality>(QualityFlags(new_qual));
PublishEvent(event);
SetCommsFailedQuality<EventType::Binary, EventType::BinaryQuality>(pConf->pPointConf->BinaryIndexes);
SetCommsFailedQuality<EventType::Analog, EventType::AnalogQuality>(pConf->pPointConf->AnalogIndexes);
SetCommsFailedQuality<EventType::AnalogOutputStatus, EventType::AnalogOutputStatusQuality>(pConf->pPointConf->AnalogOutputStatusIndexes);
SetCommsFailedQuality<EventType::BinaryOutputStatus, EventType::BinaryOutputStatusQuality>(pConf->pPointConf->BinaryOutputStatusIndexes);
}
}

//update the EventDB event with the quality as well
auto new_event = std::make_shared<EventInfo>(*last_event);
new_event->SetQuality(std::move(new_qual));
pDB->Set(new_event);
}
for (auto index : pConf->pPointConf->AnalogIndexes)
{
auto last_event = pDB->Get(EventType::Analog,index);
auto new_qual = (last_event->GetQuality() | pConf->pPointConf->FlagsToSetOnLinkStatus) & ~pConf->pPointConf->FlagsToClearOnLinkStatus;
template <EventType etype, EventType qtype>
void DNP3MasterPort::SetCommsFailedQuality(std::vector<uint16_t>& indexes)
{
auto pConf = static_cast<DNP3PortConf*>(this->pConf.get());
for (auto index : indexes)
{
auto last_event = pDB->Get(etype,index);
auto new_qual = (last_event->GetQuality() | pConf->pPointConf->FlagsToSetOnLinkStatus) & ~pConf->pPointConf->FlagsToClearOnLinkStatus;

auto event = std::make_shared<EventInfo>(EventType::AnalogQuality,index,Name);
event->SetPayload<EventType::AnalogQuality>(QualityFlags(new_qual));
PublishEvent(event);
auto event = std::make_shared<EventInfo>(qtype,index,Name);
event->SetPayload<qtype>(QualityFlags(new_qual));
PublishEvent(event);

//update the EventDB event with the quality as well
auto new_event = std::make_shared<EventInfo>(*last_event);
new_event->SetQuality(std::move(new_qual));
pDB->Set(new_event);
}
//update the EventDB event with the quality as well
auto new_event = std::make_shared<EventInfo>(*last_event);
new_event->SetQuality(std::move(new_qual));
pDB->Set(new_event);
}
}

Expand Down
2 changes: 2 additions & 0 deletions Code/Ports/DNP3Port/DNP3MasterPort.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class DNP3MasterPort: public DNP3Port, public opendnp3::ISOEHandler, public open
void RePublishEvents();
void SetCommsGood();
void SetCommsFailed();
template <EventType etype, EventType qtype>
void SetCommsFailedQuality(std::vector<uint16_t>& indexes);
void CommsHeartBeat(bool isFailed);
void LinkStatusListener(opendnp3::LinkStatus status);
template<typename T>
Expand Down
37 changes: 25 additions & 12 deletions Code/Ports/DNP3Port/DNP3OutstationPort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,19 @@ void DNP3OutstationPort::Build()
{
StackConfig.database.octet_string[index].clazz = pConf->pPointConf->OctetStringClasses[index];
}
//TODO: Analog Output Status and Binary Output Status
//StackConfig.database.binary_output_status[index].clazz
//StackConfig.database.binary_output_status[index].evariation
//StackConfig.database.binary_output_status[index].svariation
//StackConfig.database.analog_output_status[index].clazz
//StackConfig.database.analog_output_status[index].evariation
//StackConfig.database.analog_output_status[index].svariation
//StackConfig.database.analog_output_status[index].deadband
for(auto index : pConf->pPointConf->AnalogOutputStatusIndexes)
{
StackConfig.database.analog_output_status[index].clazz = pConf->pPointConf->AnalogOutputStatusClasses[index];
StackConfig.database.analog_output_status[index].evariation = pConf->pPointConf->EventAnalogOutputStatusResponses[index];
StackConfig.database.analog_output_status[index].svariation = pConf->pPointConf->StaticAnalogOutputStatusResponses[index];
StackConfig.database.analog_output_status[index].deadband = pConf->pPointConf->AnalogOutputStatusDeadbands[index];
}
for(auto index : pConf->pPointConf->BinaryOutputStatusIndexes)
{
StackConfig.database.binary_output_status[index].clazz = pConf->pPointConf->BinaryOutputStatusClasses[index];
StackConfig.database.binary_output_status[index].evariation = pConf->pPointConf->EventBinaryOutputStatusResponses[index];
StackConfig.database.binary_output_status[index].svariation = pConf->pPointConf->StaticBinaryOutputStatusResponses[index];
}

InitEventDB();

Expand Down Expand Up @@ -321,10 +326,12 @@ void DNP3OutstationPort::Build()
StackConfig.outstation.params.unsolConfirmTimeout = opendnp3::TimeDuration::Milliseconds(pConf->pPointConf->UnsolConfirmTimeoutms); /// Timeout for unsolicited confirms

// TODO: Expose event limits for any new event types to be supported by opendatacon
StackConfig.outstation.eventBufferConfig.maxBinaryEvents = pConf->pPointConf->MaxBinaryEvents; /// The number of binary events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxAnalogEvents = pConf->pPointConf->MaxAnalogEvents; /// The number of analog events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxCounterEvents = pConf->pPointConf->MaxCounterEvents; /// The number of counter events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxOctetStringEvents =pConf->pPointConf->MaxOctetStringEvents; /// The number of octet string events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxBinaryEvents = pConf->pPointConf->MaxBinaryEvents; /// The number of binary events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxAnalogEvents = pConf->pPointConf->MaxAnalogEvents; /// The number of analog events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxCounterEvents = pConf->pPointConf->MaxCounterEvents; /// The number of counter events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxOctetStringEvents =pConf->pPointConf->MaxOctetStringEvents; /// The number of octet string events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxAnalogOutputStatusEvents = pConf->pPointConf->MaxAnalogOutputStatusEvents; /// The number of analog output status events the outstation will buffer before overflowing
StackConfig.outstation.eventBufferConfig.maxBinaryOutputStatusEvents = pConf->pPointConf->MaxBinaryOutputStatusEvents; /// The number of binary output status events the outstation will buffer before overflowing

//FIXME?: hack to create a toothless shared_ptr
// this is needed because the main exe manages our memory
Expand Down Expand Up @@ -500,6 +507,12 @@ void DNP3OutstationPort::Event(std::shared_ptr<const EventInfo> event, const std
case EventType::OctetString:
EventT(FromODC<opendnp3::OctetString>(event), event->GetIndex());
break;
case EventType::AnalogOutputStatus:
EventT(FromODC<opendnp3::AnalogOutputStatus>(event), event->GetIndex());
break;
case EventType::BinaryOutputStatus:
EventT(FromODC<opendnp3::BinaryOutputStatus>(event), event->GetIndex());
break;
case EventType::BinaryQuality:
UpdateQuality(EventType::Binary,event->GetIndex(),event->GetPayload<EventType::BinaryQuality>());
EventT(FromODC<opendnp3::BinaryQuality>(event), event->GetIndex(), opendnp3::FlagsType::BinaryInput);
Expand Down
113 changes: 113 additions & 0 deletions Code/Ports/DNP3Port/DNP3PointConf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,15 @@ DNP3PointConf::DNP3PointConf(const std::string& FileName, const Json::Value& Con
StaticBinaryResponse(opendnp3::StaticBinaryVariation::Group1Var1),
StaticAnalogResponse(opendnp3::StaticAnalogVariation::Group30Var5),
StaticCounterResponse(opendnp3::StaticCounterVariation::Group20Var1),
StaticAnalogOutputStatusResponse(opendnp3::StaticAnalogOutputStatusVariation::Group40Var1),
StaticBinaryOutputStatusResponse(opendnp3::StaticBinaryOutputStatusVariation::Group10Var2),
// Default Event Variations
EventBinaryResponse(opendnp3::EventBinaryVariation::Group2Var1),
EventAnalogResponse(opendnp3::EventAnalogVariation::Group32Var5),
EventCounterResponse(opendnp3::EventCounterVariation::Group22Var1),
EventAnalogControlResponse(opendnp3::EventAnalogOutputStatusVariation::Group42Var1), // 32 bit no time
EventAnalogOutputStatusResponse(opendnp3::EventAnalogOutputStatusVariation::Group42Var8),
EventBinaryOutputStatusResponse(opendnp3::EventBinaryOutputStatusVariation::Group11Var2),
TimestampOverride(TimestampOverride_t::ZERO),
// Event buffer limits
MaxBinaryEvents(1000),
Expand Down Expand Up @@ -360,6 +364,10 @@ void DNP3PointConf::ProcessElements(const Json::Value& JSONRoot)
StaticAnalogResponse = StringToStaticAnalogResponse(JSONRoot["StaticAnalogResponse"].asString());
if (JSONRoot.isMember("StaticCounterResponse"))
StaticCounterResponse = StringToStaticCounterResponse(JSONRoot["StaticCounterResponse"].asString());
if (JSONRoot.isMember("StaticAnalogOutputStatusResponse"))
StaticAnalogOutputStatusResponse = StringToStaticAnalogOutputStatusResponse(JSONRoot["StaticAnalogOutputStatusResponse"].asString());
if (JSONRoot.isMember("StaticBinaryOutputStatusResponse"))
StaticBinaryOutputStatusResponse = StringToStaticBinaryOutputStatusResponse(JSONRoot["StaticBinaryOutputStatusResponse"].asString());

// Default Event Variations
if (JSONRoot.isMember("EventBinaryResponse"))
Expand All @@ -370,6 +378,10 @@ void DNP3PointConf::ProcessElements(const Json::Value& JSONRoot)
EventCounterResponse = StringToEventCounterResponse(JSONRoot["EventCounterResponse"].asString());
if (JSONRoot.isMember("AnalogControlResponse"))
EventAnalogControlResponse = StringToEventAnalogControlResponse(JSONRoot["AnalogControlResponse"].asString());
if (JSONRoot.isMember("EventBinaryOutputStatusResponse"))
EventBinaryOutputStatusResponse = StringToEventBinaryOutputStatusResponse(JSONRoot["EventBinaryOutputStatusResponse"].asString());
if (JSONRoot.isMember("EventAnalogOutputStatusResponse"))
EventAnalogOutputStatusResponse = StringToEventAnalogOutputStatusResponse(JSONRoot["EventAnalogOutputStatusResponse"].asString());

// Timestamp Override Alternatives
if (JSONRoot.isMember("TimestampOverride"))
Expand Down Expand Up @@ -428,6 +440,7 @@ void DNP3PointConf::ProcessElements(const Json::Value& JSONRoot)
return {true,start,stop};
};

//TODO: remove stale code from way back when removing a point on-the-fly was a thing
auto InsertOrDeleteIndex = [](const auto& PointArrayElement, auto& IndexVec, auto index) -> bool
{
if (std::find(IndexVec.begin(),IndexVec.end(),index) == IndexVec.end())
Expand Down Expand Up @@ -560,6 +573,106 @@ void DNP3PointConf::ProcessElements(const Json::Value& JSONRoot)
std::sort(BinaryIndexes.begin(),BinaryIndexes.end());
}

if(JSONRoot.isMember("AnalogOutputStatuses"))
{
const auto AnalogOutputStatuses = JSONRoot["AnalogOutputStatuses"];
for(Json::ArrayIndex n = 0; n < AnalogOutputStatuses.size(); ++n)
{
auto [success, start, stop] = GetIndexRange(AnalogOutputStatuses[n]);
if(!success)
continue;

double deadband = 0;
if(AnalogOutputStatuses[n].isMember("Deadband"))
{
deadband = AnalogOutputStatuses[n]["Deadband"].asDouble();
}

for(auto index = start; index <= stop; index++)
{
AnalogOutputStatusClasses[index] = GetClass(AnalogOutputStatuses[n]);
if (AnalogOutputStatuses[n].isMember("StaticAnalogOutputStatusResponse"))
StaticAnalogOutputStatusResponses[index] = StringToStaticAnalogOutputStatusResponse(AnalogOutputStatuses[n]["StaticAnalogOutputStatusResponse"].asString());
else
StaticAnalogOutputStatusResponses[index] = StaticAnalogOutputStatusResponse;
if (AnalogOutputStatuses[n].isMember("EventAnalogOutputStatusResponse"))
EventAnalogOutputStatusResponses[index] = StringToEventAnalogOutputStatusResponse(AnalogOutputStatuses[n]["EventAnalogOutputStatusResponse"].asString());
else
EventAnalogOutputStatusResponses[index] = EventAnalogOutputStatusResponse;

//TODO: make deadbands per point
AnalogOutputStatusDeadbands[index] = deadband;

if (std::find(AnalogOutputStatusIndexes.begin(),AnalogOutputStatusIndexes.end(),index) == AnalogOutputStatusIndexes.end())
AnalogOutputStatusIndexes.push_back(index);

if(AnalogOutputStatuses[n].isMember("StartVal"))
{
opendnp3::Flags flags;
std::string start_val = AnalogOutputStatuses[n]["StartVal"].asString();
if(start_val == "X")
{
flags.Set(opendnp3::AnalogOutputStatusQuality::COMM_LOST);
AnalogOutputStatusStartVals[index] = opendnp3::AnalogOutputStatus(0,flags);
}
else
{
flags.Set(opendnp3::AnalogOutputStatusQuality::ONLINE);
AnalogOutputStatusStartVals[index] = opendnp3::AnalogOutputStatus(std::stod(start_val),flags);
}
}
else if(AnalogOutputStatusStartVals.count(index))
AnalogOutputStatusStartVals.erase(index);
}
}
std::sort(AnalogOutputStatusIndexes.begin(),AnalogOutputStatusIndexes.end());
}

if(JSONRoot.isMember("BinaryOutputStatuses"))
{
const auto BinaryOutputStatuses = JSONRoot["BinaryOutputStatuses"];
for(Json::ArrayIndex n = 0; n < BinaryOutputStatuses.size(); ++n)
{
auto [success, start, stop] = GetIndexRange(BinaryOutputStatuses[n]);
if(!success)
continue;
for(auto index = start; index <= stop; index++)
{
BinaryOutputStatusClasses[index] = GetClass(BinaryOutputStatuses[n]);
if (BinaryOutputStatuses[n].isMember("StaticBinaryOutputStatusResponse"))
StaticBinaryOutputStatusResponses[index] = StringToStaticBinaryOutputStatusResponse(BinaryOutputStatuses[n]["StaticBinaryOutputStatusResponse"].asString());
else
StaticBinaryOutputStatusResponses[index] = StaticBinaryOutputStatusResponse;
if (BinaryOutputStatuses[n].isMember("EventBinaryOutputStatusResponse"))
EventBinaryOutputStatusResponses[index] = StringToEventBinaryOutputStatusResponse(BinaryOutputStatuses[n]["EventBinaryOutputStatusResponse"].asString());
else
EventBinaryOutputStatusResponses[index] = EventBinaryOutputStatusResponse;

if (std::find(BinaryOutputStatusIndexes.begin(),BinaryOutputStatusIndexes.end(),index) == BinaryOutputStatusIndexes.end())
BinaryOutputStatusIndexes.push_back(index);

if(BinaryOutputStatuses[n].isMember("StartVal"))
{
opendnp3::Flags flags;
std::string start_val = BinaryOutputStatuses[n]["StartVal"].asString();
if(start_val == "X")
{
flags.Set(opendnp3::BinaryQuality::COMM_LOST);
BinaryOutputStatusStartVals[index] = opendnp3::BinaryOutputStatus(false,flags);
}
else
{
flags.Set(opendnp3::BinaryQuality::ONLINE);
BinaryOutputStatusStartVals[index] = opendnp3::BinaryOutputStatus(BinaryOutputStatuses[n]["StartVal"].asBool(),flags);
}
}
else if(BinaryOutputStatusStartVals.count(index))
BinaryOutputStatusStartVals.erase(index);
}
}
std::sort(BinaryOutputStatusIndexes.begin(),BinaryOutputStatusIndexes.end());
}

if (JSONRoot.isMember("OctetStrings"))
{
const auto OctetStrings = JSONRoot["OctetStrings"];
Expand Down
Loading

0 comments on commit 9ab8770

Please sign in to comment.