diff --git a/.cproject b/.cproject
new file mode 100644
index 0000000000..636e313787
--- /dev/null
+++ b/.cproject
@@ -0,0 +1,3211 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ make
+ -j6
+ all
+ true
+ true
+ false
+
+
+ make
+ -j6
+ am--refresh
+ true
+ true
+ false
+
+
+ make
+ -j6
+ check
+ true
+ true
+ false
+
+
+ make
+ -j6
+ clean
+ true
+ true
+ false
+
+
+ make
+ -j6
+ clean-cscope
+ true
+ true
+ false
+
+
+ make
+ -j6
+ clean-libLTLIBRARIES
+ true
+ true
+ false
+
+
+ make
+ -j6
+ clean-libtool
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/examples/master/masterdemo-DemoMain.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/examples/master/masterdemo-DemoMain.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/examples/outstation/outstationdemo-DemoMain.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/examples/outstation/outstationdemo-DemoMain.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/impl/libasiodnp3_la-LinkLayerRouter.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/impl/libasiodnp3_la-PhysicalLayerMonitor.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/impl/libasiodnp3_la-PhysicalLayerMonitorStates.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-BlockingCommandCallback.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-ChangeSet.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-ChannelSet.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-ConsoleLogger.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-DefaultMasterApplication.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-DestructorHook.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-DNP3Channel.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-DNP3Manager.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-HeaderTypes.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-MasterStackImpl.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-MeasUpdate.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-MultidropTaskLock.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-OutstationStackImpl.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-PrintingSOEHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiodnp3/src/asiodnp3/libasiodnp3_la-StackActionHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-ASIOExecutor.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-ASIOSerialHelpers.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-IOServiceThreadPool.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-LogFanoutHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-PhysicalLayerBase.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-PhysicalLayerBaseTCP.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-PhysicalLayerSerial.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-PhysicalLayerTCPClient.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-PhysicalLayerTCPServer.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-SerialTypes.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-TimerASIO.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/asiopal/src/asiopal/libasiopal_la-UTCTimeSource.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-AnalogCommandEvent.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-AnalogOutput.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDUBuilders.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDUHandlerBase.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDUHeader.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDUHeaderParser.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDULogging.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDUParser.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDURequest.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDUResponse.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-APDUWrapper.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-AppControlField.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-BinaryCommandEvent.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-BitReader.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-ClassField.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-ControlRelayOutputBlock.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-EventTriggers.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-FunctionHelpers.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-GroupVariationRecord.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-IINField.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-MeasurementTypes.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-ObjectWriter.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-OctetData.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/app/libopendnp3_la-TimeAndInterval.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-ChannelState.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-CommandStatus.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-ControlCode.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-DoubleBit.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-FunctionCode.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-GroupVariation.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-IntervalUnits.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-LinkFunction.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-PointClass.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-QualifierCode.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-TaskCompletion.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/gen/libopendnp3_la-TimeSyncMode.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-CRC.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-IOpenDelayStrategy.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-LinkFrame.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-LinkHeader.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-LinkLayer.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-LinkLayerParser.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-PriLinkLayerStates.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-SecLinkLayerStates.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/link/libopendnp3_la-ShiftableBuffer.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-AssignClassTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-ClearRestartTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-CommandMarshaller.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-CommandResponse.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-CommandTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-DisableUnsolicitedTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-EnableUnsolicitedTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-EventScanTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-IMasterState.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-IMasterTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-ISOEHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-ITaskLock.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-Master.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-MasterContext.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-MasterParams.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-MasterScan.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-MasterScheduler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-MasterTasks.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-MeasurementHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-PollTaskBase.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-SerialTimeSyncTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-StartupIntegrityPoll.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-UserPollTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/master/libopendnp3_la-WriteTask.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group1.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group10.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group11.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group12.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group13.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group2.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group20.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group21.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group22.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group23.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group3.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group30.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group32.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group4.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group40.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group41.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group42.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group43.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group50.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group51.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/objects/libopendnp3_la-Group52.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-ApplicationIIN.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-AssignClassHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-ClassBasedRequestHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-CommandActionAdapter.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-CommandResponseHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-Database.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-DatabaseBuffers.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-DatabaseConfigView.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-EventBuffer.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-EventBufferConfig.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-EventCount.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-EventWriter.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-IINHelpers.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-IOutstationApplication.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-Outstation.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-OutstationContext.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-OutstationParams.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-OutstationSolicitedStates.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-OutstationUnsolicitedStates.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-ReadHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-ResponseContext.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-SelectedRanges.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-SimpleCommandHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-SOERecord.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-StaticBuffers.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-StaticLoadFunctions.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/outstation/libopendnp3_la-WriteHandler.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/transport/libopendnp3_la-TransportLayer.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/transport/libopendnp3_la-TransportRx.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/transport/libopendnp3_la-TransportStack.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/opendnp3/src/opendnp3/transport/libopendnp3_la-TransportTx.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/container/libopenpal_la-DynamicBuffer.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/container/libopenpal_la-ReadBufferView.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/container/libopenpal_la-WriteBufferView.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/executor/libopenpal_la-Action0.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/executor/libopenpal_la-Erasure.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/executor/libopenpal_la-MonotonicTimestamp.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/executor/libopenpal_la-TimeDuration.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/logging/libopenpal_la-LogEntry.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/logging/libopenpal_la-Logger.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/logging/libopenpal_la-LogRoot.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/logging/libopenpal_la-StringFormatting.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/serialization/libopenpal_la-ByteSerialization.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/serialization/libopenpal_la-UInt48LE.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/util/libopenpal_la-Limits.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/libs/openpal/src/openpal/util/libopenpal_la-ToHex.lo
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/outstation/outstation-AsyncCommandHandler.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/outstation/outstation-AsyncCommandHandler.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/outstation/outstation-OutstationApp.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/outstation/outstation-OutstationApp.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-APDUHelpers.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-APDUHelpers.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-APDUHexBuilders.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-APDUHexBuilders.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-BufferHelpers.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-BufferHelpers.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-BufferTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-BufferTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-CatchTestStart.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-CatchTestStart.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-CopyableBuffer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-CopyableBuffer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-DNPHelpers.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-DNPHelpers.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-HexConversions.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-HexConversions.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LinkLayerRouterTest.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LinkLayerRouterTest.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LinkLayerTest.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LinkLayerTest.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LogTester.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LogTester.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LoopbackPhysicalLayer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LoopbackPhysicalLayer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LowerLayerToPhysAdapter.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-LowerLayerToPhysAdapter.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MasterTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MasterTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockExecutor.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockExecutor.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockFrameSink.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockFrameSink.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockLowerLayer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockLowerLayer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockPhysicalLayer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockPhysicalLayer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockPhysicalLayerMonitor.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockPhysicalLayerMonitor.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockTransportLayer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockTransportLayer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockUpperLayer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-MockUpperLayer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-OutstationTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-OutstationTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-PhysBaseTest.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-PhysBaseTest.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-PhysLoopback.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-PhysLoopback.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-PhysTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-PhysTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-ProtocolUtil.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-ProtocolUtil.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-RandomizedBuffer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-RandomizedBuffer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-SerialTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-SerialTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-StopWatch.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-StopWatch.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestAPDUParsing.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestAPDUParsing.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestAPDUWriting.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestAPDUWriting.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestASIO.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestASIO.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestASIOThreadPool.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestASIOThreadPool.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestCastLongLongDouble.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestCastLongLongDouble.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestCRC.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestCRC.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestDatabase.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestDatabase.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestDNP3Manager.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestDNP3Manager.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestIndexSearch.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestIndexSearch.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLazyCollection.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLazyCollection.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkFrame.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkFrame.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkLayer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkLayer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkLayerRouter.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkLayerRouter.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkReceiver.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkReceiver.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkRoute.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLinkRoute.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLog.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestLog.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMaster.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMaster.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterAssignClass.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterAssignClass.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterCommandRequests.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterCommandRequests.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterMultidrop.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterMultidrop.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterUnsolBehaviors.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestMasterUnsolBehaviors.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestObjectASIO.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestObjectASIO.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstation.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstation.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationAssignClass.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationAssignClass.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationCommandResponses.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationCommandResponses.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationEventResponses.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationEventResponses.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationUnsolicitedResponses.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestOutstationUnsolicitedResponses.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerAsyncBase.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerAsyncBase.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerAsyncSerial.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerAsyncSerial.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerAsyncTCP.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerAsyncTCP.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerLoopback.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerLoopback.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerMonitor.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestPhysicalLayerMonitor.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestSerialization.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestSerialization.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestShiftableBuffer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestShiftableBuffer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTime.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTime.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTimers.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTimers.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTransportLayer.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTransportLayer.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTransportLoopback.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTransportLoopback.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTypes.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestTypes.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestUtil.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestUtil.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestWriteConversions.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TestWriteConversions.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-Timeout.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-Timeout.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportIntegrationStack.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportIntegrationStack.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportLoopbackTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportLoopbackTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportScalabilityTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportScalabilityTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportStackPair.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportStackPair.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportTestObject.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/opendnp3tests/src/dnp3test-TransportTestObject.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-CatchTestStart.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-CatchTestStart.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-FunctionalTestSuite.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-FunctionalTestSuite.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-LinkedListTestSuite.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-LinkedListTestSuite.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-ManagedPointerTestSuite.o
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cpp/tests/openpaltests/src/openpaltest-ManagedPointerTestSuite.obj
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cscope
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cscope.files
+ true
+ true
+ false
+
+
+ make
+ -j6
+ cscopelist
+ true
+ true
+ false
+
+
+ make
+ -j6
+ ctags
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-all
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-bzip2
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-gzip
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-lzip
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-shar
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-tarZ
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-xz
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dist-zip
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distcheck
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distclean
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distclean-compile
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distclean-libtool
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distclean-tags
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distcleancheck
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distdir
+ true
+ true
+ false
+
+
+ make
+ -j6
+ distuninstallcheck
+ true
+ true
+ false
+
+
+ make
+ -j6
+ dvi
+ true
+ true
+ false
+
+
+ make
+ -j6
+ html
+ true
+ true
+ false
+
+
+ make
+ -j6
+ info
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-data
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-dvi
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-exec
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-html
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-info
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-libLTLIBRARIES
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-man
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-pdf
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-ps
+ true
+ true
+ false
+
+
+ make
+ -j6
+ install-strip
+ true
+ true
+ false
+
+
+ make
+ -j6
+ installcheck
+ true
+ true
+ false
+
+
+ make
+ -j6
+ installdirs
+ true
+ true
+ false
+
+
+ make
+ -j6
+ libasiodnp3.la
+ true
+ true
+ false
+
+
+ make
+ -j6
+ libasiopal.la
+ true
+ true
+ false
+
+
+ make
+ -j6
+ libopendnp3.la
+ true
+ true
+ false
+
+
+ make
+ -j6
+ libopenpal.la
+ true
+ true
+ false
+
+
+ make
+ -j6
+ maintainer-clean
+ true
+ true
+ false
+
+
+ make
+ -j6
+ Makefile
+ true
+ true
+ false
+
+
+ make
+ -j6
+ mostlyclean
+ true
+ true
+ false
+
+
+ make
+ -j6
+ mostlyclean-compile
+ true
+ true
+ false
+
+
+ make
+ -j6
+ mostlyclean-libtool
+ true
+ true
+ false
+
+
+ make
+ -j6
+ pdf
+ true
+ true
+ false
+
+
+ make
+ -j6
+ ps
+ true
+ true
+ false
+
+
+ make
+ -j6
+ tags
+ true
+ true
+ false
+
+
+ make
+ -j6
+ uninstall
+ true
+ true
+ false
+
+
+ make
+ -j6
+ uninstall-libLTLIBRARIES
+ true
+ true
+ false
+
+
+
+
diff --git a/.gitignore b/.gitignore
index 56d753b686..fb42c96d45 100755
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,7 @@ libosslcrypto.*
libtestlib.*
libclangfuzzer.*
libdnp3mocks.*
+yaml-cpp.*
#test suites
testopenpal
@@ -55,6 +56,7 @@ testopendnp3
testasiodnp3
testosslcrypto
testsecauth
+testoutstation
#demos
master-demo
@@ -144,3 +146,11 @@ coverage.info
cpp/demos/cover.info
cpp/demos/cover_html/
+# eclipse generated files
+RemoteSystemsTempFiles
+.metadata
+.settings
+.autotools
+
+# apps
+/outstation
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..8c62de9316
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,12 @@
+[submodule "thirdparty/asio"]
+ path = thirdparty/asio
+ url = ../asio/
+ branch = develop
+[submodule "thirdparty/rapidjson"]
+ path = thirdparty/rapidjson
+ url = ../rapidjson
+ branch = develop
+[submodule "thirdparty/yaml-cpp"]
+ path = thirdparty/yaml-cpp
+ url = ../yaml-cpp
+ branch = develop
diff --git a/.project b/.project
new file mode 100644
index 0000000000..3d47e00a8a
--- /dev/null
+++ b/.project
@@ -0,0 +1,33 @@
+
+
+ dnp3
+
+
+
+
+
+ org.eclipse.cdt.autotools.core.genmakebuilderV2
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.genmakebuilder
+ clean,full,incremental,
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
+ full,incremental,
+
+
+
+
+
+ org.eclipse.cdt.core.cnature
+ org.eclipse.cdt.core.ccnature
+ org.eclipse.cdt.managedbuilder.core.managedBuildNature
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
+ org.eclipse.cdt.autotools.core.autotoolsNatureV2
+
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d18fe63d1a..398334d0b9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,6 +6,7 @@ set(OPENDNP3_MICRO_VERSION 0)
set(OPENDNP3_VERSION ${OPENDNP3_MAJOR_VERSION}.${OPENDNP3_MINOR_VERSION}.${OPENDNP3_MICRO_VERSION})
# various optional libraries and projects
+option(OUTSTATION "Build outstation application" OFF)
option(DEMO "Build demo applications" OFF)
option(TEST "Build tests" OFF)
option(FULL "Build all optional projects (secauth, demos, tests)" OFF)
@@ -18,6 +19,7 @@ option(STATICLIBS "Builds static versions of all installed libraries" OFF)
option(COVERAGE "Builds the libraries with coverage info for gcov" OFF)
if(FULL)
+ set(OUTSTATION ON)
set(DEMO ON)
set(TEST ON)
set(SECAUTH ON)
@@ -43,6 +45,23 @@ if(SECAUTH OR DNP3_TLS)
endif()
+if(OUTSTATION OR TEST)
+ include_directories(./thirdparty/rapidjson/include)
+ include_directories(./thirdparty/yaml-cpp/include)
+
+ add_subdirectory(./thirdparty/yaml-cpp)
+
+ set(Boost_USE_STATIC_LIBS OFF)
+ set(Boost_USE_MULTITHREADED ON)
+ set(Boost_USE_STATIC_RUNTIME OFF)
+ find_package(Boost 1.54.0 COMPONENTS system thread program_options)
+
+ if(Boost_FOUND)
+ include_directories(${Boost_INCLUDE_DIRS})
+ endif()
+
+endif()
+
if(WIN32)
set_property(GLOBAL PROPERTY USE_FOLDERS ON) #allows the creation of solution folders
@@ -93,17 +112,8 @@ endif()
set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX_FLAGS})
-if (ASIO_HOME)
- message("ASIO_HOME defined in cache: ${ASIO_HOME}")
- include_directories(${ASIO_HOME})
-else()
- if(DEFINED ENV{ASIO_HOME})
- message("ASIO_HOME defined in environment: $ENV{ASIO_HOME}")
- include_directories($ENV{ASIO_HOME})
- else()
- message("ASIO_HOME was not defined. ASIO expected to be on include path")
- endif()
-endif()
+# include path for asio submodule
+include_directories(./thirdparty/asio/asio/include)
# include paths for all the local libraries
include_directories(./cpp/libs/src)
@@ -184,6 +194,15 @@ endif()
set(INSTALL_ARGS FILES_MATCHING PATTERN "*.h" PATTERN ".deps" EXCLUDE PATTERN ".libs" EXCLUDE)
install(DIRECTORY ./cpp/libs/include/ DESTINATION include ${INSTALL_ARGS})
+if(OUTSTATION)
+
+ # ----- outstation executable -----
+ add_executable(outstation ./cpp/outstation/OutstationApp.cpp)
+ target_link_libraries (outstation LINK_PUBLIC yaml-cpp asiodnp3 ${PTHREAD} ${Boost_LIBRARIES})
+ #set_target_properties(outstation PROPERTIES FOLDER demos)
+
+endif()
+
if(DEMO)
# ----- master demo executable -----
@@ -279,6 +298,11 @@ if(TEST)
set_target_properties(testosslcrypto PROPERTIES FOLDER tests)
endif()
+
+ # ----- outstation app tests -----
+ file(GLOB_RECURSE outstation_TESTSRC ./cpp/tests/outstation/src/*.cpp ./cpp/tests/outstation/src/*.h)
+ add_executable (testoutstation ${outstation_TESTSRC})
+ target_link_libraries (testoutstation yaml-cpp)
endif()
diff --git a/README.md b/README.md
index 120ac0d2f1..0105b63cb7 100755
--- a/README.md
+++ b/README.md
@@ -16,5 +16,25 @@ Overview
Opendnp3 is a portable, scalable, and rigorously tested implementation
of the DNP3 (www.dnp.org) protocol stack written in C++11. The library
-is designed for high-performance applications like many concurrent TCP
-sessions or huge device simulations. It also embeds very nicely on Linux.
+can handle the largest front end processor loads, but can also be
+ported to run on various microcontrollers.
+
+Langauge bindings are available. Consult the documentation.
+
+Ubuntu 14.04 Dependencies
+=========================
+```sudo apt-get install cmake libasio-dev libboost-all-dev
+```
+
+Build Steps
+===========
+```bash
+git submodule update --init
+cmake -DDEMO=on -DTEST=on -DOUTSTATION=on .
+make -j5
+
+# from simultaneous shells
+./outstation
+./master-demo (press 'o' to send analog output 16)
+nc localhost 3384
+```
diff --git a/cpp/examples/master/main.cpp b/cpp/examples/master/main.cpp
index 7ca6f2cb3c..1e3e795755 100644
--- a/cpp/examples/master/main.cpp
+++ b/cpp/examples/master/main.cpp
@@ -74,13 +74,11 @@ int main(int argc, char* argv[])
// Create a new master on a previously declared port, with a
// name, log level, command acceptor, and config info. This
// returns a thread-safe interface used for sending commands.
- auto pMaster = pChannel->AddMaster(
- "master", // id for logging
- PrintingSOEHandler::Instance(), // callback for data processing
- asiodnp3::DefaultMasterApplication::Instance(), // master application instance
- stackConfig // stack configuration
- );
-
+ auto pMaster = pChannel->AddMaster("master", // id for logging
+ PrintingSOEHandler::Instance(), // callback for data processing
+ asiodnp3::DefaultMasterApplication::Instance(), // master application instance
+ stackConfig // stack configuration
+ );
// do an integrity poll (Class 3/2/1/0) once per minute
auto integrityScan = pMaster->AddClassScan(ClassField::AllClasses(), TimeDuration::Minutes(1));
@@ -104,45 +102,52 @@ int main(int argc, char* argv[])
char cmd;
std::cin >> cmd;
- switch(cmd)
+ switch (cmd)
{
- case('a') :
+ case ('a'):
pMaster->ScanRange(GroupVariationID(1, 2), 0, 3);
break;
- case('d') :
- pMaster->PerformFunction("disable unsol", FunctionCode::DISABLE_UNSOLICITED,
- { Header::AllObjects(60, 2), Header::AllObjects(60, 3), Header::AllObjects(60, 4) }
- );
+ case ('d'):
+ pMaster->PerformFunction("disable unsol", FunctionCode::DISABLE_UNSOLICITED, { Header::AllObjects(60, 2), Header::AllObjects(60, 3),
+ Header::AllObjects(60, 4)
+ });
break;
- case('r') :
- {
- auto print = [](const RestartOperationResult& result)
+ case ('r'):
{
- if(result.summary == TaskCompletion::SUCCESS)
- {
- std::cout << "Success, Time: " << result.restartTime.GetMilliseconds() << std::endl;
- }
- else
- {
- std::cout << "Failure: " << TaskCompletionToString(result.summary) << std::endl;
- }
- };
- pMaster->Restart(RestartType::COLD, print);
- break;
- }
- case('x'):
+ auto print = [](const RestartOperationResult & result)
+ {
+ if(result.summary == TaskCompletion::SUCCESS)
+ {
+ std::cout << "Success, Time: " << result.restartTime.GetMilliseconds() << std::endl;
+ }
+ else
+ {
+ std::cout << "Failure: " << TaskCompletionToString(result.summary) << std::endl;
+ }
+ };
+ pMaster->Restart(RestartType::COLD, print);
+ break;
+ }
+ case ('x'):
// C++ destructor on DNP3Manager cleans everything up for you
return 0;
- case('i'):
+ case ('i'):
integrityScan.Demand();
break;
- case('e'):
+ case ('e'):
exceptionScan.Demand();
break;
- case('c'):
+ case ('c'):
+ {
+ ControlRelayOutputBlock crob(ControlCode::LATCH_ON);
+ pMaster->SelectAndOperate(crob, 0, PrintingCommandCallback::Get());
+ break;
+ }
+ case ('o'):
{
- ControlRelayOutputBlock crob(ControlCode::LATCH_ON);
- pMaster->SelectAndOperate(crob, 0, PrintingCommandCallback::Get());
+ // This is an example of synchronously doing a control operation
+ AnalogOutputInt16 analogOutInt16(4242);
+ pMaster->SelectAndOperate(analogOutInt16, 0, PrintingCommandCallback::Get());
break;
}
default:
@@ -150,7 +155,7 @@ int main(int argc, char* argv[])
break;
}
}
- while(true);
+ while (true);
return 0;
}
diff --git a/cpp/outstation/AsyncCommand.cpp b/cpp/outstation/AsyncCommand.cpp
new file mode 100644
index 0000000000..6dd3c39623
--- /dev/null
+++ b/cpp/outstation/AsyncCommand.cpp
@@ -0,0 +1,66 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using namespace opendnp3;
+
+class AsyncCommand {
+public:
+ AsyncCommand(const ControlRelayOutputBlock* crob, const char* id, uint16_t idx) :
+ id_(id), idx_(idx), crob_(crob), aoInt16_(), aoInt32_(), aoFloat32_(), aoDouble64_() {
+ }
+ AsyncCommand(const AnalogOutputInt16* aoInt16, const char* id, uint16_t idx) :
+ id_(id), idx_(idx), crob_(), aoInt16_(aoInt16), aoInt32_(), aoFloat32_(), aoDouble64_() {
+ }
+ AsyncCommand(const AnalogOutputInt32* aoInt32, const char* id, uint16_t idx) :
+ id_(id), idx_(idx), crob_(), aoInt16_(), aoInt32_(aoInt32), aoFloat32_(), aoDouble64_() {
+ }
+ AsyncCommand(const AnalogOutputFloat32* aoFloat32, const char* id, uint16_t idx) :
+ id_(id), idx_(idx), crob_(), aoInt16_(), aoInt32_(), aoFloat32_(aoFloat32), aoDouble64_() {
+ }
+ AsyncCommand(const AnalogOutputDouble64* aoDouble64, const char* id, uint16_t idx) :
+ id_(id), idx_(idx), crob_(), aoInt16_(), aoInt32_(), aoFloat32_(), aoDouble64_(aoDouble64) {
+ }
+
+ const char* Id() {
+ return id_;
+ }
+
+ uint16_t Idx() {
+ return idx_;
+ }
+ const ControlRelayOutputBlock* CROB() {
+ return crob_;
+ }
+ const AnalogOutputInt16* AOInt16() {
+ return aoInt16_;
+ }
+ const AnalogOutputInt32* AOInt32() {
+ return aoInt32_;
+ }
+ const AnalogOutputFloat32* AOFloat32() {
+ return aoFloat32_;
+ }
+ const AnalogOutputDouble64* AODouble64() {
+ return aoDouble64_;
+ }
+
+private:
+ const char* id_;
+ uint16_t idx_;
+ const ControlRelayOutputBlock* crob_;
+ const AnalogOutputInt16* aoInt16_;
+ const AnalogOutputInt32* aoInt32_;
+ const AnalogOutputFloat32* aoFloat32_;
+ const AnalogOutputDouble64* aoDouble64_;
+};
diff --git a/cpp/outstation/AsyncCommandHandler.cpp b/cpp/outstation/AsyncCommandHandler.cpp
new file mode 100644
index 0000000000..e928d076b4
--- /dev/null
+++ b/cpp/outstation/AsyncCommandHandler.cpp
@@ -0,0 +1,69 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AsyncCommandHandler.h"
+
+// TODO validate requests before returning CommandStatus
+AsyncCommandHandler::AsyncCommandHandler(const char* id, AsyncCommandQueue& queue) :
+ id_(id), queue_(queue) {
+}
+
+CommandStatus AsyncCommandHandler::Select(const ControlRelayOutputBlock& command, uint16_t aIndex) {
+ return CommandStatus::SUCCESS;
+}
+CommandStatus AsyncCommandHandler::Operate(const ControlRelayOutputBlock& command, uint16_t aIndex) {
+ queue_.push(new AsyncCommand(&command, id_, aIndex));
+ return CommandStatus::SUCCESS;
+}
+
+CommandStatus AsyncCommandHandler::Select(const AnalogOutputInt16& command, uint16_t aIndex) {
+ return CommandStatus::SUCCESS;
+}
+CommandStatus AsyncCommandHandler::Operate(const AnalogOutputInt16& command, uint16_t aIndex) {
+ queue_.push(new AsyncCommand(&command, id_, aIndex));
+ return CommandStatus::SUCCESS;
+}
+
+CommandStatus AsyncCommandHandler::Select(const AnalogOutputInt32& command, uint16_t aIndex) {
+ return CommandStatus::SUCCESS;
+}
+CommandStatus AsyncCommandHandler::Operate(const AnalogOutputInt32& command, uint16_t aIndex) {
+ queue_.push(new AsyncCommand(&command, id_, aIndex));
+ return CommandStatus::SUCCESS;
+}
+
+CommandStatus AsyncCommandHandler::Select(const AnalogOutputFloat32& command, uint16_t aIndex) {
+ return CommandStatus::SUCCESS;
+}
+CommandStatus AsyncCommandHandler::Operate(const AnalogOutputFloat32& command, uint16_t aIndex) {
+ queue_.push(new AsyncCommand(&command, id_, aIndex));
+ return CommandStatus::SUCCESS;
+}
+
+CommandStatus AsyncCommandHandler::Select(const AnalogOutputDouble64& command, uint16_t aIndex) {
+ return CommandStatus::SUCCESS;
+}
+CommandStatus AsyncCommandHandler::Operate(const AnalogOutputDouble64& command, uint16_t aIndex) {
+ queue_.push(new AsyncCommand(&command, id_, aIndex));
+ return CommandStatus::SUCCESS;
+}
+
+void AsyncCommandHandler::Start() {
+ // Do nothing
+}
+
+void AsyncCommandHandler::End() {
+ // Do nothing
+}
+
diff --git a/cpp/outstation/AsyncCommandHandler.h b/cpp/outstation/AsyncCommandHandler.h
new file mode 100644
index 0000000000..cf5cad714a
--- /dev/null
+++ b/cpp/outstation/AsyncCommandHandler.h
@@ -0,0 +1,58 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef OPENDNP3_ASYNCCOMMANDHANDLER_H
+#define OPENDNP3_ASYNCCOMMANDHANDLER_H
+
+#include
+
+#include
+#include
+#include
+
+using namespace opendnp3;
+
+/**
+ * Custom command handler for asynchronously receiving AsyncCommands
+ * through blocking queue operations on a separate thread.
+ */
+class AsyncCommandHandler: public ICommandHandler {
+public:
+ AsyncCommandHandler(const char* id, AsyncCommandQueue& commandQueue);
+
+ CommandStatus Select(const ControlRelayOutputBlock& command, uint16_t index) override final;
+ CommandStatus Operate(const ControlRelayOutputBlock& command, uint16_t index) override final;
+
+ CommandStatus Select(const AnalogOutputInt16& command, uint16_t index) override final;
+ CommandStatus Operate(const AnalogOutputInt16& command, uint16_t index) override final;
+
+ CommandStatus Select(const AnalogOutputInt32& command, uint16_t index) override final;
+ CommandStatus Operate(const AnalogOutputInt32& command, uint16_t index) override final;
+
+ CommandStatus Select(const AnalogOutputFloat32& command, uint16_t index) override final;
+ CommandStatus Operate(const AnalogOutputFloat32& command, uint16_t index) override final;
+
+ CommandStatus Select(const AnalogOutputDouble64& command, uint16_t index) override final;
+ CommandStatus Operate(const AnalogOutputDouble64& command, uint16_t index) override final;
+
+protected:
+ virtual void Start() override;
+ virtual void End() override;
+
+private:
+ const char* id_;
+ AsyncCommandQueue& queue_;
+};
+
+#endif
+
diff --git a/cpp/outstation/AsyncCommandQueue.cpp b/cpp/outstation/AsyncCommandQueue.cpp
new file mode 100644
index 0000000000..a0e1f45cce
--- /dev/null
+++ b/cpp/outstation/AsyncCommandQueue.cpp
@@ -0,0 +1,33 @@
+#include
+#include
+
+#include "AsyncCommand.cpp"
+
+class AsyncCommandQueue {
+public:
+ /**
+ * Blocks on queue until one or more AsyncCommand elements are available.
+ * TODO Handle more than 1 subscriber
+ */
+ std::shared_ptr pop() {
+ std::unique_lock lock(queue_mutex_);
+ while (queue_.empty()) {
+ queue_cond_.wait(lock);
+ }
+ std::shared_ptr command = queue_.front();
+ queue_.pop();
+ return command;
+ }
+
+ void push(AsyncCommand* command) {
+ std::unique_lock lock(queue_mutex_);
+ queue_.push(std::shared_ptr(command));
+ lock.unlock();
+ queue_cond_.notify_all();
+ }
+
+private:
+ std::queue> queue_;
+ std::mutex queue_mutex_;
+ std::condition_variable queue_cond_;
+};
diff --git a/cpp/outstation/JSONTCPSession.cpp b/cpp/outstation/JSONTCPSession.cpp
new file mode 100644
index 0000000000..4199500f87
--- /dev/null
+++ b/cpp/outstation/JSONTCPSession.cpp
@@ -0,0 +1,246 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+using boost::asio::ip::tcp;
+
+using namespace asiodnp3;
+using namespace rapidjson;
+
+/**
+ * JSONTCPSession reads/writes JSON objects to socket that represent Outstation commands.
+ * Protocol is as follows: [int64_t size][size bytes of JSON]
+ */
+class JSONTCPSession: public std::enable_shared_from_this {
+public:
+ JSONTCPSession(tcp::socket socket, std::map& outstations) :
+ socket_(std::move(socket)), outstations_ { outstations } {
+ }
+
+ void start() {
+ read();
+ }
+
+ void write(AsyncCommand* command) {
+ StringBuffer out_json_sb = toJSONStringBuffer(command);
+
+ // Get JSON string length, convert size to int64_t and write in character stream
+ int64_t json_size = out_json_sb.GetSize();
+
+ // Concat raw json_size bytes and JSON bytes
+ char out_data[sizeof(int64_t) + out_json_sb.GetSize()];
+ memcpy(out_data, &json_size, sizeof(int64_t));
+ memcpy(out_data + sizeof(int64_t), out_json_sb.GetString(), out_json_sb.GetSize());
+
+ // Write data to socket
+ auto self(shared_from_this());
+ boost::asio::async_write(socket_, boost::asio::buffer(out_data, sizeof(out_data)), [this, self](boost::system::error_code ec, std::size_t length) {
+ // TODO handle errors
+ });
+ }
+
+ bool is_active() {
+ return socket_.is_open();
+ }
+
+private:
+ // Type constants (AsyncCommand)
+ static constexpr const char* AI16 = "AnalogInt16";
+ static constexpr const char* AI32 = "AnalogInt32";
+ static constexpr const char* AF32 = "AnalogFloat32";
+ static constexpr const char* AD64 = "AnalogDouble64";
+ static constexpr const char* CROB = "ControlRelayOutputBlock";
+
+ static constexpr const char* BIN = "Binary";
+ static constexpr const char* DBBIN = "DoubleBitBinary";
+ static constexpr const char* CI32 = "CounterInt32";
+ static constexpr const char* FCI32 = "FrozenCounterInt32";
+
+ /**
+ * Reads incoming JSON preceded by int64_t with number of bytes to read.
+ */
+ void read() {
+ // TODO add buffer size vs data size validation and error handling
+ auto self(shared_from_this());
+ socket_.async_receive(boost::asio::buffer(buf_, buf_sz), [this, self](boost::system::error_code error, size_t length)
+ {
+ if (!error)
+ {
+ size_t buf_pos = 0;
+ while (buf_pos < length) {
+ size_t buf_rem = length - buf_pos;
+ if (data_pos_ == 0) {
+ /** initialize data size*/
+ memcpy(&data_sz_, &buf_[buf_pos], sizeof(int64_t));
+ if (data_sz_ < 0) {
+ /** TODO warn in log **/
+ data_sz_ = 0;
+ }
+ buf_pos += sizeof(int64_t);
+ buf_rem -= sizeof(int64_t);
+
+ /** initialize data array */
+ data_ = new char[data_sz_+1];
+ data_[data_sz_] = '\0';
+
+ /** copy data from buffer (max(data_sz_,length)) */
+ size_t size = buf_rem > (size_t) data_sz_ ? data_sz_: buf_rem;
+ memcpy(data_, &buf_[buf_pos], size);
+ data_pos_ = size;
+ buf_pos += size;
+ } else if(data_pos_ < (size_t) data_sz_) {
+ /** copy data from buffer (max(data_rem,buf_rem)) */
+ size_t data_rem = data_sz_ - data_pos_;
+ size_t size = buf_rem > data_rem ? data_rem : buf_rem;
+ memcpy(data_ + data_pos_, &buf_[buf_pos], size);
+ buf_pos += size;
+ }
+ /** if data_sz has been transferred, process JSON object */
+ if (data_pos_ == (size_t) data_sz_) {
+ /** apply update from JSON chars, deallocate memory and reset pointers */
+ applyUpdate(data_);
+ delete[] data_;
+ data_pos_ = 0;
+ data_sz_ = 0;
+ }
+ }
+ read();
+ } else if (error.value() == boost::system::errc::no_such_file_or_directory) {
+ /** read() returned EOF, shutdown entire socket **/
+ socket_.shutdown(socket_.shutdown_both);
+ socket_.close();
+ } else {
+ std::cerr << "Error occurred in TCP session " << error << std::endl;
+ }
+ });
+ }
+
+ /**
+ * Takes input AsyncCommand and serializes JSON object to resulting StringBuffer
+ */
+ StringBuffer toJSONStringBuffer(AsyncCommand* command) {
+ StringBuffer sb;
+ Writer writer(sb);
+ writer.StartObject();
+ writer.String("id");
+ writer.String(command->Id());
+ writer.String("index");
+ writer.Int(command->Idx());
+ if (command->AOInt16() != NULL) {
+ writer.String("type");
+ writer.String(AI16);
+ writer.String("value");
+ writer.Int(command->AOInt16()->value);
+ writer.String("status");
+ writer.Int(static_cast(command->AOInt16()->status));
+ } else if (command->AOInt32() != NULL) {
+ writer.String("type");
+ writer.String(AI32);
+ writer.String("value");
+ writer.Int(command->AOInt32()->value);
+ writer.String("status");
+ writer.Int(static_cast(command->AOInt32()->status));
+ } else if (command->AOFloat32() != NULL) {
+ writer.String("type");
+ writer.String(AF32);
+ writer.String("value");
+ writer.Double(command->AOFloat32()->value);
+ writer.String("status");
+ writer.Int(static_cast(command->AOFloat32()->status));
+ } else if (command->AODouble64() != NULL) {
+ writer.String("type");
+ writer.String(AD64);
+ writer.String("value");
+ writer.Double(command->AODouble64()->value);
+ writer.String("status");
+ writer.Int(static_cast(command->AODouble64()->status));
+ } else if (command->CROB() != NULL) {
+ // TODO handle all CROB attributes
+ writer.String("type");
+ writer.String(CROB);
+ writer.String("value");
+ writer.Int(static_cast(command->CROB()->functionCode));
+ writer.String("status");
+ writer.Int(static_cast(command->CROB()->status));
+ } else {
+ std::cerr << "JSONTCPSession: command not recognized" << std::endl;
+ }
+ writer.EndObject();
+ return sb;
+ }
+
+ /**
+ * Takes a char[] JSON object and de-serializes to a new MeasUpdate which is applied against pOustation_
+ */
+ void applyUpdate(char* pchar_json_data) {
+ Document d;
+ d.ParseInsitu(pchar_json_data);
+ if (d.IsObject()) {
+ if (d.HasMember("id") && d["id"].IsString() && d.HasMember("type") && d["type"].IsString() && d.HasMember("index") && d["index"].IsInt()
+ && d.HasMember("value")) {
+ if (outstations_.count(d["id"].GetString())) {
+ IOutstation* pOutstation = outstations_[d["id"].GetString()];
+ MeasUpdate update(pOutstation);
+ const char* type = d["type"].GetString();
+ if (!strcmp(AD64, type)) {
+ if (d["value"].IsDouble()) {
+ update.Update(Analog(d["value"].GetDouble()), d["index"].GetInt());
+ }
+ } else if (!strcmp(BIN, type)) {
+ if (d["value"].IsBool()) {
+ update.Update(Binary(d["value"].GetBool()), d["index"].GetInt());
+ }
+ } else if (!strcmp(CI32, type)) {
+ if (d["value"].IsInt()) {
+ update.Update(Counter(d["value"].GetInt()), d["index"].GetInt());
+ }
+ } else if (!strcmp(FCI32, type)) {
+ if (d["value"].IsInt()) {
+ update.Update(FrozenCounter(d["value"].GetInt()), d["index"].GetInt());
+ }
+ } else {
+ std::cerr << "JSONTCPSession: type attribute[" << type << "]not recognized" << std::endl;
+ }
+ } else {
+ std::cerr << "JSONTCPSession: object id not found in outstations map" << std::endl;
+ }
+ } else {
+ std::cerr << "JSONTCPSession: object missing id or type attribute" << std::endl;
+ }
+ } else {
+ std::cerr << "JSONTCPSession: not an object" << std::endl;
+ }
+ }
+
+ enum {
+ buf_sz = 1024
+
+ };
+ char buf_[buf_sz];
+
+ char* data_ = NULL;
+ size_t data_pos_ = 0;
+ int64_t data_sz_ = 0;
+
+ tcp::socket socket_;
+ std::map outstations_;
+}
+;
diff --git a/cpp/outstation/OutstationApp.cpp b/cpp/outstation/OutstationApp.cpp
new file mode 100644
index 0000000000..2a491e0d6d
--- /dev/null
+++ b/cpp/outstation/OutstationApp.cpp
@@ -0,0 +1,127 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+#include "AsyncCommandQueue.cpp"
+#include "AsyncCommandHandler.cpp"
+#include "OutstationAppConfig.cpp"
+#include "OutstationJSONTCPServer.cpp"
+
+namespace po = boost::program_options;
+
+using namespace opendnp3;
+using namespace openpal;
+
+std::condition_variable g_signal_cond;
+std::mutex g_signal_mutex;
+volatile std::sig_atomic_t g_signal;
+
+void signal_handler(int signal_number) {
+ std::unique_lock lock(g_signal_mutex);
+ g_signal = signal_number;
+ g_signal_cond.notify_all();
+}
+
+/**
+ * Outstation Application providing secondary control channel over TCP (JSON payload).
+ *
+ * See OutstationJSONTCPServer for details of secondary control channel.
+ */
+class OutstationApp {
+public:
+ OutstationApp() :
+ manager_(1) {
+ }
+
+ void start(OutstationAppConfig& config) {
+ manager_.AddLogSubscriber(&ConsoleLogger::Instance());
+
+ IChannel* pChannel = manager_.AddTCPServer(config.getMasterId(), levels::NORMAL, ChannelRetry::Default(), config.getMasterHost(),
+ config.getMasterPort());
+ pChannel->AddStateListener([](ChannelState state)
+ {
+ std::cout << "channel state: " << ChannelStateToString(state) << std::endl;
+ });
+
+ AsyncCommandQueue commandQueue;
+ std::map outstations;
+ for (int i = 0; i < config.getOutstationsCount(); i++) {
+ outstations.insert(std::pair(config.getOutstationId(i), config.configureOutstation(i, pChannel, commandQueue)));
+ }
+
+ /** configurations were successful, start all outstations */
+ for (std::pair outstation : outstations) {
+ outstation.second->Enable();
+ }
+
+ OutstationJSONTCPServer s(io_service_, 3384, outstations, commandQueue);
+ io_service_.run();
+
+ }
+
+ void stop() {
+ io_service_.stop();
+ manager_.Shutdown();
+ }
+private:
+ DNP3Manager manager_;
+ boost::asio::io_service io_service_;
+};
+
+int main(int argc, char* argv[]) {
+ std::string confFile;/** "cpp/test/outstation/demo.yml" **/
+
+ std::signal(SIGINT, signal_handler);
+
+ po::options_description desc("Options");
+ desc.add_options()("conf", po::value(&confFile)->required(), "yaml configuration file")("help", "show help");
+
+ po::variables_map vm;
+ po::store(po::parse_command_line(argc, argv, desc), vm);
+ po::notify(vm);
+
+ if (vm.count("help")) {
+ std::cerr << desc << std::endl;
+ return 1;
+ }
+
+ OutstationAppConfig appConfig(confFile);
+ if (!appConfig.isValid())
+ return 1;
+
+ OutstationApp app;
+ std::thread t([&app, &appConfig] {app.start(appConfig);});
+ t.detach();
+
+ std::unique_lock lock(g_signal_mutex);
+ while (!g_signal) {
+ g_signal_cond.wait(lock);
+ }
+ app.stop();
+ return 0;
+}
diff --git a/cpp/outstation/OutstationAppConfig.cpp b/cpp/outstation/OutstationAppConfig.cpp
new file mode 100644
index 0000000000..7905c3816c
--- /dev/null
+++ b/cpp/outstation/OutstationAppConfig.cpp
@@ -0,0 +1,409 @@
+#include
+
+#include "opendnp3/app/MeasurementTypes.h"
+
+using namespace asiodnp3;
+
+/**
+ * Class responsible for parsing OutstationApp YAML Configuration
+ * and configuring appropriate OpenDNP3 Outstation objects.
+ *
+ * TODO implement a better String<->Enum mapping technique for variations/metadata/eventVariations
+ */
+class OutstationAppConfig {
+
+public:
+ OutstationAppConfig(std::string confFile) {
+ configNode_ = YAML::LoadFile(confFile);
+ }
+
+ /**
+ * Validates that config contains needed elements for basic configuration.
+ * Does not validate correctness of config elements.
+ */
+ bool isValid() {
+ bool valid = configNode_["master"] && configNode_["master"]["id"] && configNode_["master"]["host"] && configNode_["master"]["port"]
+ && configNode_["outstations"] && configNode_["outstations"][0] && configNode_["outstations"][0]["id"]
+ && configNode_["outstations"][0]["localAddr"] && configNode_["outstations"][0]["remoteAddr"];
+ if (!valid)
+ std::cerr << "Invalid YAML config, check master (id, host, port) and outstations (id)" << std::endl;
+ return valid;
+ }
+
+ const char* getMasterId() {
+ return ((std::string) configNode_["master"]["id"].as()).c_str();
+ }
+
+ const char* getMasterHost() {
+ return ((std::string) configNode_["master"]["host"].as()).c_str();
+ }
+
+ int getMasterPort() {
+ return configNode_["master"]["port"].as();
+ }
+
+ int getOutstationsCount() {
+ return configNode_["outstations"].size();
+ }
+
+ std::string getOutstationId(int idx) {
+ return configNode_["outstations"][idx]["id"].as();
+ }
+
+ IOutstation* configureOutstation(int idx, IChannel* pChannel, AsyncCommandQueue& queue) {
+ /** TODO consider decoupling dnp3 from configuration class **/
+ YAML::Node outstationNode = configNode_["outstations"][idx];
+ int binaries, doubleBinaries, analogs, counters, frozenCounters, binaryOutputStatii, analogOutputStatii, timeAndIntervals;
+ if (configNode_["measDB"]) {
+ binaries = configNode_["measDB"]["binaries"].size();
+ doubleBinaries = configNode_["measDB"]["doubleBinaries"].size();
+ analogs = configNode_["measDB"]["analogs"].size();
+ counters = configNode_["measDB"]["counters"].size();
+ frozenCounters = configNode_["measDB"]["frozenCounters"].size();
+ binaryOutputStatii = configNode_["measDB"]["binaryOutputStatii"].size();
+ analogOutputStatii = configNode_["measDB"]["analogOutputStatii"].size();
+ timeAndIntervals = configNode_["measDB"]["timeAndIntervals"].size();
+ } else {
+ /** using default demo configuration DatabaseTemplate::AllTypes(10), EventBufferConfig::AllTypes(10); **/
+ binaries = doubleBinaries = analogs = counters = frozenCounters = binaryOutputStatii = analogOutputStatii = timeAndIntervals = 10;
+ }
+ const char* outstationId = outstationNode["id"].as().c_str();
+ AsyncCommandHandler* handler = new AsyncCommandHandler(outstationId, queue);
+ IOutstation* pOutstation = pChannel->AddOutstation(outstationId, *handler, DefaultOutstationApplication::Instance(),
+ createStackConfig(outstationNode, binaries, doubleBinaries, analogs, counters, frozenCounters, binaryOutputStatii, analogOutputStatii,
+ timeAndIntervals));
+ configureDatabaseConfigView(outstationNode, pOutstation->GetConfigView());
+ return pOutstation;
+ }
+
+private:
+ OutstationStackConfig createStackConfig(YAML::Node& outstationNode, int binaries, int doubleBinaries, int analogs, int counters, int frozenCounters,
+ int binaryOutputStatii, int analogOutputStatii, int timeAndIntervals) {
+ OutstationStackConfig stackConfig;
+ stackConfig.link.LocalAddr = outstationNode["localAddr"].as();
+ stackConfig.link.RemoteAddr = outstationNode["remoteAddr"].as();
+ stackConfig.dbTemplate = DatabaseTemplate(binaries, doubleBinaries, analogs, counters, frozenCounters, binaryOutputStatii, analogOutputStatii,
+ timeAndIntervals);
+ stackConfig.outstation.eventBufferConfig = EventBufferConfig(binaries, doubleBinaries, analogs, counters, frozenCounters, binaryOutputStatii,
+ analogOutputStatii, timeAndIntervals);
+ return stackConfig;
+ }
+
+ void configureDatabaseConfigView(YAML::Node& outstationNode, DatabaseConfigView view) {
+ if (outstationNode["measDB"]) {
+ configureBinaries(outstationNode, view.binaries);
+ configureDoubleBinaries(outstationNode, view.doubleBinaries);
+ configureAnalogs(outstationNode, view.analogs);
+ configureCounters(outstationNode, view.counters);
+ configureFrozenCounters(outstationNode, view.frozenCounters);
+ configureBinaryOutputStatii(outstationNode, view.binaryOutputStatii);
+ configureAnalogOutputStatii(outstationNode, view.analogOutputStatii);
+ configureTimeAndIntervals(outstationNode, view.timeAndIntervals);
+ }
+ }
+
+ void configureBinaries(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& binaries) {
+ const char* type = "binaries";
+ for (int i = 0; i < binaries.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticBinaryVariation::Group1Var1") {
+ binaries[i].variation = StaticBinaryVariation::Group1Var1;
+ } else if (variation == "StaticBinaryVariation::Group1Var2") {
+ binaries[i].variation = StaticBinaryVariation::Group1Var2;
+ }
+ }
+ if (hasMetadata(outstationNode, type, i)) {
+ if (hasMetadataClazz(outstationNode, type, i)) {
+ binaries[i].metadata.clazz = getPointClass(getMetadataClazz(outstationNode, type, i));
+ }
+ if (hasMetadataVariation(outstationNode, type, i)) {
+ std::string metadataVariation = getMetadataVariation(outstationNode, type, i);
+ if (metadataVariation == "EventBinaryVariation::Group2Var1") {
+ binaries[i].metadata.variation = EventBinaryVariation::Group2Var1;
+ } else if (metadataVariation == "EventBinaryVariation::Group2Var2") {
+ binaries[i].metadata.variation = EventBinaryVariation::Group2Var2;
+ } else if (metadataVariation == "EventBinaryVariation::Group2Var3") {
+ binaries[i].metadata.variation = EventBinaryVariation::Group2Var3;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataVariation[" << metadataVariation << "]" << std::endl;
+ }
+ }
+ }
+ }
+ }
+
+ void configureDoubleBinaries(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& doubleBinaries) {
+ const char* type = "doubleBinaries";
+ for (int i = 0; i < doubleBinaries.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticDoubleBinaryVariation::Group3Var2") {
+ doubleBinaries[i].variation = StaticDoubleBinaryVariation::Group3Var2;
+ }
+ }
+ if (hasMetadata(outstationNode, type, i)) {
+ if (hasMetadataClazz(outstationNode, type, i)) {
+ doubleBinaries[i].metadata.clazz = getPointClass(getMetadataClazz(outstationNode, type, i));
+ }
+ if (hasMetadataVariation(outstationNode, type, i)) {
+ std::string metadataVariation = getMetadataVariation(outstationNode, type, i);
+ if (metadataVariation == "EventDoubleBinaryVariation::Group4Var1") {
+ doubleBinaries[i].metadata.variation = EventDoubleBinaryVariation::Group4Var1;
+ } else if (metadataVariation == "EventDoubleBinaryVariation::Group4Var2") {
+ doubleBinaries[i].metadata.variation = EventDoubleBinaryVariation::Group4Var2;
+ } else if (metadataVariation == "EventDoubleBinaryVariation::Group4Var3") {
+ doubleBinaries[i].metadata.variation = EventDoubleBinaryVariation::Group4Var3;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataVariation[" << metadataVariation << "]" << std::endl;
+ }
+ }
+ }
+ }
+ }
+
+ void configureAnalogs(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& analogs) {
+ const char* type = "analogs";
+ for (int i = 0; i < analogs.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticAnalogVariation::Group30Var1") {
+ analogs[i].variation = StaticAnalogVariation::Group30Var1;
+ } else if (variation == "StaticAnalogVariation::Group30Var2") {
+ analogs[i].variation = StaticAnalogVariation::Group30Var2;
+ } else if (variation == "StaticAnalogVariation::Group30Var3") {
+ analogs[i].variation = StaticAnalogVariation::Group30Var3;
+ } else if (variation == "StaticAnalogVariation::Group30Var4") {
+ analogs[i].variation = StaticAnalogVariation::Group30Var4;
+ } else if (variation == "StaticAnalogVariation::Group30Var5") {
+ analogs[i].variation = StaticAnalogVariation::Group30Var5;
+ } else if (variation == "StaticAnalogVariation::Group30Var6") {
+ analogs[i].variation = StaticAnalogVariation::Group30Var6;
+ }
+ }
+ if (hasMetadata(outstationNode, type, i)) {
+ if (hasMetadataClazz(outstationNode, type, i)) {
+ analogs[i].metadata.clazz = getPointClass(getMetadataClazz(outstationNode, type, i));
+ }
+ if (hasMetadataVariation(outstationNode, type, i)) {
+ std::string metadataVariation = getMetadataVariation(outstationNode, type, i);
+ if (metadataVariation == "EventAnalogVariation::Group32Var1") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var1;
+ } else if (metadataVariation == "EventAnalogVariation::Group32Var2") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var2;
+ } else if (metadataVariation == "EventAnalogVariation::Group32Var3") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var3;
+ } else if (metadataVariation == "EventAnalogVariation::Group32Var4") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var4;
+ } else if (metadataVariation == "EventAnalogVariation::Group32Var5") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var5;
+ } else if (metadataVariation == "EventAnalogVariation::Group32Var6") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var6;
+ } else if (metadataVariation == "EventAnalogVariation::Group32Var7") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var7;
+ } else if (metadataVariation == "EventAnalogVariation::Group32Var8") {
+ analogs[i].metadata.variation = EventAnalogVariation::Group32Var8;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataVariation[" << metadataVariation << "]" << std::endl;
+ }
+ }
+ }
+ }
+ }
+ void configureCounters(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& counters) {
+ const char* type = "counters";
+ for (int i = 0; i < counters.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticCounterVariation::Group20Var1") {
+ counters[i].variation = StaticCounterVariation::Group20Var1;
+ } else if (variation == "StaticCounterVariation::Group20Var2") {
+ counters[i].variation = StaticCounterVariation::Group20Var2;
+ } else if (variation == "StaticCounterVariation::Group20Var5") {
+ counters[i].variation = StaticCounterVariation::Group20Var5;
+ } else if (variation == "StaticCounterVariation::Group20Var6") {
+ counters[i].variation = StaticCounterVariation::Group20Var6;
+ }
+ }
+ if (hasMetadata(outstationNode, type, i)) {
+ if (hasMetadataClazz(outstationNode, type, i)) {
+ counters[i].metadata.clazz = getPointClass(getMetadataClazz(outstationNode, type, i));
+ }
+ if (hasMetadataVariation(outstationNode, type, i)) {
+ std::string metadataVariation = getMetadataVariation(outstationNode, type, i);
+ if (metadataVariation == "EventCounterVariation::Group22Var1") {
+ counters[i].metadata.variation = EventCounterVariation::Group22Var1;
+ } else if (metadataVariation == "EventCounterVariation::Group22Var2") {
+ counters[i].metadata.variation = EventCounterVariation::Group22Var2;
+ } else if (metadataVariation == "EventCounterVariation::Group22Var5") {
+ counters[i].metadata.variation = EventCounterVariation::Group22Var5;
+ } else if (metadataVariation == "EventCounterVariation::Group22Var6") {
+ counters[i].metadata.variation = EventCounterVariation::Group22Var6;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataVariation[" << metadataVariation << "]" << std::endl;
+ }
+ }
+ }
+ }
+ }
+ void configureFrozenCounters(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& frozenCounters) {
+ const char* type = "frozenCounters";
+ for (int i = 0; i < frozenCounters.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticFrozenCounterVariation::Group21Var1") {
+ frozenCounters[i].variation = StaticFrozenCounterVariation::Group21Var1;
+ } else if (variation == "StaticFrozenCounterVariation::Group21Var2") {
+ frozenCounters[i].variation = StaticFrozenCounterVariation::Group21Var2;
+ } else if (variation == "StaticFrozenCounterVariation::Group21Var5") {
+ frozenCounters[i].variation = StaticFrozenCounterVariation::Group21Var5;
+ } else if (variation == "StaticFrozenCounterVariation::Group21Var6") {
+ frozenCounters[i].variation = StaticFrozenCounterVariation::Group21Var6;
+ } else if (variation == "StaticFrozenCounterVariation::Group21Var9") {
+ frozenCounters[i].variation = StaticFrozenCounterVariation::Group21Var9;
+ } else if (variation == "StaticFrozenCounterVariation::Group21Var10") {
+ frozenCounters[i].variation = StaticFrozenCounterVariation::Group21Var10;
+ }
+ }
+ if (hasMetadata(outstationNode, type, i)) {
+ if (hasMetadataClazz(outstationNode, type, i)) {
+ frozenCounters[i].metadata.clazz = getPointClass(getMetadataClazz(outstationNode, type, i));
+ }
+ if (hasMetadataVariation(outstationNode, type, i)) {
+ std::string metadataVariation = getMetadataVariation(outstationNode, type, i);
+ if (metadataVariation == "EventFrozenCounterVariation::Group23Var1") {
+ frozenCounters[i].metadata.variation = EventFrozenCounterVariation::Group23Var1;
+ } else if (metadataVariation == "EventFrozenCounterVariation::Group23Var2") {
+ frozenCounters[i].metadata.variation = EventFrozenCounterVariation::Group23Var2;
+ } else if (metadataVariation == "EventFrozenCounterVariation::Group23Var5") {
+ frozenCounters[i].metadata.variation = EventFrozenCounterVariation::Group23Var5;
+ } else if (metadataVariation == "EventFrozenCounterVariation::Group23Var6") {
+ frozenCounters[i].metadata.variation = EventFrozenCounterVariation::Group23Var6;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataVariation[" << metadataVariation << "]" << std::endl;
+ }
+ }
+ }
+ }
+ }
+ void configureBinaryOutputStatii(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& binaryOutputStatii) {
+ const char* type = "binaryOutputStatii";
+ for (int i = 0; i < binaryOutputStatii.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticBinaryOutputStatusVariation::Group10Var2") {
+ binaryOutputStatii[i].variation = StaticBinaryOutputStatusVariation::Group10Var2;
+ }
+ }
+ if (hasMetadata(outstationNode, type, i)) {
+ if (hasMetadataClazz(outstationNode, type, i)) {
+ binaryOutputStatii[i].metadata.clazz = getPointClass(getMetadataClazz(outstationNode, type, i));
+ }
+ if (hasMetadataVariation(outstationNode, type, i)) {
+ std::string metadataVariation = getMetadataVariation(outstationNode, type, i);
+ if (metadataVariation == "EventBinaryOutputStatusVariation::Group11Var1") {
+ binaryOutputStatii[i].metadata.variation = EventBinaryOutputStatusVariation::Group11Var1;
+ } else if (metadataVariation == "EventBinaryOutputStatusVariation::Group11Var2") {
+ binaryOutputStatii[i].metadata.variation = EventBinaryOutputStatusVariation::Group11Var2;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataVariation[" << metadataVariation << "]" << std::endl;
+ }
+ }
+ }
+ }
+ }
+ void configureAnalogOutputStatii(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& analogOutputStatii) {
+ const char* type = "analogOutputStatii";
+ for (int i = 0; i < analogOutputStatii.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticAnalogOutputStatusVariation::Group40Var1") {
+ analogOutputStatii[i].variation = StaticAnalogOutputStatusVariation::Group40Var1;
+ } else if (variation == "StaticAnalogOutputStatusVariation::Group40Var2") {
+ analogOutputStatii[i].variation = StaticAnalogOutputStatusVariation::Group40Var2;
+ } else if (variation == "StaticAnalogOutputStatusVariation::Group40Var3") {
+ analogOutputStatii[i].variation = StaticAnalogOutputStatusVariation::Group40Var3;
+ } else if (variation == "StaticAnalogOutputStatusVariation::Group40Var4") {
+ analogOutputStatii[i].variation = StaticAnalogOutputStatusVariation::Group40Var4;
+ }
+ }
+ if (hasMetadata(outstationNode, type, i)) {
+ if (hasMetadataClazz(outstationNode, type, i)) {
+ analogOutputStatii[i].metadata.clazz = getPointClass(getMetadataClazz(outstationNode, type, i));
+ }
+ if (hasMetadataVariation(outstationNode, type, i)) {
+ std::string metadataVariation = getMetadataVariation(outstationNode, type, i);
+ if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var1") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var1;
+ } else if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var2") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var2;
+ } else if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var3") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var3;
+ } else if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var4") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var4;
+ } else if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var5") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var5;
+ } else if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var6") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var6;
+ } else if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var7") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var7;
+ } else if (metadataVariation == "EventAnalogOutputStatusVariation::Group42Var8") {
+ analogOutputStatii[i].metadata.variation = EventAnalogOutputStatusVariation::Group42Var8;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataVariation[" << metadataVariation << "]" << std::endl;
+ }
+ }
+ }
+ }
+ }
+ void configureTimeAndIntervals(YAML::Node& outstationNode, openpal::ArrayView, uint16_t>& timeAndIntervals) {
+ const char* type = "timeAndIntervals";
+ for (int i = 0; i < timeAndIntervals.Size(); i++) {
+ if (hasVariation(outstationNode, type, i)) {
+ std::string variation = getVariation(outstationNode, type, i);
+ if (variation == "StaticTimeAndIntervalVariation::Group50Var4") {
+ timeAndIntervals[i].variation = StaticTimeAndIntervalVariation::Group50Var4;
+ }
+ }
+ }
+ }
+
+ bool hasVariation(YAML::Node& outstationNode, const char* type, int idx) {
+ return outstationNode["measDB"][type][idx]["variation"];
+ }
+ std::string getVariation(YAML::Node& outstationNode, const char* type, int idx) {
+ return outstationNode["measDB"][type][idx]["variation"].as();
+ }
+ bool hasMetadata(YAML::Node& outstationNode, const char* type, int idx) {
+ return outstationNode["measDB"][type][idx]["metadata"];
+ }
+ bool hasMetadataClazz(YAML::Node& outstationNode, const char* type, int idx) {
+ return outstationNode["measDB"][type][idx]["metadata"]["clazz"];
+ }
+ std::string getMetadataClazz(YAML::Node& outstationNode, const char* type, int idx) {
+ return outstationNode["measDB"][type][idx]["metadata"]["clazz"].as();
+ }
+ bool hasMetadataVariation(YAML::Node& outstationNode, const char* type, int idx) {
+ return outstationNode["measDB"][type][idx]["metadata"]["variation"];
+ }
+ std::string getMetadataVariation(YAML::Node& outstationNode, const char* type, int idx) {
+ return outstationNode["measDB"][type][idx]["metadata"]["variation"].as();
+ }
+ PointClass getPointClass(std::string metadataClazz) {
+ if (metadataClazz == "PointClass::Class0") {
+ return PointClass::Class0;
+ } else if (metadataClazz == "PointClass::Class1") {
+ return PointClass::Class1;
+ } else if (metadataClazz == "PointClass::Class2") {
+ return PointClass::Class2;
+ } else if (metadataClazz == "PointClass::Class3") {
+ return PointClass::Class3;
+ } else {
+ std::cerr << "OutstationAppConfig: unable to map metadataClazz[" << metadataClazz << "] defaulting to PointClass::Class1" << std::endl;
+ return PointClass::Class1;
+ }
+ }
+
+ YAML::Node configNode_;
+}
+;
diff --git a/cpp/outstation/OutstationJSONTCPServer.cpp b/cpp/outstation/OutstationJSONTCPServer.cpp
new file mode 100644
index 0000000000..d81501a9c8
--- /dev/null
+++ b/cpp/outstation/OutstationJSONTCPServer.cpp
@@ -0,0 +1,80 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+
+#include "JSONTCPSession.cpp"
+
+using namespace asiodnp3;
+
+/**
+ * OutstationJSONTCPServer handles TCP client sessions over which JSON is transferred.
+ *
+ * All client sessions are tracked for broadcast messages from the AsyncCommandHandler (i.e. Master server)
+ * Clients may send JSON commands which are converted to MeasUpdate by JSONTCPSession.
+ *
+ */
+class OutstationJSONTCPServer {
+public:
+ OutstationJSONTCPServer(boost::asio::io_service& io_service, short port, std::map& outstations, AsyncCommandQueue& queue) :
+ outstations_ { outstations }, queue_(queue), acceptor_(io_service, tcp::endpoint(tcp::v4(), port)), socket_(io_service) {
+ start();
+ }
+
+private:
+ void start() {
+ // TODO shutdown thread correctly on SIGINT
+ std::thread t([this] {subscribe();});
+ t.detach();
+ accept();
+ }
+
+ void subscribe() {
+ while (true) {
+ std::shared_ptr command = queue_.pop();
+ std::set>::iterator it;
+ for (it = sessions_.begin(); it != sessions_.end();) {
+ if (it->get()->is_active()) {
+ it->get()->write(command.get());
+ it++;
+ } else {
+ sessions_.erase(it++);
+ }
+ }
+ }
+ }
+
+ void accept() {
+ acceptor_.async_accept(socket_, [this](boost::system::error_code error)
+ {
+ if (!error)
+ {
+ std::shared_ptr pSession = std::make_shared(std::move(socket_), outstations_);
+ sessions_.insert(pSession);
+ pSession->start();
+ } else {
+ std::cerr << "Unable to create TCP Session " << error << std::endl;
+ }
+ accept();
+ });
+ }
+
+ std::map outstations_;
+ std::set> sessions_;
+ AsyncCommandQueue& queue_;
+
+ tcp::acceptor acceptor_;
+ tcp::socket socket_;
+};
diff --git a/cpp/tests/outstation/demo.yml b/cpp/tests/outstation/demo.yml
new file mode 100644
index 0000000000..911d60fdca
--- /dev/null
+++ b/cpp/tests/outstation/demo.yml
@@ -0,0 +1,35 @@
+master:
+ id: demomaster
+ levels: normal
+ channelRetry: default
+ host: 0.0.0.0
+ port: 20000
+jsonServer:
+ port: 3384
+outstations:
+ - id: demostation
+ localAddr: 10
+ remoteAddr: 1
+ measDB:
+ analogs:
+ - idx: 0
+ variation: StaticAnalogVariation::Group30Var5
+ metadata:
+ - clazz: PointClass::Class2
+ - variation: EventAnalogVariation::Group32Var7
+ - idx: 1
+ - idx: 2
+ binaries:
+ - idx: 0
+ doubleBinaries:
+ - idx: 0
+ counters:
+ - idx: 0
+ frozenCounters:
+ - idx: 0
+ binaryOutputStatii:
+ - idx: 0
+ analogOutputStatii:
+ - idx: 0
+ timeAndIntervals:
+ - idx: 0
diff --git a/cpp/tests/outstation/jsontcp-sample.bin b/cpp/tests/outstation/jsontcp-sample.bin
new file mode 100644
index 0000000000..fa517f0e09
Binary files /dev/null and b/cpp/tests/outstation/jsontcp-sample.bin differ
diff --git a/cpp/tests/outstation/jsontcp-test.sh b/cpp/tests/outstation/jsontcp-test.sh
new file mode 100755
index 0000000000..2ac56e1cc2
--- /dev/null
+++ b/cpp/tests/outstation/jsontcp-test.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+SCRIPT=$(readlink -f $0)
+SCRIPTPATH=`dirname $SCRIPT`
+
+cat $SCRIPTPATH/jsontcp-sample.bin | nc localhost 3384
diff --git a/cpp/tests/outstation/src/OutstationAppTests.cpp b/cpp/tests/outstation/src/OutstationAppTests.cpp
new file mode 100644
index 0000000000..b1594cc9a7
--- /dev/null
+++ b/cpp/tests/outstation/src/OutstationAppTests.cpp
@@ -0,0 +1,46 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CATCH_CONFIG_MAIN
+#include
+#include
+
+#include
+
+#include "opendnp3/app/ControlRelayOutputBlock.cpp"
+#include "opendnp3/gen/ControlCode.cpp"
+#include "opendnp3/app/AnalogOutput.h"
+
+#include "../../../outstation/AsyncCommand.cpp"
+
+#define SUITE(name) "TestOutstationApp - " name
+
+using namespace std;
+
+TEST_CASE(SUITE("AsyncCommand"))
+{
+ ControlRelayOutputBlock* crob = new ControlRelayOutputBlock(ControlCode::LATCH_ON, (uint8_t)1, (uint32_t) 2, (uint32_t) 3, CommandStatus::SUCCESS);
+ AsyncCommand* ac = new AsyncCommand(crob, (char *) "testout", 123);
+ REQUIRE(ac->AOInt16() == NULL);
+}
+
+TEST_CASE(SUITE("YAMLConfig"))
+{
+ YAML::Node config = YAML::LoadFile("cpp/tests/outstation/demo.yml");
+ REQUIRE(config["master"]);
+ REQUIRE(config["outstations"]);
+ std::cout << config << std::endl;
+}
+
+
diff --git a/thirdparty/asio b/thirdparty/asio
new file mode 160000
index 0000000000..c466dc46d5
--- /dev/null
+++ b/thirdparty/asio
@@ -0,0 +1 @@
+Subproject commit c466dc46d55755d38ee1831e95207d6b329c4976
diff --git a/thirdparty/rapidjson b/thirdparty/rapidjson
new file mode 160000
index 0000000000..3d5848a7cd
--- /dev/null
+++ b/thirdparty/rapidjson
@@ -0,0 +1 @@
+Subproject commit 3d5848a7cd3367c5cb451c6493165b7745948308
diff --git a/thirdparty/yaml-cpp b/thirdparty/yaml-cpp
new file mode 160000
index 0000000000..998d7bf31e
--- /dev/null
+++ b/thirdparty/yaml-cpp
@@ -0,0 +1 @@
+Subproject commit 998d7bf31e1bab4e45221c28fad8e00ddcf8855e
| | | | | | | |