From 471cef723f7a52d6edb43fc6cf743a944056882c Mon Sep 17 00:00:00 2001 From: Wolfgang Wallner Date: Thu, 14 Apr 2016 20:58:07 +0200 Subject: [PATCH] Initial revision. --- COPYING | 674 +++++ .../SimpleNetwork/AsymmetryExperiment.ods | Bin 0 -> 17330 bytes .../SimpleNetwork/PI_Parameters.ods | Bin 0 -> 32933 bytes Docs/Usage_with_libPLN/Usage_with_libPLN.odt | Bin 0 -> 95769 bytes Docs/Usage_with_libPLN/Usage_with_libPLN.pdf | Bin 0 -> 158500 bytes .../Usage_with_libPLN/images/Dependencies.dia | Bin 0 -> 2202 bytes .../Usage_with_libPLN/images/Dependencies.png | Bin 0 -> 31095 bytes Makefile.vc | 22 + Readme.md | 211 ++ Tools/Bash_Scripts/CleanUpSources.sh | 62 + doc/img/BC8_Port2_StateDecisions.png | Bin 0 -> 17996 bytes doc/img/MessageSymbols.png | Bin 0 -> 77633 bytes doc/img/NodeSymbols.png | Bin 0 -> 64121 bytes doc/img/NodeSymbols_ExampleNetwork.png | Bin 0 -> 26128 bytes doc/img/SyncInterval_Mean.png | Bin 0 -> 9982 bytes doxy.cfg | 2365 +++++++++++++++++ images/PTP/Base/Blank/Blank.xcf | Bin 0 -> 4321 bytes images/PTP/Base/Blank/License.txt | 2 + .../Components/Clock/ConstantDriftClock.png | Bin 0 -> 5502 bytes images/PTP/Components/Clock/DigitalClock.png | Bin 0 -> 4577 bytes images/PTP/Components/Clock/DigitalClock.xcf | Bin 0 -> 18918 bytes images/PTP/Components/Clock/License.txt | 1 + images/PTP/Components/Clock/PerfectClock.png | Bin 0 -> 5245 bytes images/PTP/Components/Clock/RealClock.png | Bin 0 -> 5712 bytes images/PTP/Components/Clock/SineClock.png | Bin 0 -> 5515 bytes .../PTP/Components/ClockServo/ClockServo.png | Bin 0 -> 3830 bytes images/PTP/Components/ClockServo/License.txt | 2 + .../Components/ClockServo/PI_ClockServo.dia | Bin 0 -> 1292 bytes .../Components/ClockServo/PI_ClockServo.png | Bin 0 -> 3704 bytes .../Components/ClockServo/PI_ClockServo.xcf | Bin 0 -> 8302 bytes images/PTP/Components/Delayer/Delayer.png | Bin 0 -> 2763 bytes images/PTP/Components/Delayer/License.txt | 1 + images/PTP/Components/Encap/Encap.png | Bin 0 -> 5879 bytes images/PTP/Components/Encap/License.txt | 1 + .../InternalModule/InternalModule.png | Bin 0 -> 5227 bytes .../PTP/Components/InternalModule/License.txt | 1 + images/PTP/Components/MAC/License.txt | 1 + images/PTP/Components/MAC/MAC.png | Bin 0 -> 5960 bytes images/PTP/Components/NIC/License.txt | 1 + images/PTP/Components/NIC/NIC.png | Bin 0 -> 5030 bytes images/PTP/Components/NIC_Ctrl/License.txt | 1 + images/PTP/Components/NIC_Ctrl/NIC_Ctrl.png | Bin 0 -> 2568 bytes images/PTP/Components/PHY/License.txt | 1 + images/PTP/Components/PHY/PHY.png | Bin 0 -> 4064 bytes .../PTP_EthernetMapping/License.txt | 1 + .../PTP_EthernetMapping.png | Bin 0 -> 5833 bytes .../PTP_EthernetMapping.xcf | Bin 0 -> 12423 bytes images/PTP/Components/PTP_Stack/License.txt | 1 + images/PTP/Components/PTP_Stack/PTP_Stack.png | Bin 0 -> 7356 bytes images/PTP/Components/RelayUnit/License.txt | 1 + images/PTP/Components/RelayUnit/RelayUnit.png | Bin 0 -> 5384 bytes images/PTP/LICENSE.txt | 12 + images/PTP/Messages/Announce.png | Bin 0 -> 3243 bytes images/PTP/Messages/DelayReq.png | Bin 0 -> 2562 bytes images/PTP/Messages/DelayResp.png | Bin 0 -> 3384 bytes images/PTP/Messages/License.txt | 13 + images/PTP/Messages/Management.png | Bin 0 -> 2765 bytes images/PTP/Messages/PDelayReq.png | Bin 0 -> 2388 bytes images/PTP/Messages/PDelayResp.png | Bin 0 -> 3184 bytes images/PTP/Messages/PDelayRespFU.png | Bin 0 -> 1692 bytes images/PTP/Messages/Signaling.png | Bin 0 -> 3088 bytes images/PTP/Messages/Sync.png | Bin 0 -> 2296 bytes images/PTP/Messages/SyncFU.png | Bin 0 -> 1708 bytes images/PTP/Nodes/PTP_Nodes/B_E_1_M1.png | Bin 0 -> 4972 bytes images/PTP/Nodes/PTP_Nodes/B_E_1_M2.png | Bin 0 -> 4444 bytes images/PTP/Nodes/PTP_Nodes/B_E_1_M3.png | Bin 0 -> 4856 bytes images/PTP/Nodes/PTP_Nodes/B_E_1_SO.png | Bin 0 -> 3828 bytes images/PTP/Nodes/PTP_Nodes/B_E_2_M1.png | Bin 0 -> 5071 bytes images/PTP/Nodes/PTP_Nodes/B_E_2_M2.png | Bin 0 -> 4550 bytes images/PTP/Nodes/PTP_Nodes/B_E_2_M3.png | Bin 0 -> 4957 bytes images/PTP/Nodes/PTP_Nodes/B_E_2_SO.png | Bin 0 -> 3948 bytes images/PTP/Nodes/PTP_Nodes/B_P_1_M1.png | Bin 0 -> 5117 bytes images/PTP/Nodes/PTP_Nodes/B_P_1_M2.png | Bin 0 -> 4597 bytes images/PTP/Nodes/PTP_Nodes/B_P_1_M3.png | Bin 0 -> 5001 bytes images/PTP/Nodes/PTP_Nodes/B_P_1_SO.png | Bin 0 -> 3972 bytes images/PTP/Nodes/PTP_Nodes/B_P_2_M1.png | Bin 0 -> 5220 bytes images/PTP/Nodes/PTP_Nodes/B_P_2_M2.png | Bin 0 -> 4702 bytes images/PTP/Nodes/PTP_Nodes/B_P_2_M3.png | Bin 0 -> 5103 bytes images/PTP/Nodes/PTP_Nodes/B_P_2_SO.png | Bin 0 -> 4092 bytes images/PTP/Nodes/PTP_Nodes/Generic.png | Bin 0 -> 1723 bytes images/PTP/Nodes/PTP_Nodes/Generic.xcf | Bin 0 -> 6376 bytes images/PTP/Nodes/PTP_Nodes/License.txt | 1 + images/PTP/Nodes/PTP_Nodes/N_E_1_M1.png | Bin 0 -> 5065 bytes images/PTP/Nodes/PTP_Nodes/N_E_1_M2.png | Bin 0 -> 4533 bytes images/PTP/Nodes/PTP_Nodes/N_E_1_M3.png | Bin 0 -> 4951 bytes images/PTP/Nodes/PTP_Nodes/N_E_1_SO.png | Bin 0 -> 3916 bytes images/PTP/Nodes/PTP_Nodes/N_E_2_M1.png | Bin 0 -> 5170 bytes images/PTP/Nodes/PTP_Nodes/N_E_2_M2.png | Bin 0 -> 4639 bytes images/PTP/Nodes/PTP_Nodes/N_E_2_M3.png | Bin 0 -> 5046 bytes images/PTP/Nodes/PTP_Nodes/N_E_2_SO.png | Bin 0 -> 4032 bytes images/PTP/Nodes/PTP_Nodes/N_P_1_M1.png | Bin 0 -> 5209 bytes images/PTP/Nodes/PTP_Nodes/N_P_1_M2.png | Bin 0 -> 4686 bytes images/PTP/Nodes/PTP_Nodes/N_P_1_M3.png | Bin 0 -> 5095 bytes images/PTP/Nodes/PTP_Nodes/N_P_1_SO.png | Bin 0 -> 4063 bytes images/PTP/Nodes/PTP_Nodes/N_P_2_M1.png | Bin 0 -> 5315 bytes images/PTP/Nodes/PTP_Nodes/N_P_2_M2.png | Bin 0 -> 4792 bytes images/PTP/Nodes/PTP_Nodes/N_P_2_M3.png | Bin 0 -> 5196 bytes images/PTP/Nodes/PTP_Nodes/N_P_2_SO.png | Bin 0 -> 4179 bytes images/PTP/Nodes/PTP_Nodes/Node.xcf | Bin 0 -> 51816 bytes images/PTP/Nodes/PTP_Nodes/T_E_1_M1.png | Bin 0 -> 4756 bytes images/PTP/Nodes/PTP_Nodes/T_E_1_M2.png | Bin 0 -> 4253 bytes images/PTP/Nodes/PTP_Nodes/T_E_1_M3.png | Bin 0 -> 4642 bytes images/PTP/Nodes/PTP_Nodes/T_E_1_SO.png | Bin 0 -> 3622 bytes images/PTP/Nodes/PTP_Nodes/T_E_2_M1.png | Bin 0 -> 4858 bytes images/PTP/Nodes/PTP_Nodes/T_E_2_M2.png | Bin 0 -> 4360 bytes images/PTP/Nodes/PTP_Nodes/T_E_2_M3.png | Bin 0 -> 4741 bytes images/PTP/Nodes/PTP_Nodes/T_E_2_SO.png | Bin 0 -> 3738 bytes images/PTP/Nodes/PTP_Nodes/T_P_1_M1.png | Bin 0 -> 4899 bytes images/PTP/Nodes/PTP_Nodes/T_P_1_M2.png | Bin 0 -> 4395 bytes images/PTP/Nodes/PTP_Nodes/T_P_1_M3.png | Bin 0 -> 4778 bytes images/PTP/Nodes/PTP_Nodes/T_P_1_SO.png | Bin 0 -> 3763 bytes images/PTP/Nodes/PTP_Nodes/T_P_2_M1.png | Bin 0 -> 5008 bytes images/PTP/Nodes/PTP_Nodes/T_P_2_M2.png | Bin 0 -> 4499 bytes images/PTP/Nodes/PTP_Nodes/T_P_2_M3.png | Bin 0 -> 4881 bytes images/PTP/Nodes/PTP_Nodes/T_P_2_SO.png | Bin 0 -> 3881 bytes images/PTP/Nodes/TimeDiffObserver/License.txt | 1 + .../TimeDiffObserver/TimeDiffObserver.png | Bin 0 -> 4765 bytes simulations/.gitignore | 4 + simulations/package.ned | 25 + simulations/run | 4 + src/.gitignore | 1 + src/Components/BasicBlocks/IPTP_EtherNode.ned | 56 + src/Components/BasicBlocks/PTP_BasicNode.ned | 139 + src/Components/Cables/Cables.ned | 80 + .../Nodes/InternalNodes/PTP_InternalNodes.ned | 101 + src/Components/Nodes/PTP_BoundaryClocks.ned | 201 ++ src/Components/Nodes/PTP_Endnodes.ned | 249 ++ .../Nodes/PTP_TransparentClocks.ned | 201 ++ .../TimeDiffObserver/TimeDiffObserver.cc | 241 ++ .../TimeDiffObserver/TimeDiffObserver.h | 96 + .../TimeDiffObserver/TimeDiffObserver.ned | 54 + src/Firmware/EthernetII_LLC/EthernetII_LLC.cc | 106 + src/Firmware/EthernetII_LLC/EthernetII_LLC.h | 64 + .../EthernetII_LLC/EthernetII_LLC.ned | 55 + .../DualDelayer/DelayQueue/DelayQueue.cc | 144 + .../DualDelayer/DelayQueue/DelayQueue.h | 76 + .../DualDelayer/DelayQueue/DelayQueue.ned | 38 + src/Hardware/DualDelayer/DualDelayer.ned | 77 + src/Hardware/DualDelayer/IDualDelayer.ned | 43 + src/Hardware/DualDelayer/NopDualDelayer.ned | 48 + src/Hardware/EtherPhy/EtherPhy.ned | 49 + src/Hardware/EtherPhy/IEtherPhy.ned | 40 + .../AdjustableClock/AdjustableClock.cc | 395 +++ .../HwClock/AdjustableClock/AdjustableClock.h | 122 + .../AdjustableClock/AdjustableClockTypes.ned | 58 + .../AdjustableClock/IAdjustableClock.ned | 37 + .../Internal_AdjustableClock.ned | 51 + .../HwClock/ClockEvents/ClockEvent.cc | 187 ++ src/Hardware/HwClock/ClockEvents/ClockEvent.h | 78 + .../HwClock/ClockEvents/IClockEventSink.cc | 55 + .../HwClock/ClockEvents/IClockEventSink.h | 56 + .../ClockEvents/ScheduledClockEvent.cc | 187 ++ .../HwClock/ClockEvents/ScheduledClockEvent.h | 82 + src/Hardware/HwClock/HwClock/HwClock.cc | 303 +++ src/Hardware/HwClock/HwClock/HwClock.h | 110 + src/Hardware/HwClock/HwClock/HwClockTypes.ned | 59 + .../HwClock/HwClock_ParameterParser.cc | 61 + .../HwClock/HwClock/HwClock_ParameterParser.h | 55 + src/Hardware/HwClock/HwClock/IHwClock.ned | 41 + .../HwClock/HwClock/Internal_HwClock.ned | 55 + .../HwClock/LocalTimeStamp/LocalTimeStamp.cc | 145 + .../HwClock/LocalTimeStamp/LocalTimeStamp.h | 73 + .../HwClock/ScheduleClock/IScheduleClock.ned | 37 + .../ScheduleClock/Internal_ScheduleClock.ned | 47 + .../HwClock/ScheduleClock/ScheduleClock.cc | 405 +++ .../HwClock/ScheduleClock/ScheduleClock.h | 114 + .../ScheduleClock/ScheduleClockTypes.ned | 81 + .../HwClock/SeedProvider/SeedProvider.cc | 61 + src/Hardware/HwClock/TdGen/ConstDriftTdGen.cc | 90 + src/Hardware/HwClock/TdGen/ConstDriftTdGen.h | 73 + src/Hardware/HwClock/TdGen/ITdGen.cc | 70 + src/Hardware/HwClock/TdGen/ITdGen.h | 68 + src/Hardware/HwClock/TdGen/PerfectTdGen.cc | 70 + src/Hardware/HwClock/TdGen/PerfectTdGen.h | 57 + src/Hardware/HwClock/TdGen/SineTdGen.cc | 92 + src/Hardware/HwClock/TdGen/SineTdGen.h | 75 + .../HwClock/TdGen/TdGen_ParameterParser.cc | 59 + .../HwClock/TdGen/TdGen_ParameterParser.h | 53 + src/Hardware/HwClock/TdGen/libPLN_TdGen.cc | 120 + src/Hardware/HwClock/TdGen/libPLN_TdGen.h | 77 + .../PTP_EtherEncap/IPTP_EtherEncap.ned | 34 + .../PTP_Eth_Msgs/PtpEthernet.msg | 94 + src/Hardware/PTP_EtherEncap/PTP_EtherEncap.cc | 218 ++ src/Hardware/PTP_EtherEncap/PTP_EtherEncap.h | 70 + .../PTP_EtherEncap/PTP_EtherEncap.ned | 35 + src/Hardware/PTP_MAC/IPTP_MAC.ned | 54 + src/Hardware/PTP_MAC/PTP_MAC.cc | 965 +++++++ src/Hardware/PTP_MAC/PTP_MAC.h | 169 ++ src/Hardware/PTP_MAC/PTP_MAC.ned | 54 + src/Hardware/PTP_NIC.ned | 187 ++ .../PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.cc | 149 ++ .../PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.h | 101 + .../PTP_Config_Msg/PtpPortConfig.msg | 81 + src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.cc | 332 +++ src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.h | 108 + src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.ned | 81 + .../PTP_NIC_Ctrl/PTP_Requ_Msg/PtpPortRequ.msg | 67 + .../PTP_RelayUnit/IPTP_MACRelayUnit.ned | 45 + .../PTP_RelayUnit/MACRelayUnitBase.cc | 128 + src/Hardware/PTP_RelayUnit/MACRelayUnitBase.h | 59 + .../PTP_RelayUnit/PTP_MACRelayUnit.cc | 200 ++ src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.h | 80 + .../PTP_RelayUnit/PTP_MACRelayUnit.ned | 40 + src/Makefrag_Boost | 38 + src/Makefrag_GenericBuildOptions | 28 + src/Makefrag_libPLN | 128 + src/Software/ClockServo/IClockServo.cc | 477 ++++ src/Software/ClockServo/IClockServo.h | 145 + src/Software/ClockServo/IClockServo.ned | 40 + .../ClockServo/Internal_ClockServo.ned | 66 + src/Software/ClockServo/PI_ClockServo.ned | 71 + .../ClockServo/PI_ClockServo/PI_ClockServo.cc | 370 +++ .../ClockServo/PI_ClockServo/PI_ClockServo.h | 136 + .../PI_ClockServo_ParameterParser.cc | 62 + .../PI_ClockServo_ParameterParser.h | 48 + .../PTP_EthernetMapping/PTP_Ctrl/PTP_Ctrl.msg | 38 + .../PTP_EthernetMapping.cc | 171 ++ .../PTP_EthernetMapping/PTP_EthernetMapping.h | 79 + .../PTP_EthernetMapping.ned | 55 + .../AppServices/Announce/AppAnnounce.cc | 287 ++ .../AppServices/Announce/AppAnnounce.h | 79 + .../PTP_Stack/AppServices/AppService.cc | 76 + .../PTP_Stack/AppServices/AppService.h | 63 + .../PTP_Stack/AppServices/BasicService.cc | 197 ++ .../PTP_Stack/AppServices/BasicService.h | 108 + .../PTP_Stack/AppServices/Delay/AppDelay.cc | 441 +++ .../PTP_Stack/AppServices/Delay/AppDelay.h | 108 + .../AppServices/Delay/TimestampMatcher.cc | 123 + .../AppServices/Delay/TimestampMatcher.h | 72 + .../AppServices/FilteredPortService.cc | 171 ++ .../AppServices/FilteredPortService.h | 79 + .../PTP_Stack/AppServices/PDelay/AppPDelay.cc | 598 +++++ .../PTP_Stack/AppServices/PDelay/AppPDelay.h | 123 + .../PTP_Stack/AppServices/PortService.cc | 211 ++ .../PTP_Stack/AppServices/PortService.h | 106 + .../StateDecision/AppStateDecision.cc | 254 ++ .../StateDecision/AppStateDecision.h | 86 + .../PTP_Stack/AppServices/Sync/AppSync.cc | 475 ++++ .../PTP_Stack/AppServices/Sync/AppSync.h | 121 + .../DataTypes/DataSets/PTP_CurrentDS.cc | 175 ++ .../DataTypes/DataSets/PTP_CurrentDS.h | 100 + .../DataTypes/DataSets/PTP_DefaultDS.cc | 210 ++ .../DataTypes/DataSets/PTP_DefaultDS.h | 104 + .../DataTypes/DataSets/PTP_ForeignClockDS.cc | 657 +++++ .../DataTypes/DataSets/PTP_ForeignClockDS.h | 151 ++ .../DataTypes/DataSets/PTP_ForeignClockMsg.cc | 130 + .../DataTypes/DataSets/PTP_ForeignClockMsg.h | 73 + .../DataTypes/DataSets/PTP_ForeignMasterDS.cc | 304 +++ .../DataTypes/DataSets/PTP_ForeignMasterDS.h | 106 + .../DataSets/PTP_ForeignMasterDS_Entry.cc | 204 ++ .../DataSets/PTP_ForeignMasterDS_Entry.h | 86 + .../DataTypes/DataSets/PTP_ParentDS.cc | 235 ++ .../DataTypes/DataSets/PTP_ParentDS.h | 111 + .../DataTypes/DataSets/PTP_PortDS.cc | 348 +++ .../PTP_Stack/DataTypes/DataSets/PTP_PortDS.h | 138 + .../DataSets/PTP_TimePropertiesDS.cc | 234 ++ .../DataTypes/DataSets/PTP_TimePropertiesDS.h | 101 + .../DataSets/PTP_TransparentClockDefaultDS.cc | 146 + .../DataSets/PTP_TransparentClockDefaultDS.h | 80 + .../DataSets/PTP_TransparentClockPortDS.cc | 140 + .../DataSets/PTP_TransparentClockPortDS.h | 78 + .../PTP_Stack/DataTypes/PTP_ClockIdentity.cc | 275 ++ .../PTP_Stack/DataTypes/PTP_ClockIdentity.h | 84 + .../PTP_Stack/DataTypes/PTP_ClockQuality.cc | 238 ++ .../PTP_Stack/DataTypes/PTP_ClockQuality.h | 93 + .../PTP_Stack/DataTypes/PTP_FaultRecord.cc | 137 + .../PTP_Stack/DataTypes/PTP_FaultRecord.h | 76 + .../PTP_Stack/DataTypes/PTP_HeaderFlags.cc | 264 ++ .../PTP_Stack/DataTypes/PTP_HeaderFlags.h | 83 + .../PTP_Stack/DataTypes/PTP_PTPText.cc | 109 + .../PTP_Stack/DataTypes/PTP_PTPText.h | 78 + .../PTP_Stack/DataTypes/PTP_PortAddress.cc | 104 + .../PTP_Stack/DataTypes/PTP_PortAddress.h | 73 + .../PTP_Stack/DataTypes/PTP_PortIdentity.cc | 250 ++ .../PTP_Stack/DataTypes/PTP_PortIdentity.h | 91 + .../DataTypes/PTP_PrimitiveDataTypes.cc | 483 ++++ .../DataTypes/PTP_PrimitiveDataTypes.h | 375 +++ src/Software/PTP_Stack/DataTypes/PTP_TLV.cc | 105 + src/Software/PTP_Stack/DataTypes/PTP_TLV.h | 74 + .../PTP_Stack/DataTypes/PTP_TimeInterval.cc | 273 ++ .../PTP_Stack/DataTypes/PTP_TimeInterval.h | 90 + .../PTP_Stack/DataTypes/PTP_TimeStamp.cc | 222 ++ .../PTP_Stack/DataTypes/PTP_TimeStamp.h | 87 + src/Software/PTP_Stack/Includes/PTP.h | 65 + .../PTP_Stack/Includes/PTP_ByteBuffers.h | 150 ++ .../PTP_Stack/Includes/PTP_Constants.h | 70 + .../PTP_Stack/Includes/PTP_Ethernet.h | 66 + .../PTP_Stack/PTP_EventMsg/PTP_EventMsg.msg | 48 + src/Software/PTP_Stack/PTP_Messages/PTPv2.msg | 216 ++ src/Software/PTP_Stack/PTP_Stack.cc | 1050 ++++++++ src/Software/PTP_Stack/PTP_Stack.h | 274 ++ src/Software/PTP_Stack/PTP_Stack.ned | 185 ++ .../PTP_Stack/ParameterParser/PTP_Parser.cc | 342 +++ .../PTP_Stack/ParameterParser/PTP_Parser.h | 66 + src/Software/PTP_Stack/Port/PTP_Port.cc | 1016 +++++++ src/Software/PTP_Stack/Port/PTP_Port.h | 179 ++ .../CustomProfile/PTP_CustomProfileChecker.cc | 99 + .../CustomProfile/PTP_CustomProfileChecker.h | 91 + .../PTP_DefaultE2EProfileChecker.cc | 128 + .../PTP_DefaultE2EProfileChecker.h | 91 + .../PTP_DefaultP2PProfileChecker.cc | 128 + .../PTP_DefaultP2PProfileChecker.h | 91 + .../PTP_DefaultProfileChecker.cc | 176 ++ .../PTP_DefaultProfileChecker.h | 91 + .../PTP_Stack/Profiles/PTP_ProfileChecker.cc | 94 + .../PTP_Stack/Profiles/PTP_ProfileChecker.h | 101 + .../PowerProfile/PTP_PowerProfileChecker.cc | 160 ++ .../PowerProfile/PTP_PowerProfileChecker.h | 106 + src/Software/SimTimeFilter/ISimTimeFilter.cc | 92 + src/Software/SimTimeFilter/ISimTimeFilter.h | 68 + .../Identity/IdentitySimTimeFilter.cc | 121 + .../Identity/IdentitySimTimeFilter.h | 71 + .../MovingAvg/MovingAvgSimTimeFilter.cc | 168 ++ .../MovingAvg/MovingAvgSimTimeFilter.h | 76 + .../SimTimeFilter/SimTimeFilterTypes.h | 45 + .../SimTimeFilter_ParameterParser.cc | 60 + .../SimTimeFilter_ParameterParser.h | 48 + src/Testbenches/BabblingIP/BabblingIP.cc | 104 + src/Testbenches/BabblingIP/BabblingIP.h | 61 + src/Testbenches/BabblingIP/BabblingIP.ned | 34 + .../ClockScaleTest/ClockScaleTest.cc | 83 + .../ClockScaleTest/ClockScaleTest.h | 48 + .../ClockScaleTest/ClockScaleTest.ned | 32 + .../ClockScheduleTest/ClockScheduleTest.cc | 90 + .../ClockScheduleTest/ClockScheduleTest.h | 68 + .../ClockScheduleTest/ClockScheduleTest.ned | 32 + .../ClockServoTest/ClockServoTest.cc | 91 + .../ClockServoTest/ClockServoTest.h | 71 + .../ClockServoTest/ClockServoTest.ned | 36 + src/Testbenches/ClockTest/ClockTest.cc | 169 ++ src/Testbenches/ClockTest/ClockTest.h | 60 + src/Testbenches/ClockTest/ClockTest.ned | 31 + src/Testbenches/EncapTest/EncapTest.cc | 220 ++ src/Testbenches/EncapTest/EncapTest.h | 61 + src/Testbenches/EncapTest/EncapTest.ned | 30 + .../EtherPhyTester/EtherPhyTester.cc | 100 + .../EtherPhyTester/EtherPhyTester.h | 54 + .../EtherPhyTester/EtherPhyTester.ned | 38 + src/Testbenches/PTP_EthSink/PTP_EthSink.cc | 230 ++ src/Testbenches/PTP_EthSink/PTP_EthSink.h | 48 + src/Testbenches/PTP_EthSink/PTP_EthSink.ned | 30 + src/Testbenches/PtpFrameGen/PtpFrameGen.cc | 433 +++ src/Testbenches/PtpFrameGen/PtpFrameGen.h | 88 + src/Testbenches/PtpFrameGen/PtpFrameGen.ned | 55 + src/Testbenches/PtpMacSink/PtpMacSink.ned | 89 + src/Testbenches/PtpMacSource/PtpMacSource.ned | 114 + .../ParameterParser/PtpStackTest_Parser.cc | 66 + .../ParameterParser/PtpStackTest_Parser.h | 48 + src/Testbenches/PtpStackTest/PtpStackTest.cc | 94 + src/Testbenches/PtpStackTest/PtpStackTest.h | 50 + src/Testbenches/PtpStackTest/PtpStackTest.ned | 31 + .../PtpStackTest/PtpStackTestType.h | 48 + .../PtpStackTest/Tests/ClockIdentity.cc | 88 + .../PtpStackTest/Tests/ForeignClockDS.cc | 199 ++ .../PtpStackTest/Tests/PortIdentity.cc | 95 + src/Utils/ByteOrder/ByteOrder.cc | 165 ++ src/Utils/ByteOrder/ByteOrder.h | 62 + src/Utils/Constants/PhysicalConstants.cc | 49 + src/Utils/Constants/PhysicalConstants.h | 42 + src/package.ned | 25 + 360 files changed, 35577 insertions(+) create mode 100644 COPYING create mode 100644 Docs/Measurements/SimpleNetwork/AsymmetryExperiment.ods create mode 100644 Docs/Measurements/SimpleNetwork/PI_Parameters.ods create mode 100755 Docs/Usage_with_libPLN/Usage_with_libPLN.odt create mode 100755 Docs/Usage_with_libPLN/Usage_with_libPLN.pdf create mode 100755 Docs/Usage_with_libPLN/images/Dependencies.dia create mode 100755 Docs/Usage_with_libPLN/images/Dependencies.png create mode 100644 Makefile.vc create mode 100644 Readme.md create mode 100755 Tools/Bash_Scripts/CleanUpSources.sh create mode 100644 doc/img/BC8_Port2_StateDecisions.png create mode 100644 doc/img/MessageSymbols.png create mode 100644 doc/img/NodeSymbols.png create mode 100644 doc/img/NodeSymbols_ExampleNetwork.png create mode 100644 doc/img/SyncInterval_Mean.png create mode 100644 doxy.cfg create mode 100644 images/PTP/Base/Blank/Blank.xcf create mode 100644 images/PTP/Base/Blank/License.txt create mode 100644 images/PTP/Components/Clock/ConstantDriftClock.png create mode 100644 images/PTP/Components/Clock/DigitalClock.png create mode 100644 images/PTP/Components/Clock/DigitalClock.xcf create mode 100644 images/PTP/Components/Clock/License.txt create mode 100644 images/PTP/Components/Clock/PerfectClock.png create mode 100644 images/PTP/Components/Clock/RealClock.png create mode 100644 images/PTP/Components/Clock/SineClock.png create mode 100644 images/PTP/Components/ClockServo/ClockServo.png create mode 100644 images/PTP/Components/ClockServo/License.txt create mode 100644 images/PTP/Components/ClockServo/PI_ClockServo.dia create mode 100644 images/PTP/Components/ClockServo/PI_ClockServo.png create mode 100644 images/PTP/Components/ClockServo/PI_ClockServo.xcf create mode 100644 images/PTP/Components/Delayer/Delayer.png create mode 100644 images/PTP/Components/Delayer/License.txt create mode 100644 images/PTP/Components/Encap/Encap.png create mode 100644 images/PTP/Components/Encap/License.txt create mode 100644 images/PTP/Components/InternalModule/InternalModule.png create mode 100644 images/PTP/Components/InternalModule/License.txt create mode 100644 images/PTP/Components/MAC/License.txt create mode 100644 images/PTP/Components/MAC/MAC.png create mode 100644 images/PTP/Components/NIC/License.txt create mode 100644 images/PTP/Components/NIC/NIC.png create mode 100644 images/PTP/Components/NIC_Ctrl/License.txt create mode 100644 images/PTP/Components/NIC_Ctrl/NIC_Ctrl.png create mode 100644 images/PTP/Components/PHY/License.txt create mode 100644 images/PTP/Components/PHY/PHY.png create mode 100644 images/PTP/Components/PTP_EthernetMapping/License.txt create mode 100644 images/PTP/Components/PTP_EthernetMapping/PTP_EthernetMapping.png create mode 100644 images/PTP/Components/PTP_EthernetMapping/PTP_EthernetMapping.xcf create mode 100644 images/PTP/Components/PTP_Stack/License.txt create mode 100644 images/PTP/Components/PTP_Stack/PTP_Stack.png create mode 100644 images/PTP/Components/RelayUnit/License.txt create mode 100644 images/PTP/Components/RelayUnit/RelayUnit.png create mode 100644 images/PTP/LICENSE.txt create mode 100644 images/PTP/Messages/Announce.png create mode 100644 images/PTP/Messages/DelayReq.png create mode 100644 images/PTP/Messages/DelayResp.png create mode 100644 images/PTP/Messages/License.txt create mode 100644 images/PTP/Messages/Management.png create mode 100644 images/PTP/Messages/PDelayReq.png create mode 100644 images/PTP/Messages/PDelayResp.png create mode 100644 images/PTP/Messages/PDelayRespFU.png create mode 100644 images/PTP/Messages/Signaling.png create mode 100644 images/PTP/Messages/Sync.png create mode 100644 images/PTP/Messages/SyncFU.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_1_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_1_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_1_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_1_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_2_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_2_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_2_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_E_2_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_1_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_1_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_1_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_1_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_2_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_2_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_2_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/B_P_2_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/Generic.png create mode 100644 images/PTP/Nodes/PTP_Nodes/Generic.xcf create mode 100644 images/PTP/Nodes/PTP_Nodes/License.txt create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_1_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_1_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_1_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_1_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_2_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_2_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_2_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_E_2_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_1_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_1_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_1_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_1_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_2_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_2_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_2_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/N_P_2_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/Node.xcf create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_1_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_1_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_1_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_1_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_2_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_2_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_2_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_E_2_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_1_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_1_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_1_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_1_SO.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_2_M1.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_2_M2.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_2_M3.png create mode 100644 images/PTP/Nodes/PTP_Nodes/T_P_2_SO.png create mode 100644 images/PTP/Nodes/TimeDiffObserver/License.txt create mode 100644 images/PTP/Nodes/TimeDiffObserver/TimeDiffObserver.png create mode 100644 simulations/.gitignore create mode 100644 simulations/package.ned create mode 100755 simulations/run create mode 100644 src/.gitignore create mode 100644 src/Components/BasicBlocks/IPTP_EtherNode.ned create mode 100644 src/Components/BasicBlocks/PTP_BasicNode.ned create mode 100644 src/Components/Cables/Cables.ned create mode 100644 src/Components/Nodes/InternalNodes/PTP_InternalNodes.ned create mode 100644 src/Components/Nodes/PTP_BoundaryClocks.ned create mode 100644 src/Components/Nodes/PTP_Endnodes.ned create mode 100644 src/Components/Nodes/PTP_TransparentClocks.ned create mode 100644 src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.cc create mode 100644 src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.h create mode 100644 src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.ned create mode 100644 src/Firmware/EthernetII_LLC/EthernetII_LLC.cc create mode 100644 src/Firmware/EthernetII_LLC/EthernetII_LLC.h create mode 100644 src/Firmware/EthernetII_LLC/EthernetII_LLC.ned create mode 100644 src/Hardware/DualDelayer/DelayQueue/DelayQueue.cc create mode 100644 src/Hardware/DualDelayer/DelayQueue/DelayQueue.h create mode 100644 src/Hardware/DualDelayer/DelayQueue/DelayQueue.ned create mode 100644 src/Hardware/DualDelayer/DualDelayer.ned create mode 100644 src/Hardware/DualDelayer/IDualDelayer.ned create mode 100644 src/Hardware/DualDelayer/NopDualDelayer.ned create mode 100644 src/Hardware/EtherPhy/EtherPhy.ned create mode 100644 src/Hardware/EtherPhy/IEtherPhy.ned create mode 100644 src/Hardware/HwClock/AdjustableClock/AdjustableClock.cc create mode 100644 src/Hardware/HwClock/AdjustableClock/AdjustableClock.h create mode 100644 src/Hardware/HwClock/AdjustableClock/AdjustableClockTypes.ned create mode 100644 src/Hardware/HwClock/AdjustableClock/IAdjustableClock.ned create mode 100644 src/Hardware/HwClock/AdjustableClock/Internal_AdjustableClock.ned create mode 100644 src/Hardware/HwClock/ClockEvents/ClockEvent.cc create mode 100644 src/Hardware/HwClock/ClockEvents/ClockEvent.h create mode 100644 src/Hardware/HwClock/ClockEvents/IClockEventSink.cc create mode 100644 src/Hardware/HwClock/ClockEvents/IClockEventSink.h create mode 100644 src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.cc create mode 100644 src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.h create mode 100644 src/Hardware/HwClock/HwClock/HwClock.cc create mode 100644 src/Hardware/HwClock/HwClock/HwClock.h create mode 100644 src/Hardware/HwClock/HwClock/HwClockTypes.ned create mode 100644 src/Hardware/HwClock/HwClock/HwClock_ParameterParser.cc create mode 100644 src/Hardware/HwClock/HwClock/HwClock_ParameterParser.h create mode 100644 src/Hardware/HwClock/HwClock/IHwClock.ned create mode 100644 src/Hardware/HwClock/HwClock/Internal_HwClock.ned create mode 100644 src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.cc create mode 100644 src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.h create mode 100644 src/Hardware/HwClock/ScheduleClock/IScheduleClock.ned create mode 100644 src/Hardware/HwClock/ScheduleClock/Internal_ScheduleClock.ned create mode 100644 src/Hardware/HwClock/ScheduleClock/ScheduleClock.cc create mode 100644 src/Hardware/HwClock/ScheduleClock/ScheduleClock.h create mode 100644 src/Hardware/HwClock/ScheduleClock/ScheduleClockTypes.ned create mode 100644 src/Hardware/HwClock/SeedProvider/SeedProvider.cc create mode 100644 src/Hardware/HwClock/TdGen/ConstDriftTdGen.cc create mode 100644 src/Hardware/HwClock/TdGen/ConstDriftTdGen.h create mode 100644 src/Hardware/HwClock/TdGen/ITdGen.cc create mode 100644 src/Hardware/HwClock/TdGen/ITdGen.h create mode 100644 src/Hardware/HwClock/TdGen/PerfectTdGen.cc create mode 100644 src/Hardware/HwClock/TdGen/PerfectTdGen.h create mode 100644 src/Hardware/HwClock/TdGen/SineTdGen.cc create mode 100644 src/Hardware/HwClock/TdGen/SineTdGen.h create mode 100644 src/Hardware/HwClock/TdGen/TdGen_ParameterParser.cc create mode 100644 src/Hardware/HwClock/TdGen/TdGen_ParameterParser.h create mode 100644 src/Hardware/HwClock/TdGen/libPLN_TdGen.cc create mode 100644 src/Hardware/HwClock/TdGen/libPLN_TdGen.h create mode 100644 src/Hardware/PTP_EtherEncap/IPTP_EtherEncap.ned create mode 100644 src/Hardware/PTP_EtherEncap/PTP_Eth_Msgs/PtpEthernet.msg create mode 100644 src/Hardware/PTP_EtherEncap/PTP_EtherEncap.cc create mode 100644 src/Hardware/PTP_EtherEncap/PTP_EtherEncap.h create mode 100644 src/Hardware/PTP_EtherEncap/PTP_EtherEncap.ned create mode 100644 src/Hardware/PTP_MAC/IPTP_MAC.ned create mode 100644 src/Hardware/PTP_MAC/PTP_MAC.cc create mode 100644 src/Hardware/PTP_MAC/PTP_MAC.h create mode 100644 src/Hardware/PTP_MAC/PTP_MAC.ned create mode 100644 src/Hardware/PTP_NIC.ned create mode 100644 src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.cc create mode 100644 src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.h create mode 100644 src/Hardware/PTP_NIC_Ctrl/PTP_Config_Msg/PtpPortConfig.msg create mode 100644 src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.cc create mode 100644 src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.h create mode 100644 src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.ned create mode 100644 src/Hardware/PTP_NIC_Ctrl/PTP_Requ_Msg/PtpPortRequ.msg create mode 100644 src/Hardware/PTP_RelayUnit/IPTP_MACRelayUnit.ned create mode 100644 src/Hardware/PTP_RelayUnit/MACRelayUnitBase.cc create mode 100644 src/Hardware/PTP_RelayUnit/MACRelayUnitBase.h create mode 100644 src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.cc create mode 100644 src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.h create mode 100644 src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.ned create mode 100755 src/Makefrag_Boost create mode 100755 src/Makefrag_GenericBuildOptions create mode 100755 src/Makefrag_libPLN create mode 100644 src/Software/ClockServo/IClockServo.cc create mode 100644 src/Software/ClockServo/IClockServo.h create mode 100644 src/Software/ClockServo/IClockServo.ned create mode 100644 src/Software/ClockServo/Internal_ClockServo.ned create mode 100644 src/Software/ClockServo/PI_ClockServo.ned create mode 100644 src/Software/ClockServo/PI_ClockServo/PI_ClockServo.cc create mode 100644 src/Software/ClockServo/PI_ClockServo/PI_ClockServo.h create mode 100644 src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.cc create mode 100644 src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.h create mode 100644 src/Software/PTP_EthernetMapping/PTP_Ctrl/PTP_Ctrl.msg create mode 100644 src/Software/PTP_EthernetMapping/PTP_EthernetMapping.cc create mode 100644 src/Software/PTP_EthernetMapping/PTP_EthernetMapping.h create mode 100644 src/Software/PTP_EthernetMapping/PTP_EthernetMapping.ned create mode 100644 src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.cc create mode 100644 src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.h create mode 100644 src/Software/PTP_Stack/AppServices/AppService.cc create mode 100644 src/Software/PTP_Stack/AppServices/AppService.h create mode 100644 src/Software/PTP_Stack/AppServices/BasicService.cc create mode 100644 src/Software/PTP_Stack/AppServices/BasicService.h create mode 100644 src/Software/PTP_Stack/AppServices/Delay/AppDelay.cc create mode 100644 src/Software/PTP_Stack/AppServices/Delay/AppDelay.h create mode 100644 src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.cc create mode 100644 src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.h create mode 100644 src/Software/PTP_Stack/AppServices/FilteredPortService.cc create mode 100644 src/Software/PTP_Stack/AppServices/FilteredPortService.h create mode 100644 src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.cc create mode 100644 src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.h create mode 100644 src/Software/PTP_Stack/AppServices/PortService.cc create mode 100644 src/Software/PTP_Stack/AppServices/PortService.h create mode 100644 src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.cc create mode 100644 src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.h create mode 100644 src/Software/PTP_Stack/AppServices/Sync/AppSync.cc create mode 100644 src/Software/PTP_Stack/AppServices/Sync/AppSync.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.cc create mode 100644 src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PTPText.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PTPText.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PortAddress.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PortAddress.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_TLV.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_TLV.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.h create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.cc create mode 100644 src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.h create mode 100644 src/Software/PTP_Stack/Includes/PTP.h create mode 100644 src/Software/PTP_Stack/Includes/PTP_ByteBuffers.h create mode 100644 src/Software/PTP_Stack/Includes/PTP_Constants.h create mode 100644 src/Software/PTP_Stack/Includes/PTP_Ethernet.h create mode 100644 src/Software/PTP_Stack/PTP_EventMsg/PTP_EventMsg.msg create mode 100644 src/Software/PTP_Stack/PTP_Messages/PTPv2.msg create mode 100644 src/Software/PTP_Stack/PTP_Stack.cc create mode 100644 src/Software/PTP_Stack/PTP_Stack.h create mode 100644 src/Software/PTP_Stack/PTP_Stack.ned create mode 100644 src/Software/PTP_Stack/ParameterParser/PTP_Parser.cc create mode 100644 src/Software/PTP_Stack/ParameterParser/PTP_Parser.h create mode 100644 src/Software/PTP_Stack/Port/PTP_Port.cc create mode 100644 src/Software/PTP_Stack/Port/PTP_Port.h create mode 100644 src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.cc create mode 100644 src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.h create mode 100644 src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.cc create mode 100644 src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.h create mode 100644 src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.cc create mode 100644 src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.h create mode 100644 src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.cc create mode 100644 src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.h create mode 100644 src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.cc create mode 100644 src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.h create mode 100644 src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.cc create mode 100644 src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.h create mode 100644 src/Software/SimTimeFilter/ISimTimeFilter.cc create mode 100644 src/Software/SimTimeFilter/ISimTimeFilter.h create mode 100644 src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.cc create mode 100644 src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.h create mode 100644 src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.cc create mode 100644 src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.h create mode 100644 src/Software/SimTimeFilter/SimTimeFilterTypes.h create mode 100644 src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.cc create mode 100644 src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.h create mode 100644 src/Testbenches/BabblingIP/BabblingIP.cc create mode 100644 src/Testbenches/BabblingIP/BabblingIP.h create mode 100644 src/Testbenches/BabblingIP/BabblingIP.ned create mode 100644 src/Testbenches/ClockScaleTest/ClockScaleTest.cc create mode 100644 src/Testbenches/ClockScaleTest/ClockScaleTest.h create mode 100644 src/Testbenches/ClockScaleTest/ClockScaleTest.ned create mode 100644 src/Testbenches/ClockScheduleTest/ClockScheduleTest.cc create mode 100644 src/Testbenches/ClockScheduleTest/ClockScheduleTest.h create mode 100644 src/Testbenches/ClockScheduleTest/ClockScheduleTest.ned create mode 100644 src/Testbenches/ClockServoTest/ClockServoTest.cc create mode 100644 src/Testbenches/ClockServoTest/ClockServoTest.h create mode 100644 src/Testbenches/ClockServoTest/ClockServoTest.ned create mode 100644 src/Testbenches/ClockTest/ClockTest.cc create mode 100644 src/Testbenches/ClockTest/ClockTest.h create mode 100644 src/Testbenches/ClockTest/ClockTest.ned create mode 100644 src/Testbenches/EncapTest/EncapTest.cc create mode 100644 src/Testbenches/EncapTest/EncapTest.h create mode 100644 src/Testbenches/EncapTest/EncapTest.ned create mode 100755 src/Testbenches/EtherPhyTester/EtherPhyTester.cc create mode 100755 src/Testbenches/EtherPhyTester/EtherPhyTester.h create mode 100755 src/Testbenches/EtherPhyTester/EtherPhyTester.ned create mode 100644 src/Testbenches/PTP_EthSink/PTP_EthSink.cc create mode 100644 src/Testbenches/PTP_EthSink/PTP_EthSink.h create mode 100644 src/Testbenches/PTP_EthSink/PTP_EthSink.ned create mode 100644 src/Testbenches/PtpFrameGen/PtpFrameGen.cc create mode 100644 src/Testbenches/PtpFrameGen/PtpFrameGen.h create mode 100644 src/Testbenches/PtpFrameGen/PtpFrameGen.ned create mode 100644 src/Testbenches/PtpMacSink/PtpMacSink.ned create mode 100644 src/Testbenches/PtpMacSource/PtpMacSource.ned create mode 100644 src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.cc create mode 100644 src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.h create mode 100644 src/Testbenches/PtpStackTest/PtpStackTest.cc create mode 100644 src/Testbenches/PtpStackTest/PtpStackTest.h create mode 100644 src/Testbenches/PtpStackTest/PtpStackTest.ned create mode 100644 src/Testbenches/PtpStackTest/PtpStackTestType.h create mode 100644 src/Testbenches/PtpStackTest/Tests/ClockIdentity.cc create mode 100644 src/Testbenches/PtpStackTest/Tests/ForeignClockDS.cc create mode 100644 src/Testbenches/PtpStackTest/Tests/PortIdentity.cc create mode 100644 src/Utils/ByteOrder/ByteOrder.cc create mode 100644 src/Utils/ByteOrder/ByteOrder.h create mode 100644 src/Utils/Constants/PhysicalConstants.cc create mode 100644 src/Utils/Constants/PhysicalConstants.h create mode 100644 src/package.ned diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Docs/Measurements/SimpleNetwork/AsymmetryExperiment.ods b/Docs/Measurements/SimpleNetwork/AsymmetryExperiment.ods new file mode 100644 index 0000000000000000000000000000000000000000..9fe849a24bf268dbad30fd577a8ba57671229dda GIT binary patch literal 17330 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 z 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 literal 0 HcmV?d00001 diff --git a/Docs/Measurements/SimpleNetwork/PI_Parameters.ods b/Docs/Measurements/SimpleNetwork/PI_Parameters.ods new file mode 100644 index 0000000000000000000000000000000000000000..527ead3a15b4eeee2284181aa2aaa58d968b1d0c GIT binary patch literal 32933 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%0Dbc{h52!zBZu|MjSNzS=lQ$qKV-~G%Tfx? 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

J7J zUf3rblq44-!R+XDxf>Bd6{7UfS=FHG5} zd&bF{Gj1%?C4mY(Kl;EHPO||ymO2WtgM7ty=OX~G!@rIfzk9m31o_q$$ix!#MiYc8 z{o2o@Ogc5!S8jGLvz-L{t%b?)pqngXEpdxK{^$i5Kob3Vgcs$_0wl4Kji{SX8|Ysk zN6`l+YHmfd$`GnQVQ$4yz(wnuMv$rqj5SkcjJJvzN&;zgcZUfiP$>j+h~xvvS6d(Y zJU0SSFe=*SU}>i3qj&Y@8+^V_+p>;(7xZUU#(PR?mD;L#8S@~pj@zfz;DQ#T@_*b_ zEMj>=n3ob7kOR*vSzFSz>v{DQB)DsNebasoO?hkv-g~>?!C4?*8xY>nx-=%VrqOM9 zw5FCRKsKMe~y*+&Xa zLh1V%xe)%u)g3SGbH+}U#pP!fv?s}*>dK=fZr)4<4tv*eZ8?(9zt}lEuE}3IUJzvQ zE#D5(mah@uE}c&>Z2~|AhpG&CPADpHKs4>!9`(W%m`x-ZnsEZ(cl(=H ziKzubfbOpFjc(5MroL9>$xPhHG}U0KCMtB%3D6y?b^v?`K}dBrbHKA9ZLt}xTzOz1migUVsO zr3Ef1zo4;<_z_8W$tX92%&A?tf$@`M=_F3*?FcoUD|--Xs-y00>nmE|2lSe9`7BVZ z(ML62oK`e@<;LX+vBt1!Ov_k-c<#4?3!zTSB7RHua0Nrrq3SvPu;^&AMKT>-_t46) z?=D#T{d8kjy){p%0UD+Z*@{v~cuj4)nmr);gAhzk3HSGK?wl)ns5B7q<*)653-+Wa z(9U1biO8H^^BC3A8|wSS+x;E^7KD+bw9Eu zJ^~I@&)Zy&pLmC~d%{21+tYO%!%Sy3)F1=&wojuywA;H+CSRco$!Q^-d<|SlM{@5H z58L{joE+&9;JZ1wcqnx%FaUsziI2ws!Rs83 zU2o*$)4|l(F;LY!=@b5t^*|8n(DK^zjpG_V8FAVpMjblF&JI@b9Ag9ZV>|Ch)N29Q z!w3j5jRDn3tEM*5^_rV6yCk$dNdyHs7n(ujmvVAWF4%9Z_l!JviJrivzMR=vNRl#0 z9wZ0K48^YDB)n;obdOJ_64`DyH*3V;*!Jdlk3RbL_h$M%R6DQ#&6--$34M||Z z$u}?Rro^hAC#$uNUon1Cwd(L%{0o8sxHZaab`yfJ04lY0Xq2RPL7zW%o7N~EW)+pz zTsSr+4$f1iUA;=7UMcbc37Lx`0J^x5x3Psq4b^cq-@y+4=4waA()T=)xPoO`mclD# zI>VR6lYkEQJPMOD7Dn|RV5gd+Fj{lf;|xm3cY0LwRVm_w-BQ${PNK<3~CScAu2~cRZtxRzhbz z8o;^TUW#Lug9%F+rt>ZDB)USiWeu@1WQ}%XA6*q220on}O$zU2v!wBOe*8Qt*8e#r zUvzw9MZ$(IFD-O~6>ibXyQ0BkPbajnOw02zr5a}^R|Dj`oel5PD$K3dcLTcID}XnG zddHqBd0phw*kaXQ*HqnusSttKnNhLi^2!$FWEP8WW!2cK19&s5#M=43#48pW^$b?#a@x+$>rfitS z_Mm4!1kMo!4&xxRak87?Sb_!5P^m>_By3`u7&}~^?mnSMnRz|lOm3+4wGYZ?9%Z9+ zl+y?5VsWR$ug-tz!-Z*|vsxBtyMcZl8{onS9^S&0e#U$ve)Sj%uFqB1WVWJ2{0 zXSE);#MgfAiX!edwTHUXU^CWSM4&&CyZ#UYDbV$c(LpZ{gqBIwy9Hk)RK3-t+06ukv?9LAhLKc;ZnCsY!G8DT_BCAtGw!N z@o8#i+*1hfBr{J4Zpc1wSrSv`7L7$0hU>kA-3ry!v18Ti_7%QdS(;9ursPPU<+W*Z zM+c@wEiVAF-(9z|QlV`lIF(JE*S67xR3_v15>VwekCQs$~98vNG?v$I5}Z z0;LcqtfXn*6^%YaS+<{kBMuJ}L$JoTNcBMG4CSBVJ_63SD ztyN~%Qs537zH_T750jLTxPuOumHnD1pslP)%vts7!u|XA#&u{n@;+t=eaV*mSt;*x zGZTYD?8GIa9#Ch;trkq(R*BQ00STL^z2*;WQ*?+J*dj+)x8S9 zchEM@25#-5Yq@6;0j9$9I(c`&8v#g{&thk=@GOPNhcQP{-NGztUfFbHWJ=w)(ERL8 z{Cv&+t&;?Hb<{mXTJW?jdch}pRnqu+m@D;IyJjAW3{CpSyM2m?$Gtkrk>&cdSfL<3<_eiHf>46TgotQ@h&>JGx78m{hx<73EcUgh^@2kgrZ z{AY1Qj4R8bPnQELV=ng(w=@*hbE9#T$Y{q5QL&`@j!;k7ZAa>qOqXVj+c!}KE{n7Kn&j&*f=H!yR}X{c7B!+ivf5O9Gd=7I?_i?Px3+IT)!EVSd!3fB zz+$qql_@;t2fU}^DK=lW9yfl!zvQ44(=~1j@9%9L2M96Oa4%D?4VuTmVsYt8$qk|i z)QCd*Dqh>X(ZFObD9SPUzRx(}^KpexI|QG%mdpLKVrZw1;KxV^TNlI_3T=*lQPb0D@zb(L7-@D6je)LT$n#ZSc4JohO%@ zUHs)pnAG^)^TB|vjLsWOOloh7sfp3eEbT@Uk2x6gIE13KGE*h}-Ak+3$pqVh=&V@O z!T4I~TWN=#z^D%UA~y$iT(sg3pT5ivVK;A+6IU3&ui-gke1pd`OqF7uC}-I5owXK- zMCthkEt-#-Tyk2r9|yem;uqxr9Y`0voa=7~Rsuc&=&U%4LjpHBt~M z*#3dh*=tbk zf4Xt9&1$Htyfkw;4rLe5+@r{>qj!9Lhg6?UG+EANH$zb)wB?lTt4SVU|5&R!yBd;1 zQb%}Rv|PSfJL}m6rp@Od5{S`v5AwWV91NIsAW+uWur{08p^OcXPHt);oed+|eT+kL zcC4&co=!ITRhvvAxq1avfRp^qt7UW0pzqTGEmpMV?efJqwHy%U? z&H12C49dr=h4Lg3lHF*EBM#V0Cr*|VyIlh=VapT~61>qnDI0Ctc9Hv~2Txo}D{M;! z)f29#z~`=yek6~tC#jd~2UQ_T7C+-PF`6+LIzn|}4n)Hpaw9udT_)j$RKS@EeIuJE zdav|M3uek33}qkS_^>1ztwugV<>$0AB@?Q7)h1m`Q7B!{v_Z|NiQ4TAUM;g+ug`G4 zmlDF|`kul9yDg)2F!I@)_`CZFubc*<-_rB_=3EG7q`np!`i0@Hy!6h!k!*pZsRknf z2i?A}H`2^SN^G1{E&V~{9rt;|C2XnaRy<7^)b>dxN8Mp^?)$Px@_u!a*MKfw`4k#~ z5V1=%#WCgj5v$K@8PapxpK%Ye+J{EpwuxV5=DV{mHmBdg-hlJ81TGKzeJCK=sZ1PDcF@ zzSK6NO006Q>tUITTsN^U8ywBOI7+YLsNPfF_zc(#t8o**oe+5)uIa~5hOZ?V2Bmgp zanp>VO^B%URE#O!88FioC9$q8vfR3f2}06O?x+{)d~gSBo^pLNoZGOKE~f+ZS? zwOOrZ$aOQ2eDLFc7zXN0-xUXyY1$h+I0(4_d85R3i)o$aB{p+EhN~tStr>qV>ECmM@_)aK-zq9ZDd>~% z_ax}JPs34DhLyf$(5foD{EbIs+HsMe_WFxh8Y4xtAkmy9qsCek;yKOZR5I`!pQ{lTP^54T*Nxf|CarQC#)Wfu) zi0rnnb4!Y<2i5!aQukUNRqrd6L}860W~@asWTsu-WE5wp%&a2%lBBg5PsQhO&&x3Z zUgmhg6!#=XFC}R_Vh)pAVu^7;t2H zKInLEuvd;s_hM6n7tHr96}2p!_K<5LRSTEmBQH0azqgdKM$9O`pZ(58+I0&8ofr58ZJ0HXfA7Xk<;)pJmz&}o8_#)D&U40+HeEHCd0|Za?(NQXc zzGqcz#I#(%P))=LI)fSw&K4hD`3>~7L|0)0!MxZ|$&^A1x8~EdC zJH*R++}>eewfSceDa-%qI&FHI|6Qm3zw5MLeo{Y|U)E{U)BNu`?f<`Z+JCpz&x;6{ zsg9YEzMi!WwUw^^Q1p4lf8D3lRwh4XFq*Rjq~+$YXhoT< z{Jqr4jbcHDm9WahLmjN%;PSjSdB%@qME$~sRl5{y4b*kn3vBbV)fBi1*r%Vovs20I z?ajzpwPJj!SYS*>Dl$kN63#iK8VTB_0G7fq-IZaGP&lEyMwj1Q-mng@L+LBY*zBi8 zI9}m#1~0LFO)H1isg`Z>uf~h?tcvGmtk#7KV0w8Zkq7U;Ltvy z2pgGQ4uHwo)Ld;UJyQ2xart8Cbo9~5cB1Qa<=toPB>E$%y#{v2r|n1TktfXcl1e*% z6Yk+mgWHJh$}?OAp^}$ztMbZl9PD|SyZ4_OYCo2_|B;yg{`a!XozL7%-^jq$>W_Vw ztY6a9%=Uwh70th9g8x%1eG_vX8@>N#_2zHtvbNE&v9tSofQ^i2L8mA^|xlD}!E&c}~>CVJ1IHMgSqXlrHl?;WY09}jr$ zqUX;8JwJr-NBQSq|6Ow|%q?s!Ui#_3bQ10}?q9ps$W+Hb&zgqU$i`I1!ur4Xg8FPM zAt28~B_b(E^XK*TETPz?I3U?Ui_mzE8VRp1{UHGvT7uPs8w&x6+(0);;?p;w(wWK! z=YxzKfjm-QaI^1DoD7MWDwtZ7pT(D3*>r3~1n0;^xUJ=&mOg*8bV^Q%4DK6Krla~~ zptmg>7=d3hf5sP7w$OIG%0<~dA)$~J0iK^Hi% z5`eq_#CV#-)wG{bTSbzzQ7k+nF2L}iM)PxoKj(-}ir&bK$&(vVNS zl7ndAX(AeWTqvo4wWeEnbQJ+%Q^i>t*3CM+1E=+C5TY}8xhGr97iB*XSu}PFp3j6z z=`LceMYV;@kfd%hED_$r7<5I81~XfmS?Um5g)~EDlixsKJs?h+Aw8m7DQmOgI!crWxYXbLO4Z@2&n{qpoHyBAFT6kxHg=|}zZNAyM_vq=&Qhc~Hz0zQ` zpwV7Bj!UVcPrDzO+ZmN{W-yC>Q4@8l46Clnaj&mSfy3|1M zeryaO^@xvl_$DS#EK2t}-7X5H>Fv?5AtrVidxOJU>|v&111d{^U9!y~=U1&HUrZ6N zZ}-7f?bN&{_<%emR#ijn+qfquv(q?>cB^ zP(s;`iJ*ec50BCYb|6Q@kl1h$ws+^&>X^OZ$^U4iWeQGGiZR3W=jnf+{CfI)GCc2J zCm)joO82qlM*i)PW*9qUsDQqM$OW5wCA!b~sj%YqiY?&j;CU~ju|U6tOk1EY_CPk1 zK`Pt01;EtLl3XQ7`|uG!4$ZGtAE3*SY%8%=o9#Rf_1aUnMS&931nhIBc98>)hrzE^ z)H!lzQC|TNwIo7gxZ$dL*nd#O{bYh8nWpt!lQ{Ptg49ecW5uF4PWxudW4R zn;I5ik6*}lq=&0SdJbh9FtSCD|2}px=Q`I6xE7rx$YWa63H(n}_@6Y;Zx>cNz zSfP*&#VQ5T2c=9CWZnj zjR~~3^Z*Ty$daCy2wwiI3|qJgiWO=mgUb3XO8>n?!p~qKPXOWYlD8lBax!bA54w|P z2>rViis;i(UeEemb+WnATl+69(A5bQc@FOo)63ht1q!6+wt;`TQz8%jBHJbYg*U!L z>`zC`^AMgT#l$pt(+6jW-X{+DT&r6puH%xTz6gyW>p|-^Q~$e5)ijhTkY3(Zd+xG7 z-W5cfj{D}6Nrhw|%J(>iR#FEHVH`!Ux?00Tu&Q0F3W}v(fC3C5Ma19Alfes$g!p;}6G~HJ_!>>Ane%xorUJv)aCOTAx^|~zy&i7}n0n7e5MA&l%%^$t)%~wc zsFByL`%HtRc)%y`cFAd_DZJoyZ{?UG$Y=2X^bx*3R=xh>FdpH^+e?bjb`VDnu2aeh>;#7;}nmgw;(r=i!HbORxk|=&uouAf0#WJ1y9ruZ6I`P|qT9>`c?4)mx;Iz=hP^d*vDy>ezZYPTWzJE=`HxJQpv%-0?63@ zKyM`y9k~6v0a4-GI(LWT(U0#k8xn8*?T(SzH3COd4aeXugL&Om`g;|x-H}NtqAAGN}IX{hW;h&{vt86DAm3u(#*wR-a6Yp*H4o5i?8YXO*aME zFE@z?*ynu~v-onYAx*3J4JmpWwukU%haE*r!EoBFOk!F5wR7t~YX@_9Z8|J9vVuA{ zs6kC1B2xC;-45X^Z^#nOB86+ShOy^1YhOr>(QI(qb%|Tp?fE!fAK5%wG>rD)uW5E( zfpIKwoh~Z)N-EdPOHDMf3^aZv+0v34BoI6@mJUCf$alRKm`V5 zSuS;2ez-C3;Z)G~g|KrFx1-(8S>cCW&<`1&PHfyY1u<^(za1T{OFSwZb<0+wvcxJv zHiiM}+=wi71d$T89uhT)0aEXXPllT^)U%&dUwyU04iBAGC&qbe*$4YT=%h(yF3@C% z;^SD;IJly&NJW#}xh3Tiy_&o4bArAv;4A$3CYQuzm+`5@|9QN3;Y<>HUxlk|CU6hO zTro|!q<0zw-%*PDeZA-tE{9(#JkX|G9~$8UN6IN}N(-ixCCtjmx}!lSa7x>iGU8$U z+I8CSgA?>a@cN^Gz@tD{vw)oYXNN^o;o~Xfx2;YlkbJX{d2}!x-iGJ;;#^+4IVD=sgN-J_?{d3P7!7czjO~kSS`~f?3w8W(w3ihg=TE?QXj`2ku5gDwb&WoCi%#?qyyp^pp+O#2 zH<3nH1HsZZQOo2H=MNrX>~)c_LXayiMQyJLyP1yk^aq8yNW%ak`%KhSeR-Lh0E3_a z{qt7P|C$Z|`2S;L=pR-;rrp2pzw@%$^Uosx{PGt?v%ebtx@W-4^ZP%`!t-`MKZ)DF z+Wo3X|Hr)jPebkJ`S}lG@2^(BY8<~1RX+>cGpX^bcKu&1f7Nk&nX~;YI0Qem+x}J& z`L95~%8b0+!2elnsD1$bE&}&ggkJ?IUgSuA795se5dJxKzX~qA@S#5oGwUw^e+}Xf z;_0ud3_s0d+5TCh;jdo&O5^_?hkUg^8}Zw+hQA{Ga~!&Le?jOjQ*1&`bq!&ex?4j`YTH1-=O@Y1OFN2h1vR9 z_WlOtS9ZsDD8DK_~tT=SMHU z)c%7Ld?6Hnmj1uN`3HLOza_x$jl(zon_&GyF8;HVFZANiqVhL5|3NnX?&+_D)eGJD zv&1}u{Vn=E~`t^(D_fgW!^v4zTccUbov>4d4ybcf$=JTK5^D8K)m!tm!crzD+ literal 0 HcmV?d00001 diff --git a/Docs/Usage_with_libPLN/Usage_with_libPLN.odt b/Docs/Usage_with_libPLN/Usage_with_libPLN.odt new file mode 100755 index 0000000000000000000000000000000000000000..c4cc08fa4e376b3cc0ac9f465cb93519c7dcd808 GIT binary patch literal 95769 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πGM57!gaJm-&;hqdWPkYl)m5fpz@v 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 z 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 >O9)jB_9T5^5I6 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 literal 0 HcmV?d00001 diff --git a/Docs/Usage_with_libPLN/images/Dependencies.dia b/Docs/Usage_with_libPLN/images/Dependencies.dia new file mode 100755 index 0000000000000000000000000000000000000000..f62e4d8cb1173dc35a9bbcf013b854d00147aed5 GIT binary patch literal 2202 zcmV;L2xa#liwFP!000023+-K7kLorMem}ngab9WgC3ftd?p8fgkNQxxmAX|QQ6OGO z8xoXcZ++-*ubpHU2nhiahvux2_5zr3#&*7$Z#-kZ`~Gh87r)M7W4N zKV067|G4||4I6)d`{6s!cfXZC%h+9yG<};XtXQ9b5mEnR=nz_Y3$D*QZWkMwYVA2-NoO_IC==Zao(zEw}^r$ z9zD9j&G_Se_83nKG1EOcM`Ul@rI^RU{kvW?kPpr;5xo&{S?qcfCBAHuzHAPf)W}i4 zciI%K64@wR-hTYS{+M4<@xoSC)rHn}lCIr&>4%5?$gv6Xfj|~R2+6@9oN?gT4tyPx z^Shh(+g;FacX7Yn`N?b(#cAyN>0y`iC<=rd=DnuzgSfn(WZ?#Ki5*j`g%YJSK=B=bMM+gYSu?VVUi>sgblQnoXNVz1;59fyrMg8SAWz zzbS$sl4VQt)#dC|3i7kJ9 zg3JPB;>cf@bteL`o-Lv{JoFDbQ74n1VaTCMulT`Qh+YYQxk~G0qswH^qu3L1(}e>I zg8Tt(4j`oP!tVS~j{j^Q0BW4i`GpgatfHq`g@@a0zR&3;UrDpr_56qA*uY(=#m4o# zeb=BX$*Fiw#|?O2YEw7TnS;F7uH;MCc+yAAPn|Clg=tks>h;mRyY_<@c|C5Jj7LfO zqF_=YT!Eq@coeC>aChPS{3|%P|0?H%f&GWwuG4+&tpqH4LM_NiyMD?yWG!t2pj}7O z-(7}lQ9YR=d{|ewD#x6aDtlY!GfcPkvCd%rE^UwJ#-6m7cj^|{q3wmigslyxoG^_B z6Cw-139+EV9BDL(#kpJ{3_&yixcD4JN&3z(16mHnOg=b=lrhp1WY|ou4+9!api$@e zXO~pIEw-0(qIa~2q&p-X`6x^d?y%Q)Mk@(P#o|n(%w->K-<4X^L;6vYdJ6J2Qw7u( z`4G}d6%c&m3K;ak6)xAua0SB^3|BB*p=YjusNo8Ias}MlVp6SK0ofLy5Wlen9sA1^BQ0W4?5?!!c2TJG!LYcMhy9<}AWGF+Y zl)+={-cSZZ84P7;F#ccyk>L#eaR$g!Z=|~9Z&PRfLn~)^&PkTnvr5YK|&>?5wKnM;D zXE2<>aE8YG0t5|b=#Miv9poe-t)Vt(F^4f}$a2yEsRa?lYMBg&HQfDXSVJ$!q4pXK zbPSL+&?~V9)&*=HFxCuslA~8^!NIpXO><489G%oz!QG-eO2ypv;mzst$bEB%}M=|J?y;ly|cJc=*`?Xj-Kj!g)Jq| z0ue5cvm?^Bi$?xM`pb622eVZ1SzJ?Ih5H4H)+MHpX|A%eEySoggN8E)qHO<@VwA%L zMjGLwq*+A+XTpxYPmaUM6%Vg>19dsyf82*ehp98QpQDs0QQmIz0z%HKpSeX;yq%;L42|eSlIo7MJLrNKyTg|v57NrlwYB;F2KcE7bp7^ zpur<(pcinj=D(r=GJ#D)KTw-c1?np=t93PpG^=U*vWu4S=ahyxT>HVKIHT&_|U2g)Bn53)z@WjUEgdEmFxiR4~&)5+aANNh;BtQ*39aXB}-Pb%LrL-EC)L7G}$|F=lZ99{ui_ zqW1aguWMg-U75;9$k4-aGCvMb+t#`ja;R5T{$_y?xFO54K@2j<7VmOXgtgcdv9)Z9 zobat$sDp5)R)?bqck-PwRNZG@wovJmU?(?as9#Gc)0Cl_DCyT8B_$Ry2Ws{jB1P@L zxhtPEybaJk2|iq;nB2RwgFe6MC3Z)S-TV7rJghv~|shWc7$>j)`)M zP&c;O6v9=ut^^_p;j9}=Yv!J cJaAt`eET7PB7c^#yT1MKAChcT&C8Vl08E=PhX4Qo literal 0 HcmV?d00001 diff --git a/Docs/Usage_with_libPLN/images/Dependencies.png b/Docs/Usage_with_libPLN/images/Dependencies.png new file mode 100755 index 0000000000000000000000000000000000000000..97337ca3fbbafad0d035508047e6fcc39217aa10 GIT binary patch literal 31095 zcmZU*1z43^)HV8oAV`NGN{E1TS;VFr6$xqS5Kvm!l(eLzqJq+((jeWPf|Qgr(j5Xy zbLT$)|9$s9&)w%d91rez-?iqNbIdWu+@Y$<3d9802oMAzzON|z7(uY{5Cp3V9~*u{ z>GR?V{O6+SLj_sn9P>{~ZAJ`&Fe3M5r8L|U)+b$c)Gd$kwncE77;azYXJlk#;*?R9 zbl_5N@zY#u;QUop`XpyKqCjJfA@H>jXNeFQnIjjI%;ji*f8Vqa?$MX5^%6bfyIoxr z{K6`2yOlLHriw4>;$obZt}Y4R6^JK3 >qy?b}BErPbPvQkb??&;H~zB__cR8+*o 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 literal 0 HcmV?d00001 diff --git a/Makefile.vc b/Makefile.vc new file mode 100644 index 0000000..2a8c6e3 --- /dev/null +++ b/Makefile.vc @@ -0,0 +1,22 @@ +all: checkmakefiles + cd src && $(MAKE) -f Makefile.vc + +clean: checkmakefiles + cd src && $(MAKE) -f Makefile.vc clean + +cleanall: checkmakefiles + cd src && $(MAKE) -f Makefile.vc MODE=release clean + cd src && $(MAKE) -f Makefile.vc MODE=debug clean + +makefiles: + cd src && call opp_nmakemake -f --deep + +checkmakefiles: + @if not exist src\Makefile.vc ( \ + echo. && \ + echo ============================================================================ && \ + echo src/Makefile.vc does not exist. Please use the following command to generate it: && \ + echo nmake -f Makefile.vc makefiles && \ + echo ============================================================================ && \ + echo. && \ + exit 1 ) diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..2ca574d --- /dev/null +++ b/Readme.md @@ -0,0 +1,211 @@ + +LibPTP: A Library for PTP Simulation +============================================================== + +Project description +------------------------------- + +LibPTP is an implementation of the Precision Time Protocol (PTP) as it is specified in IEEE 1588-2008 in the simulation framework [OMNeT++][1]. +LibPTP uses models of standare network components from the [INET][2] library, and extends them with PTP functionality. + +This library is one of the outcomes of my master thesis, _Simulation of Time-synchronized Networks using IEEE 1588-2008_, published at the University of Technology in 2016. + +The current version of this library can be found on the [author's Github site][3]. + +Feature list of LibPTP: + +* Models for Ordinary, Boundary and Transparent Clocks +* Support for both End-to-End (E2E) as wells as Peer-to-Peer (P2P) delay measurement +* Debugging and tracing support +* Suport for PTP/Ethernet as specified in Annex F of IEEE 1588-2008 +* A model for a PI clock servo, and support for implementing custom clock servos +* Support for different kinds of filters +* __Realistic clock noise__ can optionally be provided by using [LibPLN][4], a library for efficient powerlaw noise generation. + +[1]: https://omnetpp.org/ +[2]: https://inet.omnetpp.org/ +[3]: https://github.com/w-wallner/libPTP +[4]: https://github.com/w-wallner/libPLN + +Project overview +------------------------------- + +The goal of LibPTP is it to provide an easy to use simulation tool for PTP networks. +OMNeT++ was chosen as the development environment because it provides a powerful tool with support for all stages of simulation development, from inital model design to carrying out sophisticated parameter studies. + +For an example for what is possible with LibPTP see the following image, which shows a parameter study for the sync interval: + + + +The images shows the mean offset value a PTP slave node that is directly connected to its master, depending on the configured sync interval. +The same simulation was carried out with two different oscillators, and LibPTP allows to study the behavior of each of them. + +Another advantage of LibPTP are its sophisticated debugging and tracing capabilities. +For example it is easy to trace all state decisions of a PTP node, as shown in the following image: + + + +Additionally, it would be possible to log every single step during the Best Master Clock Algorithm (BMCA) or the dataset comparison algorithm. + +Example PTP Simulations +------------------------------- + +The LibPTP repository contains only the actual simulation model for PTP nodes. +It does not contain any simulatons networks. +Example simulations can be found in the [PTP Simulations][10] repository. + +[10]: https://github.com/w-wallner/PTP_Simulations + +Node Symbols +------------------------------- + +OMNeT++ provides a powerful Graphical User Interface (GUI). +To make full use of it, LibPTP uses custom icons for all PTP related models. + +__PTP Nodes:__ + +The example components that come with LibPTP use an icon scheme to visualize there attributes: + +The node icons have a symbol in each corner: +* __Upper left:__ Node type + * N = End node (Ordinary Clock) + * B = Boundary Clock + * T = Transparent Clock + +* __Upper right:__ Delay mechanism + * P = Peer-to-Peer (P2P) + * E = End-to-End (E2E) + +* __Lower left:__ Timestamping capability + * 1 = 1-step clock (on-the-fly timestamps) + * 2 = 2-step clock + +* __Lower right:__ Clock attributes for Best Master Clock Algorithm (BMCA) + * Golden star: Excellent clock attributes, such a node will prefer its ports to be passive rather than slave + * Silver star: Average clock attributes, such a clock will loose in the BMCA against a clock with golden star + * Brozne star: Bad clock attributes, such a clock will loose in the BMCA against a clock with silver star + * No icon: Slave only + +The image in the center shows a boundary clock, with P2P delay mechanism, which is 1-step capable and has average clock attributes. + + + +__Example network:__ + +The folloing images shows an example network to visualize the example icons used by LibPTP: + + + +All nodes in the PTP network use the P2P delay mechanism (blue P). +The left-most node is a 1-step capable (1) ordinary clock (E) with excellent clock attributes (golden star). +It is connected to a 1-step capble boundary clock (B) with average clock attributes (silver star). +The boundary clock is connected to two daisy chains to the right. +The upper chain consist of two slave only (no icon) transparent clocks (T), where the second one is only 2-step capable (2), followed by an ordinary clock with bad clock attributes. +The lower chain consists of a transparent, a boundary and an ordinary clock. + +As only one clock has a golden star, we can safely assume that it will win the BMCA and become the grand master of the PTP network. + +__PTP Messages:__ + +As with the PTP nodes, LibPTP also provides custom icons for PTP messages: + + + +_Remark:_ While icons for Management and Signaling messages are provided, these two message types are currently not implemented. + + +Project structure +------------------------------- + +The _src_ directory contains most of the project relevant contents. +It structured as follwos: Most of the individual parts of a PTP system are loosely grouped into the folders _Hardware_, _Firmware_ and _Sotware_. +These individual parts are then plugged to together to form network components, which are placed in the _Components_ directory. +The _Utils_ folder contains generic utilities. + +The other directories are the following: + +* `doc:` Documentation for the project, mainly doxygen generated files. +* `Docs:` Various documents, mainly related to measuremnt results. +* `images:` The image files for the OMNeT++ models. +* `Tools:` This the place for generic tools like shell scripts. + +Documentation format +------------------------------- + +The documentation files in this repository are written in [Markdown][20] (line ending *.md). +They can be either read in a text editor, are converted to HTML using the markdown utility. + +[20]: https://daringfireball.net/projects/markdown/ + +Usage +------------------------------- + +__Documentation:__ + +* Doxygen files can be found in the _doc_ directory +* The _Docs_ contains various files with additional information (e.g. how to use LibPTP together with LibPLN) +* Both LibPLN and LibPTP have been developed as part of my master thesis: + +_Wolfgang Wallner_, __Simulation of Time-synchronized Networks using IEEE 1588-2008__, 2016, Vienna University of Technology + +This document contains an in-depth description on the various design decitions of both libraries, and would be the definitive source of information. +As of April 2016, it is __not yet published__. +Expected publication date: __May or June 2016__ + +__Supported platforms:__ + +LibPTP has been tested on Linux and Windows. +Non-Portable libraries have been avoided, thus it is likely that it will work (maybe with small adjustments) also on other platforms. + +__Requirements:__ + +* The [Boost C++ library][30], in a version before 5.x, preferrably 4.6 +* The [OMNeT++ simulation framework][31], in a version before 3.x, preferrably 2.6 +* The [INET library][32] +* The [OMNeT_Utils project][33] +* (optionally) The [LibPLN library][34], for realistic clock noise + +[30]: http://www.boost.org/ +[31]: https://omnetpp.org/omnetpp/category/30-omnet-releases +[32]: https://inet.omnetpp.org/Download.html +[33]: https://github.com/w-wallner/OMNeT_Utils +[34]: https://github.com/w-wallner/libPLN + +__Getting started:__ + +* Install OMNeT++ 4.6 and INET 2.6 +* Get the _OMNeT Utils_ repository, and add it in your OMNeT++ workspace +* Get _LibPTP_, and add it in your OMNeT++ workspace +* Create a new OMNeT++ project, and add LibPTP as project reference + * You can now use PTP nodes in your network simulation + * For an example on how to create PTP simulation networks, have a look at the examples in the _PTP Simulations_ repository +* (Optional) Install LibPLN, and provide information about its installation in the provided _makefrag_ file + * This enables you to simulate network nodes with realistic clock noise, and thus make PTP simulations more plausible + +Credits +------------------------------- + +The implementation of LibPTP would not have been possible without the availability of the following components: + +* The [Buttonized icon theme][40] +* The [OMNeT++ simulation framework][41] +* The [INET library][42], which provides a large set of models for standard network components +* The [Boost C++ library][43] + +[40]: http://kde-look.org/content/show.php/?content=161553 +[41]: https://omnetpp.org/ +[42]: https://inet.omnetpp.org/ +[43]: http://www.boost.org/ + +License +------------------------------- + +Most parts of this project are licensed under the _GPLv3 license_. See the _COPYING_ file for details. + +As stated, the icons use by LibPTP are based on the _Buttonized_ icon theme, which is licensed unter the _GPLv2 license_. +The images of LibPTP keep this license, and are thus also licensed unter the _GPLv2 license_. See the _images/LICENSE.txt_ file for details. + +Contact +------------------------------- + +Wolfgang Wallner (wolfgang-wallner [AT] gmx.at) diff --git a/Tools/Bash_Scripts/CleanUpSources.sh b/Tools/Bash_Scripts/CleanUpSources.sh new file mode 100755 index 0000000..0db3d3f --- /dev/null +++ b/Tools/Bash_Scripts/CleanUpSources.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Simple script to clean up source code (line endings, trailing spaces, ...) + +# ========================================================================== +# Variable initialization +# ========================================================================== +declare -a SRC_PATHS=("src" "simulations") +#declare -a SRC_PATHS=(".") +declare -a FILE_EXTENSIONS=("*.c" "*.cc" "*.cpp" "*.h" ".hpp" "*.msg" "*.ned" "*.ini") + +#declare -a SRC_PATHS=("../../src/Components/BasicBlocks") +#declare -a FILE_EXTENSIONS=("*.ned") + +# ========================================================================== +# Functions +# ========================================================================== + +# -------------------------------------------------------------------------- +# Clean source directory +# -------------------------------------------------------------------------- + +function CleanSourceDir +{ + Path=$1 + Ext=$2 + + echo "Searching for ${Ext} in ${Path}" + echo "" + + echo " Converting DOS encoding to UNIX encoding" + find ${Path} -name ${Ext} ! -type d -exec bash -c 'dos2unix -q "$0"' {} \; + + echo " Replacing tabs with 4 spaces" + find ${Path} -name ${Ext} ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \; + + echo " Removing trailing spaces" + SED_CMD='sed '\''s/[[:space:]]*$//'\'' $0 > /tmp/e && mv /tmp/e $0' + + find ${Path} -name ${Ext} ! -type d -exec bash -c "${SED_CMD}" {} \; +} + +# -------------------------------------------------------------------------- +# main +# -------------------------------------------------------------------------- +for SrcPath in "${SRC_PATHS[@]}" +do + for FileExt in "${FILE_EXTENSIONS[@]}" + do + + if [ "$#" -ne 1 ]; then + echo "Illegal number of parameters. Root directory required." + exit + fi + + ROOT_PATH="$1" + + echo "Root: " ${ROOT_PATH} + + CleanSourceDir "${ROOT_PATH}/${SrcPath}" ${FileExt} + + done +done diff --git a/doc/img/BC8_Port2_StateDecisions.png b/doc/img/BC8_Port2_StateDecisions.png new file mode 100644 index 0000000000000000000000000000000000000000..142708f7cd38e126a012a5ad6b3846ca68430f49 GIT binary patch literal 17996 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%0DDzFdqNQlPDLmgy=Jm2IBO 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Ð!0UGi}Jh5&>Y2Duw7+X@n# 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 CtGd_# literal 0 HcmV?d00001 diff --git a/doc/img/NodeSymbols_ExampleNetwork.png b/doc/img/NodeSymbols_ExampleNetwork.png new file mode 100644 index 0000000000000000000000000000000000000000..1357a8b73b0ddac97d6a0e540d15b61770ca0dc9 GIT binary patch literal 26128 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 z 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 kfk3oK0?>!`dOXn|l6z~TVEhJf^bDk~0#!ymeDd

^SSyDQk3kdLpuCSbuS?a@~~ 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 yV2fUcI2X0--7@_zs)3~D+6 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_E_1_M3.png b/images/PTP/Nodes/PTP_Nodes/N_E_1_M3.png new file mode 100644 index 0000000000000000000000000000000000000000..35eb97608ba4369a3c261bcf3254318ae64a61c3 GIT binary patch literal 4951 zcmZ`-WmMEp)c!3j4bs9A(y%BXAtBu%4ePq7gyfPUtiYne(%rSRlpvi-NJ*o_(ukBG zEFee;62iOx_uKp7opWZMb7$txoS8fKx%W(ziIENsB^xCG05p2Knq~x4{jZXf654X- z7fS>HJE<9}0YF^>)#Wo1f(-ID(@_ViM>)0#0=J#6nIQo93jzQp6adZ%T^Kw7yc7q3 zZ5sfP#{vM0XHKiBB4L2kR$oVxfP`|U(H2Mm3Qt`NZvdcT{I7z5tQ=MVU@*|rRD=0_ z`}u^zgxTqXqmGaLGQFl6oo`%oTt7337NNlCJV|=ru+W2FbeEaZo9amEy_RsX{mQRB z0`+L_ZIaC7Piro&H~9Ipz5LnIozfrZO@;3r-#gyi{haz@Xs5iU8FhWVrMw(?iT5SNAIoZ!_VhD>gkw9eZ!pXDkHrqPI#;XkvVOeg zg4V3mv~ImsISedyT&9EHfv+5@YdUDIk%6j?w1{MT4^)@`VuuzJ^=u=zM z(e?a=o5F%7#%E_U!fDvG8yrUpe}2iBEPq&Gg{(x>1#f)K)aZ$zEq(izdzzbV>qLp~ z1~}H{vb3p5dVYSM!p+U?czAPTW8$4*Hu7j=1ZHI=7H9}^kHw{aC=c+;7Yx2&U1AAj z5kaW(@bDDI6a@UOqT0>m=BBz{2`(xs0>BvISVge=_vWPN=xC_4blSw^FI~$$eBurVZL(i@bED5-}#?L&sBAax(HgXiS}2g;M3pBr8~1Vlf`-|y~pF# zjg2tOvRvTRCE7XS1WyG?beNl|9s8`MkDTtc@kh|>p8AkVQzB!O@vfKDN1J0pEG#TM zpjI^|Kd|24#IiLtmuW$SX<{Ox z-pkzFTwy6Gb1-23&Y*yXT_&+*?eD=_YGR@)8jU_z#s>C-RY~nv&%d{LKTNzRHZG!p zbh{A!J++=8H5*D*y}jnzKPM*|+279gU{bOH=5@f1qk_k&g?WtAhwi7EFakcGKYxba zy9fIL-Jsx@`_;$#Hh+Ise0@S#dhRh*PWJDx)r zzuc*kmYQ0YdUmq#JZ$xM=e1SfUVj|>RE@O?!JK^K)$&4AplSho> zCQK`Iew~uobDxAlo&yc(Lz_74MD6y;?m|pFmXwXi^Dym^Re_jg>tIgM>@QqV1Qevo zV-7~`%ou9_XPlY*f#*3r(&k|3I#zww<3*#`h_ODVg<}Pw-`c5w^?w=vw&S1G^x)27 z+s9btN`T;o2E;TVxu~S5n3$B>pyv4&Z>KY^E?qnOI12nydmDMLv_{0=^PQTQAqXSJ zCy^u*?@Hccd|?Dr9D1bD70BhLc6ujR+OJt5R4n7K`RtrYI(=ypS-nK=Dw$`s(^#J5 z$o+Y9Moh|^ixcMlL`H7*P$EtRpHydD>r2<6rZ@_q0w2S^_z7(uND}}uA`keykD(q6 zHZVtWG8<~WS7sAJht|B=We%8?d<|DHFbOOM3MQfv(?jdm~3vZuBg#$S!3QHuN{j#X8gcEa1Dugafhna{Aw@h44FoLEYWmOrt~e z7{!9xT9cBmA!M}BbRDjBRpHRg?QNGd4b5sBKUGo&dVWj16M4?j5pAJ5BX}jpS~YPf zfCj`vI}>Y9KCGehSC9rI{$b;g~O^KKIWyG23R$ncb0XudaqFAQ`>D?DD~Ti!g=cP5|ogm zl&qA_3@SA1;O8SeJRtd61C1wFDdho{=->aw~FGbZkGki$AT27>Ed2 zU(2u#kkA9{S%;;=tJ;Z~i~Y(d#y3HtBkM$>TWSxXVP^`7A$IK|p(@|0TDxj^BtAbT z=}^@I#v;Bczd6^1lQE<|X2bSenm(|vcvLe(hq)ha8!~ZYBRu=}a_2Jtf3))8l^p_s zm~^4r5BSnQON_;mrSUNRaMyv)$do&ox&&iKO>W75W~sGsGb8$*s6YwRk@)Pp)#7aOVMH5_RN;}%G$0!iSL8j_ zzhy$IG`o30XzY?5lH)3OBtG1}%dAc@Jl->$ji`&Vycv4Ct4{h)kTneI4<;owgTvGD z~8C?M^oGmCCxIxhb4S4%Bj#M`&Ul-HIxX%tv zxWBjiLwCe&zD@Q8Tlck13Yk3bgfqG7rS%85%wO-g13f%DEb2mDGz#8PQK;X-tNVns z4+da;IT=0;Rd)(m$TawuXJ^7I$19T58Us({vZj=>epO}*a;>ttGQ*|z)^n|Q^SFRfkiYOPRLbPT z?{&}|UnQfqnbxO^uOi)`vuZk;1TrcscvkYk>*oWD#Y_PVb4YNZ2-Bne)*F`#nNfA9 zKz}g8TTVYF=G~VkkH!~eKTeJ)mB7U7Wm~?*h?4-DTa65ZTkz@smbBN-uA>R1QrUgq(~c-8TB4nK2+l(p2{G`Fy>WjF`!)#BV^f z7xZ{;%E`hrAX-b2hKNq<*W8-j(&q7wYW~fiFxQ`|g^g4v4QN5(`;ecC6L41U$11uuzu**0Ec*SlMXtBB{`r6qoW2O;>;NZ+j zGsut?O~!RRZ0g7Z!eYzKT0gjxaOPz^=o@+?Tw$!h#>#Avr0ijPKm4%HZ2UA(jK$ZTXo3-!*1^dH$pOj^_nrit$sfwZAMpH@swO?fEt z!+~Gm--~UJTrQyrO8P|Kv*yj7nHjet6tl&iW5`K&8AC$Vzv(ZNnqwO88_@{OJx+LQ(tj6K416{S6*JuKl=iO zf-z!5wz`6dVHOs`T0KrmB)0#uC*$=(@UK^?TM=108Q93I!#>o@9WS_N8uT2JaBuLh z+w3>_zRaA81q52KB3!SnQ8`~MZGBv!#m-qb+ooRH&y=#@&1|)0A)gl~f!Jd{F8Rk0 z0=CcvXe4rM^kj}2xSf0}HM)vdJFIKmSFp~+m1%~oduY)%U^K+l%>P?49?NkPF@@cbOfx4Kw z&%8-2Q2uI^oI}y2FF1RKaDx}@447^u|Kl?LS8POyf&u&8WVR$_bSig=iv8ENRcqod zh~KozrCrs(zRj25F?SIqmVsXty?-tB{V+tG@e5$!fg8Q(hr@l=h!&}A$dxK zRVMv%ChuZfSxZp4c}G?0H=1E(&qgOqqisCjsdsYVJ|BMSQyofMr++>T5^S7dyz0%k zm2zo+Za?bzEqCGx^GR&b_l-s0<&p&DbKNCVPahvRBgX5`)@12cZxmzwo$oe+ct<`; zN;&BdT)P#5m|4o1f*XH!{Xo;HKkFijl+yTpn2C%w1P^o*Y8t=?R1Lih`|O*ZyRaqQ zZyDbbc-t4h4r_WGExr1Udv9LK&tpd4MHTYc?Xm5Xo%NxV`Y5D~p+gA=OITDgtMg_d zyq_>N9(l4AA{XtKoff6^l~(1_WC^LJUd#hj6zgY*MKRlKu18gL=Q^hMkXEQG-L@?@ znLrRF4(~lnxjH)b%Um<5XB+i!5ahH@)y}UReivh)Ev+ylWiTrAeFJeq$2KnCRcXNQ zi~YfJ*DBTRkp^&6jgYCPaay`1E7sjIp*Ob%qlup0S&)gsAXR+7eKFeAJh6d`MH#Kf zzoW6cQl+SSR=>Zsa61vsfAY4d#SRPjg#HqWmjXpe@YE+t3NQ{WW=c8XhtuvP@3uBK zm%RbVyDpS!iGI<^BIs?s!`06v=Tf@dtDY!GM*)6+G|a6^>XVd^es0jS7CHQ*S7oQ& zrYDrAMGgm+TgK*O6LnwJQURi`sZ;dx;(Y~t+lGQ% zx7YVxS@P?vv(4i^7He{ix&7C9{0QmV53+5-wqh;yO=Ekb?0%KX?>bJ8j)SrRmO;V zyZLrjMz2k?LIc+a>2VaTJd=GF)UW?h_86ZbDY+gf-;%Hm#ghA=Kwlv+6B7ye61PV3 zF)#60WDvWQV{B??wPoA8S+bj$!_N4(wmmg^Mf<`KR*h%t6*V2nRc@cQ{!ALH=sD$t zU@}PN;s+Wu$ne3wEOk3!k2VmqH81n0$~%~i20m(tqx}qu(Znp7L#e~E@*oV3ui1)Ob5e%6s1|LhhA z*EEFN!j<#1VNm-i@qqRCYx@RtuhF6PT}Hn~u7iea+MRvIHMXKq`i1d<$$l=gwW80; zu@?Ho^Y`_FN!R literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_E_1_SO.png b/images/PTP/Nodes/PTP_Nodes/N_E_1_SO.png new file mode 100644 index 0000000000000000000000000000000000000000..c754699465b028caffe35c92743177da17546483 GIT binary patch literal 3916 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 CDKNDF literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_E_2_M1.png b/images/PTP/Nodes/PTP_Nodes/N_E_2_M1.png new file mode 100644 index 0000000000000000000000000000000000000000..747df8b269f5ec2cc940ffbe60c121320ec3170e GIT binary patch literal 5170 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 QZ~_2LRUH-dV_W3^028{Rl>h($ literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_E_2_M2.png b/images/PTP/Nodes/PTP_Nodes/N_E_2_M2.png new file mode 100644 index 0000000000000000000000000000000000000000..f71124da65d1744de9368845fd5bcdf7e6deff65 GIT binary patch literal 4639 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 AqW}N^ literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_E_2_M3.png b/images/PTP/Nodes/PTP_Nodes/N_E_2_M3.png new file mode 100644 index 0000000000000000000000000000000000000000..5fbcb94fefc1d6ed197d8face1ea66f3f8ebad9b GIT binary patch literal 5046 zcmZ`-cQo8V*ZwVQiC&}kgy^e9EK$}XHhM3KwrYqT(XC#>>Lmz55G8sDqW2O#L@yyW zgdlqT_I=Oy&-ceS=gd53X3o7c_nhb6J9lEVHC4%onTP=ZAX9sS(8Zy`e>xEaH|qNIn6qH+@6?t0sslke>w=r%3%Ni>T)%NqQ1}k zrWv8Wp;1GzKy(4^Z%dH}-tj*mG|c>l2z`WdEFGo4au1$$JQGTZRWg0zV~i*Y6!%=P zrc#|}5GiQFS9f^0Y});`F^BGSVdZ3b#q1^z7_7MSwq>UAtm!l-@bAs2od4gtu~u*I z84CYM`a(L?Bkr`M&VdT90qtDr$ce1AZ*J8B{UItEjQ<$u8MPSX#gL4*M!owKeZk_n zV#4BgMY|X<4XOPR;64imc~R_Z2=>UR4-uIniMP%We96l1(nAsFJ|xk|63;+!gP&@2 zKyM2(#*D0#&SgXtmC^Z#5f8s`c6HUNtgPHvTT}e(G<&0gKNfBO%FXRe8n043Kv99D z�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 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_P_1_M2.png b/images/PTP/Nodes/PTP_Nodes/N_P_1_M2.png new file mode 100644 index 0000000000000000000000000000000000000000..155468d2656b6be2e6dc6d1eb3aa91353a16ea58 GIT binary patch literal 4686 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 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_P_1_M3.png b/images/PTP/Nodes/PTP_Nodes/N_P_1_M3.png new file mode 100644 index 0000000000000000000000000000000000000000..030a945a7a63e0e21ba05216f46ed2bae6240bf8 GIT binary patch literal 5095 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 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_P_1_SO.png b/images/PTP/Nodes/PTP_Nodes/N_P_1_SO.png new file mode 100644 index 0000000000000000000000000000000000000000..c415eefc0fa4a39bc65a5f59b918077af51bf322 GIT binary patch literal 4063 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 a?p_`b{%?V!Id4^h0AQeFf~eL)MgKo1hLBSL literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/N_P_2_SO.png b/images/PTP/Nodes/PTP_Nodes/N_P_2_SO.png new file mode 100644 index 0000000000000000000000000000000000000000..72a7e21888eaea3189564b09457bdba3ef266cf9 GIT binary patch literal 4179 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 KPMwxZ{Qm%yT%^SS literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/Node.xcf b/images/PTP/Nodes/PTP_Nodes/Node.xcf new file mode 100644 index 0000000000000000000000000000000000000000..6fe9b83e50dad17f89fc405b35362738d1709c83 GIT binary patch literal 51816 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 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_E_1_M1.png b/images/PTP/Nodes/PTP_Nodes/T_E_1_M1.png new file mode 100644 index 0000000000000000000000000000000000000000..830e5b40ac28777962fb88151965ba0a4a09b876 GIT binary patch literal 4756 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
SKV|YH| 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 X`2PuP%Fm0)1VBggjz*;#Cj5T@^dOGH literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_E_2_M1.png b/images/PTP/Nodes/PTP_Nodes/T_E_2_M1.png new file mode 100644 index 0000000000000000000000000000000000000000..f23ead9e5ee057c258f04dc460d3867ae835ede9 GIT binary patch literal 4858 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 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_E_2_M3.png b/images/PTP/Nodes/PTP_Nodes/T_E_2_M3.png new file mode 100644 index 0000000000000000000000000000000000000000..04e8b7495e4ffec1832134a394c07c986ffc38f1 GIT binary patch literal 4741 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 AbpQYW literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_E_2_SO.png b/images/PTP/Nodes/PTP_Nodes/T_E_2_SO.png new file mode 100644 index 0000000000000000000000000000000000000000..20ed0155c94149884b6cb88aa3318ae810f482ae GIT binary patch literal 3738 zcmZ`+WmuHW8h&@Fr37RF5$UBtSxQP8mZdwzk5;--N|umVY6(F?1nH1iLiPg}j3uN) z5D-uVL0Y;w>-lqjow=@=`En0D#4};9p}!VgA*^`;& zd(*N0h~7m_j2pAFAkmP}yS;w~96wX_hb#d)D>PhfG!=hBy}?T^0ubq-w_O ztVO{d^gO*)eR^8P=^J~&!Ny6%Jj;C8(OeE{Xo+llAku$t?(F5`;VXp&xfO+lcdDxR zG<(gg*Tt`2Es_y52&InEiIb8?mQp4fB5Ki8e!iw+gg}BEftH}m6oS5DmOL*y&RHy9 z>~L&-UB@$l47u$rj5j$q5S)q>M% zOLL}MJ2WbyiWE->Wi-b?JBhJys?&q7*bklPd|c_V&`tTJJOzhPLG> z?FZ!yo-Flcmfgqs$B_oA_Ykcmx|D7Dj7O+X4LR;=YHA{#olCh`T@3JniwmZuHa}tmoCyiehT#44jc5&P2~p$h zb!i`Bw;55xr8+~s#vE8Vfl!ApG{XND^Tw`b?ZANGYKu3*RaI5|sS<|1Hcs8i@N3EG z0X;>kF{QR|yD_cQ({t56C*t*=N)PH2~2adREeU2-}kPlUs3UN zoZfq@kgeIb^?p+G6khj#a%?~C^#@O#9kOxthtsRrc3zT1d=AXepT%&i>qW^5GWO6oiK&p(LVL7U#i0a*o*fm~Pdn!l4Nw%I#_e{)9J zDRZh^pH1@A3PfD`@h&YOCD|s4SxoOMm$!`D*r)S_j*tMhM?ND(QKB}@y;^|R{eb^Y zqp_2hFSLcyOED=*eEj6So;IbC>3ZJ}0ghL&u;Qn-+JMavXnHA?e81 zIy;4rH{?LmTmirO1YSvve_(N+taaP<*^ZH^lRMap&udx#{2}?Xj6j>KM)HJV(sTYG zMkY#c?JTX9>-$Gz&7Bw4tdMfa@jQ?gqCA=c#6)-%FE>NKFBl&5 zKAKDh{ODuV>H2!?M70~%UOPDjMW!yC@6obOU)53MWw3kWZ!Y!#*uiC5m{x>UKi087Hw0$t%UTCAPVok+L(GjY{_8|rY@M4I+!Qj z#E6Pnoo=f5bE>G=?~uS15eUXdlf@sYvQ3>I1ZA<>S^uI-ZDkOTG|JL2OUSNVwL*L% z5W;#LQN0(edWG=~sj0{02Y!X5-?@6{D@#Yxd7?d@t&*xGF@o;zO-&iQo}jg?JK7N( zn0&5)u@V}&`CfCqEmP!TNk6hQUo@-L;wk?XA|&wB@HC`LA>;ILeQLOY0;B!f_L{&Yu9ipK{y@H?R`&cV9zZfQ{3TrnP2`fXH}q1$fA5z*$&i~!Wqs3teeOg} z^tVxlcU5q_`0@HLGhCaf@pYc(UEe9N9ZBuAv{)>*G+<>Y7;MicAkc$Rf$W*5 zy^;9F8Km=88g}}JA^Fom^Q11;83>9=xE0Ej(9&5hp-Gv;K~#~1`-ET#?~qZGx*^dQ z*G~(*My0NTApxfYTTxQ}U ziKRIbLispmQm`im%77EDpkmYd+>wXUJ!(wS&^ab2C=MNj_Cm9x-=JI2edudwG^m+X zM@KIzM+ZKtVwq4#O3yoOTT-C+yEg};P2eXW39Qr=Vg>#R6&EiYt6m$R=vAEaWxVo6 z(;pG#d-G;Z+th=w!3nYm%0%OhU6UIw3y*n!-lMLlZKZAC1W4(p8+@7Fp?Y4V4KA8u zX^Mr5m5iV;r~ow5Ow=OTl*TgI(zCht=e@fsI@j=}36xw|_^DY9s27D7Lmtc=46F{@#VU;K0w>2-b_B^~t_iO2!RR43GCrGx9;?MUbgv*aWA0iR_{lkm?)v+Khow<0z3NT7Xf2+F8C2-YpO|)yROd-k538ZS|gWKP2J!{88AV zE%%-72_-rKK*m%zZ-|Aw8tqraYD&Tb0I=uGTaz$($W+=ltwR%Rge_;o(uFEXs;F-FK4< z-LITxI^-*jbwn*~&oLm|c45-^X8fL3mHJITb&nymwU_GVd?TwIgxD? z9NS)dwae4xLW%e7=HV_Usto6Zjy(Uo4dHcN5cEZUfOGDPQHsCCgmR{I`HnIZA#ySgW* z>-Dti9Ttaeg2UuO%&Fe?i=PskBZt4YE4^q-O$op!AUGqO z1JBO)h6hE$Cn*@kVruDN!ws(x)lW8Oq))cmxAl~|vgNj?pq7LXY4T;dTe9|nYIh~< z=9x#=bD(~8Y30X8yA|{A!q^ybLF?I|fH*2DCZ{PjXBaYyrZ7D!?DAwq|73l4x&P$U z9+Y(@7R`){)VIku?6Xh)(EE1bsYff$X8|vEv^6LB(oJ2(4`h3ws32d||E0!qu#TmE zpsLRDjiLKJJk!vxS#CX!G~`S|A6IGHkFB}(la=t2E^xx#FJe7I6U>g3<}{Hm15>LMe7?vyLE}8=-r6Ox)?K+VmCmDw9j4LVn>T;oHJn?me6n zo?A7U=Z&(eh%>y>S)^Hf7`h+LhbAW)9bRJc(x_&IyKPua{XRX>4Ablwrx?GYFgj9f zTV`R!6aQ;`ftpr<&JKYt@=pFPL;xhj;ZkDa5@HhKW^fsKDY(3(jEFc~UR=CUP3Z4u f@_!CqKF)5gA^+c@i!*(T=l~!!4K(W1?4$n!&mQ3x literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_P_1_M1.png b/images/PTP/Nodes/PTP_Nodes/T_P_1_M1.png new file mode 100644 index 0000000000000000000000000000000000000000..066062237eb7e94f43e95d568c95d21dac59e977 GIT binary patch literal 4899 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 dWbCo#lxb?e*A7=Lc02Pg_M4V*?}M zXhlI6e!=iW1=@R?941P9sq4D_=?*{4mdDy3<_|l-yHB2ORw>$aCCPmv4!G}TY^f4R z^uc@}C?de9P0k#iv|>IzbX0(>?np0=rlO{aXS0?d4v|LrF39%V>_RhU+dXzKZL}h} zY2(>NrAl`8)?D_OOVOp(sdyIi9zU3Uxtdk@xY136D>|A+UsY26JJN&(f`FfnO;@^7XfBbM*n zd}fJo%|-Q|DgaccQeQZb66+v8V{J8{a*%b6SmAWkF}?=?!Mp%~ivfT$ViRr^00Q6u zux<|k^4S2ugvo6*QXqaHyMISpgNVd*s@@bq1WJsKr5^xLGya=FKu#_*0MO^_YN(n9 zO)gncnxcB1IWsuMn#OynL!Xq%2?w=?5&WR}Z} zek<@n>ofeHWFpElvs)V@j@NgavepK-Ym<&~$D&M&J+60K>yH+i-!*p4H0i&T4_LRQ zPU=&B7DWb*Pvj;tXVQ7RN%x8bmhcZ96?`1;VH<-X}deF%;n>EQD&d(kdqZQNwIyU z%<26(D|XHBsP`fxCk$obSr>H{HH?yOsH=-QCaet?!}$2fKYsie;VNuRpp$eN)UdE%o}QVxNA^8=iZ%+M zq7yxl_3-eRn4A=xn3#B)ZeU=b?ch+*adx6oc?KYaLd;K@$Qogv9hw>;0H1nzGNUEY{)?2Cm9(Lj1%PN3j-B4O%2Avb}g(% zKDzNbI%8e@s{Prz%oz$0w_CMoO|D#Hm6nBTS!x>E+GT4hKeWOZ1#|PMZdx#NzhyAP zToDKaevtckCBD?8_&(lW;XyyMb)!EeEr-(QXmZ*su3hv|dy82G`f;X^xqI6l*3$1g zOo_?CiXG-T9IZ#+_y-wNe$JqtWuyA{K$f2QWy0>N#-i{SnY9;lb|G8R2qaR7{tKR! zjg3vNt(7?nmL#xO7=wpOx#Eb!7XqCiB_$cLSgekop7HJQjkvJ)Rd4QO-S2(E1AQNA zrUeZyF7I58Zd-j+)*fm6dGqp?YAtPH&pf}=U=C_b`R`!9GDl@)WgmEbw5%9mOo0au zUghrNq0+{@YM=-G*^#8wH#wD*u0B$>wbazsj^dw3FO?3z<-~NLHHCd5t?$doS z!t$9pEsR06-60$KNZ&GoRfEc~%uJ*U&ngh31qiBF{j75{alX>m*Uy%6V|ef_51*Ds z$8)W?lc@UD(9jU)w=(D!Uy~dFncDl;T|p+bPDTCgdruetP%yW4$8ffiujHwJ6DKwP z0F4x2fy2+Q-Jjz(TEuTL%wCbsIGrW4$m$O zHuW@OQVb9O_*AdXC^IW&$WpES6{mN{3{oajBt2=-JHMl=YXtLhaVa*7Ax>|B&+l$D zC`ZaIs^Q_(>w7O`5*1$Fx3#87+Oi4~QP3gYqlo+eVp9XB+p)F5MNW1|u za}&lni+^7VOVNemy+~lX53OilB_= z1Uwlkt;ZBiMq}(Z^RMZsfC~25YqV;#&o`FA?>$|2RWxdBG!SG5JJhWO0`Wih^PR(S z&m@HZk_D4x{`Ed%-7C;MKRXjl$;-<_gDfWA++p%xAJMJI%#|b|yKV@R3q9G@`(A9o zQEpMK1_QoY6hE>-t&V@7ZRD#MqJle+gvyh@GO3E(rKQk{;}T#t+bvCCECM1+yC^6& zqv>O>qN1ZmpT0#s+MF=*S(F;0XzR#R40&z!!OMMc$b*cGOpyLys5#`34amZLv_5!s zv=G^dmx18oe_nD>%w4GL($QOSzyiO0gYKnk8qAL(8JbB*`p&4SsEi8@gg9k=|LapN z1+AijL(34n0=Fhnm)oOdS!BY;`}^wQ;VoLgvugbvwBq})m6Y(+oa`!k&hNq$dgbsP zU8Tko?^U|=$APm-6eU9(O3X6=Rn7F!Qh*!HooL;5BAZE~EcfP&P6Ds3bqNXjHPA@Ha^jQ(D^7xP@#$DaZ?-qy{3vTf99=jo8B^4LUkyvURkglz&Cl7hV)E&ST(g5fDFGvEK_PlxM)gqE8SepjA%hX7P*; z>gO8pZMws@H<|{(e}C#a;fpV%41y!}p=)>h;lyd_N&aGQdbmCeXojiJm6Y(mVJDr} zYm+vmEnjIEzUZu3uN+l>*o59qwR}zgzclNsAdW?~*6ucb85y%vFeAuG>FL4NlzG>{ zjrQz2g}%AU!S*k@l9)WM+27iD*Jws-_Uxuwp!gHPMUs=3{|U#AWPyt%dx^$IBD9Fc zUiPGNR6&PMZ;3haY7&K??e}m*d@j^Zy+I_5+E}6TXJL}`A1}2jgV+Ujkzd^>i?P00 zAtYXzasRNrgW%rX%|#d`wDFYJc)q)N(>=uqj;3kfd9z>H=y7;nzBCVxBO4&yCX+IE z{;QL(#eU#1J(~ezYt{nZbtuZQU{3j&UIn{;qYp>sY3D<#itQwqxmT}CgAE}MJfBl6 z<(w(>e!9zymGT(7p225e#=3T$M_!7kirKCKF_K4^g4}}*wAgd=`su_EyICU>fSTJ+ z;iuw{wyj5TT$p;Wg=zF$gea|)mJgcwB+i>8`YKz>Ou70G&+I_GWSrIUB z=AzSoO-!=On#L*4RS+20$*T~5**}uLty)S~i+8@#FA9$cZ{M>`17lmP4ou_B zgH`8OqF}`2B&Q@n$jhs@YeshL_kOd7k2%K09Mq7^_Rq2WKi@`Z&1KLCT35oz)YMcK zHK(d7<^C3njHh8SqW_;Yqzd&CAmZ6qC@imBsHs2;A|y^E_N;s|U>Ty!7dGg$d48V1 zV~hVr6LETuc940dee^ih%4XtgpS=y&-mpb_IdQySixx0O6c@#UYx-P;f}*YOGJ|sbe1uq znqLYKS(nSaq!rx?9V;j)(IE|mf`S-6g&cc%PBq-)QL`%o0^G;dY&E>Cd-EebJZv+J z@{=}LYJ5zzj)x!K*|q#$V_buzekwi9yLjxvJy={N)+NFf;rJU&Z_i#?@iKa&%*A2u zDZDPL)Zv1|dQ#SVHhE#;K4b%dKx9+3@w?5ngiU<(E7pUytP0GJ7qK_sZ$;g~4r831 z$tn}+#;I3+Ex%>u$}Wo!=Y`!9SDI=me@ck=^Q%o`s;*3OH8*!&a94%?^S0-{MaWN8 zvmnF7JLgARQxNTLc__&B=K1kX4>A6YVB3;zwrRcoYFhy6pZz%b9KaAbsioN8apqJp9_dJ7btcaC+^!K`g7kVi%sK69`UJq9XZ5ddUw&mcyDcEez^-L_!iGSHhg? zhmX&iDQosEJRO%o{{30mtRrIJTbOT;7G%fx`%6LWXFp*P!3X@aVaGO^GU2zt%$gL> zfE69Il&ElCOcJ*A%@}7&I_%zG6-?s$i;gF~2uVKnI6hi!YZn7VHui5K7=uY=m3tkf zhzG7SDqrlXJCpX)wF-kwkGi|qS$?1W%E0O(vLsbwvdd!#b)Jo!D6J5xL9x5t&p#$0 zGDU^7*C}rBT@z4#z2SIqfthauu_6QSl7d%kzgj5|e=FR^65Lvw98fH&|KcQKbGu?x3wqTuvYb4f>YP&Uxf8-Q2tbu(B3TvOJ$sdo0#GxwoGoN zBQm6+D}H=^x%mS62BDEB(O*v`QMW4Q>*{{yD`=Rd$2NxTy*emNRqA+5&19-lM2~AK zDl@MV6q@hVt#4`{4`}Fw1U`1=-$Nxlae?3pHbuqPTJN$|E+cepi)yW;D0^zU7k*C* z5V-GK-{8ZVeLl6AWPK@dJ-Rt({it_7#pI$dQ%G=6S1nOOQc)SwBUBT!x_~v|L`t%0sLN zN~uy6ZhxUt&JKx(w?1>-^t5Z3a%@RWsY!Kl6lR`f^ERO9{;sP|rv?%h+Q|@NDMrW{ z1J$@qUM9QH$f}kzJ%P_XeYS5Y%ut(KLJ_5% zYU3AJ6+LP`S#B6CdP&)D7cX{K;Z?%)M?Z4Ah)d~8yH?vb96_UPLsxrCUlXT@3v2`y z8q35B7P&5_ledIrSMSsM!hCj%49fD)qu_hC&eaHRi4!l-(@0VwaiU z#&^Hu_L96!C!{W4U?FWG&cUg7kOs}CpVEX6TcKKcVtPN#4)>(YA1ky6v@?F(-MKGg z(4~8|C#b4*UOIgqK^d2S?ZY)vaO*qUJei2}95Kid>e89sQ&?;*vVx8 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_P_1_SO.png b/images/PTP/Nodes/PTP_Nodes/T_P_1_SO.png new file mode 100644 index 0000000000000000000000000000000000000000..0eda0a503d0c093c656604fc728e0f177fa1622f GIT binary patch literal 3763 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 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_P_2_M1.png b/images/PTP/Nodes/PTP_Nodes/T_P_2_M1.png new file mode 100644 index 0000000000000000000000000000000000000000..1a28bc92566406a466e6cff3b395868743761d33 GIT binary patch literal 5008 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 G)c*j KE2 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_P_2_M2.png b/images/PTP/Nodes/PTP_Nodes/T_P_2_M2.png new file mode 100644 index 0000000000000000000000000000000000000000..a2c435b441e6309528eda4b3b3229ad4cfdc6215 GIT binary patch literal 4499 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 TjJ0M73II~WK)p%TKK6eA1$#VR literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/PTP_Nodes/T_P_2_M3.png b/images/PTP/Nodes/PTP_Nodes/T_P_2_M3.png new file mode 100644 index 0000000000000000000000000000000000000000..0df06f75b0c40f3d715b8915e1b7eb9616cf2e79 GIT binary patch literal 4881 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 g%l{+b<>Tn)9P?2qHk#e0AUgUfR6)!BdQ9& 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 literal 0 HcmV?d00001 diff --git a/images/PTP/Nodes/TimeDiffObserver/License.txt b/images/PTP/Nodes/TimeDiffObserver/License.txt new file mode 100644 index 0000000..c2b6244 --- /dev/null +++ b/images/PTP/Nodes/TimeDiffObserver/License.txt @@ -0,0 +1 @@ +The TimeDiffObserver icon is a renamed and resized version of the gnome-util.png file from the GPLv2 licensed Buttonized icon theme. diff --git a/images/PTP/Nodes/TimeDiffObserver/TimeDiffObserver.png b/images/PTP/Nodes/TimeDiffObserver/TimeDiffObserver.png new file mode 100644 index 0000000000000000000000000000000000000000..0065ba1172e02fc202faeb702176f6be62465fa1 GIT binary patch literal 4765 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 literal 0 HcmV?d00001 diff --git a/simulations/.gitignore b/simulations/.gitignore new file mode 100644 index 0000000..4c76274 --- /dev/null +++ b/simulations/.gitignore @@ -0,0 +1,4 @@ +Makefile +omnetpp.ini +new.anf +core diff --git a/simulations/package.ned b/simulations/package.ned new file mode 100644 index 0000000..b4dc586 --- /dev/null +++ b/simulations/package.ned @@ -0,0 +1,25 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.simulations; + +@license(PTP); diff --git a/simulations/run b/simulations/run new file mode 100755 index 0000000..55ee262 --- /dev/null +++ b/simulations/run @@ -0,0 +1,4 @@ +#!/bin/sh +cd `dirname $0` +../src/ptp_simulation -n .:../src $* +# for shared lib, use: opp_run -l ../src/ptp_simulation -n .:../src $* diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..f3c7a7c --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +Makefile diff --git a/src/Components/BasicBlocks/IPTP_EtherNode.ned b/src/Components/BasicBlocks/IPTP_EtherNode.ned new file mode 100644 index 0000000..589aab5 --- /dev/null +++ b/src/Components/BasicBlocks/IPTP_EtherNode.ned @@ -0,0 +1,56 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Components.BasicBlocks; + +// ====================================================== +// Imports +// ====================================================== + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +moduleinterface IPTP_EtherNode +{ + parameters: + + @display("i=PTP/Nodes/PTP_Nodes/Generic"); + + string PTP_ClockType; + bool PTP_TwoStepFlag; + string PTP_DelayMechanism; + + string ClockServoType; + + gates: + + inout ethg[] @labels(EtherFrame-conn); +} diff --git a/src/Components/BasicBlocks/PTP_BasicNode.ned b/src/Components/BasicBlocks/PTP_BasicNode.ned new file mode 100644 index 0000000..b192484 --- /dev/null +++ b/src/Components/BasicBlocks/PTP_BasicNode.ned @@ -0,0 +1,139 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Components.BasicBlocks; + +// ====================================================== +// Imports +// ====================================================== + +import libptp.Software.PTP_Stack.*; +import libptp.Software.ClockServo.IClockServo; +import libptp.Software.ClockServo.*; +import libptp.Software.PTP_EthernetMapping.*; + +import libptp.Firmware.*.*; +import libptp.Hardware.*; + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +module PTP_BasicNode like IPTP_EtherNode +{ + parameters: + + @display("i=PTP/Nodes/PTP_Nodes/Generic"); + + string PTP_ClockType = default("PTP_CLOCK_TYPE_ORDINARY"); + bool PTP_TwoStepFlag = default(false); + string PTP_DelayMechanism = default("DELAY_MECH_E2E"); + + string ClockServoType = default("PI_ClockServo"); + + gates: + + inout ethg[] @labels(EtherFrame-conn); + + submodules: + + PTP_Stack: PTP_Stack { + parameters: + + @display("p=174,52"); + + PTP_ClockType = PTP_ClockType; + PTP_TwoStepFlag = PTP_TwoStepFlag; + PTP_DelayMechanism = PTP_DelayMechanism; + + ClockPath = default( "^.NIC.Clock" ); + + gates: + PtpIn[sizeof(ethg)]; + PtpOut[sizeof(ethg)]; + PortConfig[sizeof(ethg)]; + PortRequest[sizeof(ethg)]; + } + + ClockServo: like IClockServo { + parameters: + @display("p=60,52"); + } + + PTP_EthMap: PTP_EthernetMapping { + parameters: + @display("p=233,171"); + + gates: + upperLayerIn[sizeof(ethg)]; + upperLayerOut[sizeof(ethg)]; + } + + LLC: EthernetII_LLC { + parameters: + @display("p=233,292"); + + EtherTypeMapping = default("35063:0"); + } + + NIC: PTP_NIC { + parameters: + @display("p=174,406"); + + PTP_ClockType = PTP_ClockType; + PTP_DelayMechanism = PTP_DelayMechanism; + PTP_TwoStepFlag = PTP_TwoStepFlag; + + gates: + ethg[sizeof(ethg)]; + PortConfig[sizeof(ethg)]; + PortRequest[sizeof(ethg)]; + } + + connections allowunconnected: + + for i=0..sizeof(ethg)-1 { + PTP_Stack.PtpOut[i] --> PTP_EthMap.upperLayerIn[i]; + PTP_Stack.PtpIn[i] <-- PTP_EthMap.upperLayerOut[i]; + } + + PTP_EthMap.lowerLayerOut --> LLC.upperLayerIn++; + PTP_EthMap.lowerLayerIn <-- LLC.upperLayerOut++; + + LLC.lowerLayerOut --> NIC.upperLayerIn; + LLC.lowerLayerIn <-- NIC.upperLayerOut; + + for i=0..sizeof(ethg)-1 { + NIC.ethg[i] <--> ethg[i]; + + NIC.PortConfig[i] <-- PTP_Stack.PortConfig[i]; + NIC.PortRequest[i] --> PTP_Stack.PortRequest[i]; + } +} diff --git a/src/Components/Cables/Cables.ned b/src/Components/Cables/Cables.ned new file mode 100644 index 0000000..d464638 --- /dev/null +++ b/src/Components/Cables/Cables.ned @@ -0,0 +1,80 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== +package libptp.Components.Cables; + +// ====================================================== +// Imports +// ====================================================== + +import inet.nodes.ethernet.Eth1G; + +// ====================================================== +// Channel declarations +// ====================================================== + +channel GigabitCable extends Eth1G +{ +} + +channel GigabitCable20cm extends Eth1G +{ + parameters: + + length = 0.2m; +} + +channel GigabitCable1m extends Eth1G +{ + parameters: + + length = 1m; +} + +channel GigabitCable10m extends Eth1G +{ + parameters: + + length = 10m; +} + +channel GigabitCable100m extends Eth1G +{ + parameters: + + length = 100m; +} + +channel GigabitCable1000m extends Eth1G +{ + parameters: + + length = 1000m; +} + +// ====================================================== +// Network declarations +// ====================================================== + diff --git a/src/Components/Nodes/InternalNodes/PTP_InternalNodes.ned b/src/Components/Nodes/InternalNodes/PTP_InternalNodes.ned new file mode 100644 index 0000000..3f2d75d --- /dev/null +++ b/src/Components/Nodes/InternalNodes/PTP_InternalNodes.ned @@ -0,0 +1,101 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Components.Nodes.InternalNodes; + +// ====================================================== +// Imports +// ====================================================== + +import libptp.Components.BasicBlocks.PTP_BasicNode; + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +// ------------------------------------------------------ +// Base class for internal nodes +// ------------------------------------------------------ + +module PTP_Internal_Node extends PTP_BasicNode +{ + parameters: + + @display("i=PTP/Components/InternalModule/InternalModule"); + + submodules: + + connections: +} + +// ------------------------------------------------------ +// Slave/Master capabilities +// ------------------------------------------------------ + +module PTP_Internal_Node_SO extends PTP_Internal_Node +{ + parameters: + + PTP_Stack.SlaveOnly = true; + + PTP_Stack.Priority1 = default(128); + PTP_Stack.ClockClass = default("CLOCK_CLASS_SLAVE_ONLY"); + PTP_Stack.ClockAccuracy = default("CLOCK_ACCURACY_UNKNOWN"); +} + +module PTP_Internal_Node_M3 extends PTP_Internal_Node +{ + parameters: + + PTP_Stack.SlaveOnly = false; + + PTP_Stack.ClockClass = default("CLOCK_CLASS_DEFAULT"); + PTP_Stack.ClockAccuracy = default("CLOCK_ACCURACY_1_S"); +} + +module PTP_Internal_Node_M2 extends PTP_Internal_Node +{ + parameters: + + PTP_Stack.SlaveOnly = false; + + PTP_Stack.ClockClass = default("CLOCK_CLASS_DEFAULT"); + PTP_Stack.ClockAccuracy = default("CLOCK_ACCURACY_1_US"); +} + +module PTP_Internal_Node_M1 extends PTP_Internal_Node +{ + parameters: + + PTP_Stack.SlaveOnly = false; + + PTP_Stack.ClockClass = default("CLOCK_CLASS_PRIMARY"); + PTP_Stack.ClockAccuracy = default("CLOCK_ACCURACY_25_NS"); +} diff --git a/src/Components/Nodes/PTP_BoundaryClocks.ned b/src/Components/Nodes/PTP_BoundaryClocks.ned new file mode 100644 index 0000000..749e5c9 --- /dev/null +++ b/src/Components/Nodes/PTP_BoundaryClocks.ned @@ -0,0 +1,201 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Components.Nodes; + +// ====================================================== +// Imports +// ====================================================== + +import libptp.Components.Nodes.InternalNodes.*; + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +module PTP_BC_E2E_1S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_1_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_E2E_1S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_1_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_E2E_1S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_1_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_E2E_1S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_1_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_E2E_2S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_2_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_E2E_2S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_2_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_E2E_2S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_2_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_E2E_2S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_E_2_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_BC_P2P_1S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_1_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_BC_P2P_1S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_1_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_BC_P2P_1S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_1_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_BC_P2P_1S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_1_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_BC_P2P_2S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_2_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_BC_P2P_2S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_2_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_BC_P2P_2S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_2_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_BC_P2P_2S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/B_P_2_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} diff --git a/src/Components/Nodes/PTP_Endnodes.ned b/src/Components/Nodes/PTP_Endnodes.ned new file mode 100644 index 0000000..ac96587 --- /dev/null +++ b/src/Components/Nodes/PTP_Endnodes.ned @@ -0,0 +1,249 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Components.Nodes; + +// ====================================================== +// Imports +// ====================================================== + +import libptp.Components.Nodes.InternalNodes.*; + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +module PTP_EN_E2E_1S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_1_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_E2E_1S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_1_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_E2E_1S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_1_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_E2E_1S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_1_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_E2E_2S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_2_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_E2E_2S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_2_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_E2E_2S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_2_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_E2E_2S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_E_2_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_1S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_1_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_1S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_1_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_1S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_1_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_1S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_1_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_2S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_2_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_2S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_2_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_2S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_2_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} + +module PTP_EN_P2P_2S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/N_P_2_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_ORDINARY"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; + + gates: + ethg[1]; +} diff --git a/src/Components/Nodes/PTP_TransparentClocks.ned b/src/Components/Nodes/PTP_TransparentClocks.ned new file mode 100644 index 0000000..e1208ba --- /dev/null +++ b/src/Components/Nodes/PTP_TransparentClocks.ned @@ -0,0 +1,201 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Components.Nodes; + +// ====================================================== +// Imports +// ====================================================== + +import libptp.Components.Nodes.InternalNodes.*; + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +module PTP_TC_E2E_1S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_1_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_E2E_1S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_1_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_E2E_1S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_1_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_E2E_1S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_1_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_E2E_2S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_2_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_E2E_2S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_2_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_E2E_2S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_2_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_E2E_2S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_E_2_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_E2E"; +} + +module PTP_TC_P2P_1S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_1_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_TC_P2P_1S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_1_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_TC_P2P_1S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_1_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_TC_P2P_1S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_1_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = false; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_TC_P2P_2S_SO extends PTP_Internal_Node_SO +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_2_SO"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_TC_P2P_2S_M1 extends PTP_Internal_Node_M1 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_2_M1"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_TC_P2P_2S_M2 extends PTP_Internal_Node_M2 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_2_M2"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} + +module PTP_TC_P2P_2S_M3 extends PTP_Internal_Node_M3 +{ + parameters: + @display("i=PTP/Nodes/PTP_Nodes/T_P_2_M3"); + + PTP_ClockType = "PTP_CLOCK_TYPE_TRANSPARENT"; + PTP_TwoStepFlag = true; + PTP_DelayMechanism = "DELAY_MECH_P2P"; +} diff --git a/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.cc b/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.cc new file mode 100644 index 0000000..fca01e1 --- /dev/null +++ b/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.cc @@ -0,0 +1,241 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "TimeDiffObserver.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(cTimeDiffObserver); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +simtime_t +cTimeDiffObserver::RoundSimtime( simtime_t t, int Exponent ) +{ + if( Exponent <= t.getScaleExp() ) + { + return t; + } + + if( t >= SIMTIME_ZERO ) + { + t += simtime_t(5, Exponent-1); + } + else + { + t -= simtime_t(5, Exponent-1); + } + + t = simtime_t( t.inUnit(Exponent), Exponent); + + return t; +} + +std::string +cTimeDiffObserver::GetUnitString( int Exponent ) +{ + std::string UnitString; + + switch( Exponent ) + { + case 3: UnitString = " ks"; break; + case SIMTIME_S: UnitString = " s"; break; + case SIMTIME_MS: UnitString = " ms"; break; + case SIMTIME_US: UnitString = " us"; break; + case SIMTIME_NS: UnitString = " ns"; break; + case SIMTIME_PS: UnitString = " ps"; break; + case SIMTIME_FS: UnitString = " fs"; break; + + default: + { + std::stringstream ss; + ss << " *10^" << Exponent; + UnitString = ss.str(); + break; + } + } + + return UnitString; +} + +int SelectExponent( simtime_t t ) +{ + if( t == SIMTIME_ZERO ) + { + return -12; + } + + if( t < SIMTIME_ZERO ) + { + t = -t; + } + + if( t >= simtime_t(1, SIMTIME_MS)) + { + t = t; + } + + int64_t t64 = t.inUnit( t.getScaleExp() ); + double log = log10( t64 ); + int ilog = log; + + ilog = ilog + t.getScaleExp(); + + if( ilog < 0 ) + ilog -= 2; + + ilog /= 3; + ilog *= 3; + + return ilog; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +cTimeDiffObserver::ParseResourceParameters() +{ + ClockPath1 = par( "ClockPath1" ).stringValue(); + ClockPath2 = par( "ClockPath2" ).stringValue(); +} + +void +cTimeDiffObserver::AllocateResources() +{ + pClock1 = check_and_cast( getModuleByPath( ClockPath1.c_str() ) ); + pClock2 = check_and_cast( getModuleByPath( ClockPath2.c_str() ) ); + + pTraceMsg = new cMessage("TimeDiffObserver: Trace TimeDiff" ); +} + +void +cTimeDiffObserver::ParseParameters() +{ + TraceInterval = simtime_t( par("TraceInterval").doubleValue() ); + GuiExponent = par( "GuiExponent" ).longValue(); + AutomaticGuiExponent = par( "AutomaticGuiExponent").boolValue(); +} + +void +cTimeDiffObserver::RegisterSignals() +{ + // Register signals + HwTimeDiff_SigId = registerSignal( "HwTimeDiff" ); + ScaledTimeDiff_SigId = registerSignal( "ScaledTimeDiff" ); +} + +void +cTimeDiffObserver::InitInternalState() +{ +} + +void +cTimeDiffObserver::InitSignals() +{ +} + +void +cTimeDiffObserver::FinishInit() +{ + if( TraceInterval != 0 ) + { + scheduleAt( simulation.getWarmupPeriod() + TraceInterval, pTraceMsg ); + } +} + +// ------------------------------------------------------ +// Handle messages +// ------------------------------------------------------ +void +cTimeDiffObserver::handleMessage(cMessage *pMsg) +{ + if( pMsg == pTraceMsg ) + { + simtime_t ScaledTimeDiff = pClock1->GetScaledTime() - pClock2->GetScaledTime(); + + emit( ScaledTimeDiff_SigId, ScaledTimeDiff ); + + int Exponent = AutomaticGuiExponent ? SelectExponent( ScaledTimeDiff ) : GuiExponent; + + std::stringstream ss; + + ss << "TimeDiff: " << RoundSimtime( ScaledTimeDiff, Exponent ).inUnit( Exponent ) << GetUnitString( Exponent ); + DisplayString = ss.str(); + + getDisplayString().setTagArg("t", 0, DisplayString.c_str()); + + scheduleAt( simTime() + TraceInterval, pTraceMsg ); + } +} + +// ------------------------------------------------------ +// Finish +// ------------------------------------------------------ +void +cTimeDiffObserver::finish() +{ +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cTimeDiffObserver::cTimeDiffObserver() +{ + DisplayString = "TimeDiff: no data yet"; +} + +// ------------------------------------------------------ +// Desctructor +// ------------------------------------------------------ +cTimeDiffObserver::~cTimeDiffObserver() +{ + cancelAndDelete( pTraceMsg ); +} + diff --git a/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.h b/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.h new file mode 100644 index 0000000..8e5b204 --- /dev/null +++ b/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.h @@ -0,0 +1,96 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_TIME_DIFF_OBSERVER_H_ +#define LIBPTP_TIME_DIFF_OBSERVER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "ModuleInitBase.h" +#include "ScheduleClock.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cTimeDiffObserver: public cModuleInitBase +{ + private: + + // Resources + cMessage *pTraceMsg; + cScheduleClock *pClock1; + cScheduleClock *pClock2; + + std::string DisplayString; + + // Configuration + std::string ClockPath1; + std::string ClockPath2; + simtime_t TraceInterval; + + // Gui configuration + int GuiExponent; + bool AutomaticGuiExponent; + + // Internal housekeeping + + // Signal handling + simsignal_t HwTimeDiff_SigId; + simsignal_t ScaledTimeDiff_SigId; + + // Internal functions + static simtime_t RoundSimtime( simtime_t t, int Exponent ); + static std::string GetUnitString( int Exponent ); + + // Initialization + void ParseResourceParameters(); + void AllocateResources(); + void ParseParameters(); + void RegisterSignals(); + void InitInternalState(); + void InitSignals(); + void FinishInit(); + + protected: + + // OMNeT API + void handleMessage(cMessage *msg); + void finish(); + + public: + + cTimeDiffObserver(); + ~cTimeDiffObserver(); +}; + +#endif diff --git a/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.ned b/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.ned new file mode 100644 index 0000000..9878908 --- /dev/null +++ b/src/Components/Utilities/TimeDiffObserver/TimeDiffObserver.ned @@ -0,0 +1,54 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Components.Utilities.TimeDiffObserver; + +simple TimeDiffObserver +{ + parameters: + + @display("i=PTP/Nodes/TimeDiffObserver/TimeDiffObserver"); + @class(cTimeDiffObserver); + + // Hardware configuration + string ClockPath1 = default( "^.Clock1" ); + string ClockPath2 = default( "^.Clock2" ); + + // Trace configuration + double TraceInterval @unit(s) = default(0); // 0 -> tracing disabled + + // GUI configuration + int GuiExponent = default(-6); + bool AutomaticGuiExponent = default(true); + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + @signal[HwTimeDiff](type=simtime_t); + @signal[ScaledTimeDiff](type=double); + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- + @statistic[HwTimeDiff](record=stats?,vector?); + @statistic[ScaledTimeDiff](record=stats?,vector?); +} diff --git a/src/Firmware/EthernetII_LLC/EthernetII_LLC.cc b/src/Firmware/EthernetII_LLC/EthernetII_LLC.cc new file mode 100644 index 0000000..1d0f41a --- /dev/null +++ b/src/Firmware/EthernetII_LLC/EthernetII_LLC.cc @@ -0,0 +1,106 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "EthernetII_LLC.h" + +#include +#include + +#include "Ieee802Ctrl_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(EthernetII_LLC); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +EthernetII_LLC::AllocateResources() +{ + upperLayerOutBaseID = gate("upperLayerOut",0)->getBaseId(); + lowerLayerInID = gate("lowerLayerIn")->getId(); + lowerLayerOutID = gate("lowerLayerOut")->getId(); + + // Sanity checks + if( gate("upperLayerOut",0)->getVectorSize() != gate("upperLayerIn",0)->getVectorSize() ) + { + throw cRuntimeError( "Size of upper layer gate vectors for input and output direction does not match." ); + } +} + +void +EthernetII_LLC::ParseParameters() +{ + mapping.parseProtocolMapping( par("EtherTypeMapping").stringValue() ); +} + +// ------------------------------------------------------ +// Handle messages +// ------------------------------------------------------ +void +EthernetII_LLC::handleMessage(cMessage *msg) +{ + if( msg->getArrivalGate()->getId() == lowerLayerInID ) + { + Ieee802Ctrl *pCtrl = check_and_cast(msg->getControlInfo()); + int EthType = pCtrl->getEtherType(); + int GateID = mapping.findOutputGateForProtocol( EthType ); + + if( GateID == -2 ) + { + EV_WARN << "Dropping frame with unregistered Ethertype (" << EthType << ")" << endl; + + delete msg; + } + else + { + send( msg, upperLayerOutBaseID + GateID ); + } + } + else + { + send( msg, lowerLayerOutID ); + } +} diff --git a/src/Firmware/EthernetII_LLC/EthernetII_LLC.h b/src/Firmware/EthernetII_LLC/EthernetII_LLC.h new file mode 100644 index 0000000..9857eb7 --- /dev/null +++ b/src/Firmware/EthernetII_LLC/EthernetII_LLC.h @@ -0,0 +1,64 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_ETHERNET_II_LLC_H_ +#define LIBPTP_ETHERNET_II_LLC_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include +#include + +#include "ModuleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class EthernetII_LLC : public cModuleInitBase +{ + private: + + // Resources + ProtocolMapping mapping; + int lowerLayerInID; + int lowerLayerOutID; + int upperLayerOutBaseID; + + // Config + + // Init API + void AllocateResources(); + void ParseParameters(); + + protected: + + virtual void handleMessage(cMessage *msg); +}; + +#endif diff --git a/src/Firmware/EthernetII_LLC/EthernetII_LLC.ned b/src/Firmware/EthernetII_LLC/EthernetII_LLC.ned new file mode 100644 index 0000000..fa15325 --- /dev/null +++ b/src/Firmware/EthernetII_LLC/EthernetII_LLC.ned @@ -0,0 +1,55 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Firmware.EthernetII_LLC; + +// ====================================================== +// Imports +// ====================================================== + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +simple EthernetII_LLC +{ + parameters: + @display("i=block/fork_l"); + + string EtherTypeMapping = default(""); + + gates: + + input upperLayerIn[] @labels(Ieee802Ctrl/down); + output upperLayerOut[] @labels(Ieee802Ctrl/up); + + input lowerLayerIn @labels(Ieee802Ctrl/up); + output lowerLayerOut @labels(Ieee802Ctrl/down); +} diff --git a/src/Hardware/DualDelayer/DelayQueue/DelayQueue.cc b/src/Hardware/DualDelayer/DelayQueue/DelayQueue.cc new file mode 100644 index 0000000..ad50966 --- /dev/null +++ b/src/Hardware/DualDelayer/DelayQueue/DelayQueue.cc @@ -0,0 +1,144 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "DelayQueue.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module( DelayQueue ); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Private functions +// ------------------------------------------------------ +void +DelayQueue::HandleReminder() +{ + cMessage *pNext = MsgQueue.front(); + + MsgQueue.pop(); + send( pNext, pOutGate ); + ScheduleReminder(); +} + +void +DelayQueue::ScheduleReminder() +{ + if + ( + ( !MsgQueue.empty() ) && + ( !ReminderMsg->isScheduled() ) + ) + { + simtime_t delay = simtime_t( delayPar->doubleValue() ); + + scheduleAt( simTime() + delay, ReminderMsg ); + } +} + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ +void +DelayQueue::AllocateResources() +{ + ReminderMsg = new cMessage( "DelayQueue Reminder" ); + pOutGate = gate( "out" ); +} + +void +DelayQueue::ParseParameters() +{ + delayPar = &par("Delay"); + MaxSize = par( "Size" ).longValue(); + + if( MaxSize <= 0 ) + { + throw cRuntimeError( "Delay queue size must be strictly positive." ); + } +} + +// ------------------------------------------------------ +// OMNeT API +// ------------------------------------------------------ +void +DelayQueue::handleMessage(cMessage *pMsg) +{ + if( pMsg->isSelfMessage() ) + { + HandleReminder(); + } + else + { + if( MsgQueue.size() < MaxSize ) + { + MsgQueue.push( pMsg ); + ScheduleReminder(); + } + else + { + delete pMsg; + } + } +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +DelayQueue::DelayQueue() +{ + MaxSize = 1; + ReminderMsg = NULL; + pOutGate = NULL; +} + +DelayQueue::~DelayQueue() +{ + while( !MsgQueue.empty() ) + { + delete MsgQueue.front(); + MsgQueue.pop(); + } + + cancelAndDelete( ReminderMsg ); +} diff --git a/src/Hardware/DualDelayer/DelayQueue/DelayQueue.h b/src/Hardware/DualDelayer/DelayQueue/DelayQueue.h new file mode 100644 index 0000000..0bbb8ba --- /dev/null +++ b/src/Hardware/DualDelayer/DelayQueue/DelayQueue.h @@ -0,0 +1,76 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_DELAY_QUEUE_H_ +#define LIBPTP_DELAY_QUEUE_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include + +#include "ModuleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class DelayQueue: public cModuleInitBase +{ + private: + + // Configuration + size_t MaxSize; + + // Resources + std::queue MsgQueue; + cMessage *ReminderMsg; + cPar *delayPar; + cGate *pOutGate; + + // Private functions + void HandleReminder(); + void ScheduleReminder(); + + protected: + + // Init API + void AllocateResources(); + void ParseParameters(); + + // OMNeT API + virtual void handleMessage(cMessage *pMsg); + + public: + DelayQueue(); + ~DelayQueue(); +}; + +#endif + diff --git a/src/Hardware/DualDelayer/DelayQueue/DelayQueue.ned b/src/Hardware/DualDelayer/DelayQueue/DelayQueue.ned new file mode 100644 index 0000000..e2579fc --- /dev/null +++ b/src/Hardware/DualDelayer/DelayQueue/DelayQueue.ned @@ -0,0 +1,38 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.DualDelayer.DelayQueue; + +import inet.base.IHook; + +simple DelayQueue like IHook +{ + parameters: + @display("i=status/lightning"); + + volatile double Delay @unit(s) = default(0s); + int Size = default(10); + + gates: + input in; + output out; +} diff --git a/src/Hardware/DualDelayer/DualDelayer.ned b/src/Hardware/DualDelayer/DualDelayer.ned new file mode 100644 index 0000000..5fcc2ac --- /dev/null +++ b/src/Hardware/DualDelayer/DualDelayer.ned @@ -0,0 +1,77 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.DualDelayer; + +import libptp.Hardware.DualDelayer.DelayQueue.*; + +module DualDelayer like IDualDelayer +{ + parameters: + + @display("i=PTP/Components/Delayer/Delayer"); + + volatile double UpDelay @unit(s) = default(0s); + volatile double DownDelay @unit(s) = default(0s); + + bool EnableUpDelay = default(false); + bool EnableDownDelay = default(false); + + gates: + + input upperLayerIn; + output upperLayerOut; + input lowerLayerIn; + output lowerLayerOut; + + submodules: + + UpDelayer: DelayQueue if EnableUpDelay == true { + parameters: + Delay = UpDelay; + @display("p=62,30"); + } + + DownDelayer: DelayQueue if EnableDownDelay == true { + parameters: + Delay = DownDelay; + @display("p=62,76"); + } + + connections: + + if EnableDownDelay == true { + upperLayerIn --> DownDelayer.in; + DownDelayer.out --> lowerLayerOut; + } + if EnableDownDelay == false { + upperLayerIn --> lowerLayerOut; + } + + if EnableUpDelay == true { + lowerLayerIn --> UpDelayer.in; + UpDelayer.out --> upperLayerOut; + } + if EnableUpDelay == false { + lowerLayerIn --> upperLayerOut; + } +} diff --git a/src/Hardware/DualDelayer/IDualDelayer.ned b/src/Hardware/DualDelayer/IDualDelayer.ned new file mode 100644 index 0000000..52cd033 --- /dev/null +++ b/src/Hardware/DualDelayer/IDualDelayer.ned @@ -0,0 +1,43 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.DualDelayer; + +moduleinterface IDualDelayer +{ + parameters: + + @display("i=PTP/Components/Delayer/Delayer"); + + volatile double UpDelay @unit(s); + volatile double DownDelay @unit(s); + + bool EnableUpDelay; + bool EnableDownDelay; + + gates: + + input upperLayerIn; + output upperLayerOut; + input lowerLayerIn; + output lowerLayerOut; +} diff --git a/src/Hardware/DualDelayer/NopDualDelayer.ned b/src/Hardware/DualDelayer/NopDualDelayer.ned new file mode 100644 index 0000000..767758c --- /dev/null +++ b/src/Hardware/DualDelayer/NopDualDelayer.ned @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.DualDelayer; + +module NopDualDelayer like IDualDelayer +{ + parameters: + + @display("i=PTP/Components/Delayer/Delayer"); + + volatile double UpDelay @unit(s) = default(0s); + volatile double DownDelay @unit(s) = default(0s); + + bool EnableUpDelay = default(false); + bool EnableDownDelay = default(false); + + gates: + + input upperLayerIn; + output upperLayerOut; + input lowerLayerIn; + output lowerLayerOut; + + connections: + + upperLayerIn --> lowerLayerOut; + lowerLayerIn --> upperLayerOut; +} diff --git a/src/Hardware/EtherPhy/EtherPhy.ned b/src/Hardware/EtherPhy/EtherPhy.ned new file mode 100644 index 0000000..117a519 --- /dev/null +++ b/src/Hardware/EtherPhy/EtherPhy.ned @@ -0,0 +1,49 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.EtherPhy; + +import libptp.Hardware.EtherPhy.IEtherPhy; + +import ned.DelayChannel; +import utils.Channels.VolatileDelayChannel.VolatileDelayChannel; + +module EtherPhy like IEtherPhy +{ + parameters: + // The Rx delay is added to incoming frames (phy -> mii). + volatile double rxDelay @unit(s) = default(176ns + intuniform(0,4) * 8ns); + + // The Tx delay is added to outgoing frames (mii -> phy). + volatile double txDelay @unit(s) = default(76ns + intuniform(0,1) * 8ns); + + @display("i=PTP/Components/PHY/PHY"); + + gates: + inout mii @labels(EtherFrame); // Media Independent Interface to MAC + inout phy @labels(EtherFrame); // Phy interface to network + + connections: + + mii$i --> VolatileDelayChannel{ delay = txDelay; } --> phy$o; + mii$o <-- VolatileDelayChannel{ delay = rxDelay; } <-- phy$i; +} diff --git a/src/Hardware/EtherPhy/IEtherPhy.ned b/src/Hardware/EtherPhy/IEtherPhy.ned new file mode 100644 index 0000000..b38aa09 --- /dev/null +++ b/src/Hardware/EtherPhy/IEtherPhy.ned @@ -0,0 +1,40 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.EtherPhy; + +moduleinterface IEtherPhy +{ + parameters: + + @display("i=PTP/Components/PHY/PHY"); + + // The Rx delay is added to incoming frames (phy -> mii). + volatile double rxDelay @unit(s); + + // The Tx delay is added to outgoing frames (mii -> phy). + volatile double txDelay @unit(s); + + gates: + inout mii @labels(EtherFrame); // Media Independent Interface to MAC + inout phy @labels(EtherFrame); // Phy interface to network +} diff --git a/src/Hardware/HwClock/AdjustableClock/AdjustableClock.cc b/src/Hardware/HwClock/AdjustableClock/AdjustableClock.cc new file mode 100644 index 0000000..ed82b38 --- /dev/null +++ b/src/Hardware/HwClock/AdjustableClock/AdjustableClock.cc @@ -0,0 +1,395 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "AdjustableClock.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(cAdjustableClock); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ + +void +cAdjustableClock::SetScalePoint( simtime_t HwTime, simtime_t ScaledTime, int64_t ScaleFactor_ppb, bool Reset ) +{ + if( ScaledTime < SIMTIME_ZERO ) + { + throw cRuntimeError( "Can't set ScaledTime to a negative value." ); + } + + // Limit ScaleFactor + int64_t NewScaleFactor_ppb = ScaleFactor_ppb; + double ScaleFactor; + + if( NewScaleFactor_ppb < ScaleFactor_LowerBound_ppb ) + NewScaleFactor_ppb = ScaleFactor_LowerBound_ppb; + if( NewScaleFactor_ppb > ScaleFactor_UpperBound_ppb ) + NewScaleFactor_ppb = ScaleFactor_UpperBound_ppb; + + ScaleFactor = (1E9L+NewScaleFactor_ppb) / 1E9L; + + assert( ScaleFactor != 0.0L ); + + ScalePoints[0] = ScalePoints[1]; + ScalePoints[1].HwTime = RoundDownToTickLen( HwTime );; + ScalePoints[1].ScaledTime = ScaledTime; + ScalePoints[1].ScaleFactor_ppb = NewScaleFactor_ppb; + ScalePoints[1].ScaleFactor = ScaleFactor; + + if( Reset ) + { + ScalePoints[0].HwTime = SIMTIME_ZERO; + ScalePoints[0].ScaledTime = SIMTIME_ZERO; + ScalePoints[0].ScaleFactor = ScalePoints[1].ScaledTime / ScalePoints[1].HwTime; + ScalePoints[0].ScaleFactor_ppb = (ScalePoints[0].ScaleFactor * 1E9L) - 1E9L; + } + + // Emit statistics signal + emit( ScaleFactor_ppb_SigId, (long) NewScaleFactor_ppb ); +} + +// ------------------------------------------------------ +// OMNeT API +// ------------------------------------------------------ + +void +cAdjustableClock::handleMessage(cMessage *pMsg) +{ + // Handle self-message (can only be schedule App msgs) + if( pMsg->isSelfMessage() ) + { + if( pTimeTraceMsg == pMsg ) + { + emit( ScaledTime_SigId, GetScaledTime() ); + + cHwClock::handleMessage( pMsg ); + } + } + else + { + throw cRuntimeError( "HwClock: Received unexpected message" ); + delete pMsg; + } +} + +void +cAdjustableClock::finish() +{ +} + +// ------------------------------------------------------ +// Time conversion +// ------------------------------------------------------ +simtime_t +cAdjustableClock::GetScaledTimeAtHwTime( simtime_t HwTime ) const +{ + if( HwTime < ScalePoints[0].HwTime ) + { + throw cRuntimeError( "Clock: Can't calculate ScaledTime based on old HwTime." ); + } + + simtime_t BaseScaledTime; + simtime_t BaseHwTime; + double ScaleFactor; + + if( HwTime >= ScalePoints[1].HwTime ) + { + BaseScaledTime = ScalePoints[1].ScaledTime; + BaseHwTime = ScalePoints[1].HwTime; + ScaleFactor = ScalePoints[1].ScaleFactor; + } + else + { + BaseScaledTime = ScalePoints[0].ScaledTime; + BaseHwTime = ScalePoints[0].HwTime; + ScaleFactor = ScalePoints[0].ScaleFactor; + } + + simtime_t HwDiff = RoundDownToTickLen(HwTime) - BaseHwTime; + simtime_t ScaledDiff = HwDiff * ScaleFactor; + + return BaseScaledTime + ScaledDiff; +} + +simtime_t +cAdjustableClock::GetHwTimeAtScaledTime( simtime_t ScaledTime ) const +{ + if( ScaledTime == ScalePoints[1].ScaledTime ) + { + return ScalePoints[1].HwTime; + } + + if( ScaledTime < ScalePoints[0].ScaledTime ) + { + std::stringstream ss; + + ss << "Clock: Can't calculate HwTime based on old ScaledTime." << endl; + ss << "Requested ScaledTime: " << ScaledTime << endl; + ss << "ScaledTime 0: " << ScalePoints[0].ScaledTime << endl; + ss << "ScaledTime 1: " << ScalePoints[1].ScaledTime << endl; + + throw cRuntimeError( ss.str().c_str() ); + } + + simtime_t BaseScaledTime; + simtime_t BaseHwTime; + double ScaleFactor; + + if( ScaledTime >= ScalePoints[1].ScaledTime) + { + BaseScaledTime = ScalePoints[1].ScaledTime; + BaseHwTime = ScalePoints[1].HwTime; + ScaleFactor = ScalePoints[1].ScaleFactor; + } + else + { + BaseScaledTime = ScalePoints[0].ScaledTime; + BaseHwTime = ScalePoints[0].HwTime; + ScaleFactor = ScalePoints[0].ScaleFactor; + } + + simtime_t ScaledDiff = (ScaledTime - BaseScaledTime); + simtime_t HwDiff = ScaledDiff / ScaleFactor; + simtime_t HwTime = RoundDownToTickLen( BaseHwTime + HwDiff ); + + while( GetScaledTimeAtHwTime(HwTime) < ScaledTime ) + { + HwTime += TickLenNom; + } + + if( HwTime >= BaseHwTime + TickLenNom ) + { + assert( GetScaledTimeAtHwTime(HwTime-TickLenNom) < ScaledTime); + } + assert( GetScaledTimeAtHwTime(HwTime) >= ScaledTime ); + + return HwTime; +} + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ + +void +cAdjustableClock::AllocateResources() +{ + cHwClock::AllocateResources(); +} + +void +cAdjustableClock::ParseParameters() +{ + cHwClock::ParseParameters(); + + EnableAdjustments = par("EnableAdjustments").boolValue(); + ScaleFactor_LowerBound_ppb = par("ScaleFactor_LowerBound_ppb").longValue(); + ScaleFactor_UpperBound_ppb = par("ScaleFactor_UpperBound_ppb").longValue(); + + if( ScaleFactor_UpperBound_ppb < 0 ) + { + throw cRuntimeError( "Clock: Can't have a negative upper bound for scale factor: %d", ScaleFactor_UpperBound_ppb ); + } + if( ScaleFactor_LowerBound_ppb > 0 ) + { + throw cRuntimeError( "Clock: Can't have a positive lower bound for scale factor: %d", ScaleFactor_LowerBound_ppb ); + } + + ScalePoints[1].ScaledTime = simtime_t( par("BeginScaledTime").doubleValue() ); +} + +void +cAdjustableClock::RegisterSignals() +{ + cHwClock::RegisterSignals(); + + ScaledTime_SigId = registerSignal( "ScaledTime" ); + ScaleFactor_ppb_SigId = registerSignal( "ScaleFactor_ppb" ); + + WATCH(ScalePoints[1].ScaleFactor_ppb); + WATCH(LastScaledTime); +} + +void +cAdjustableClock::InitInternalState() +{ + cHwClock::InitInternalState(); + + SetScalePoint( SIMTIME_ZERO, ScalePoints[1].ScaledTime, 0, true ); +} + +void +cAdjustableClock::InitSignals() +{ + cHwClock::InitSignals(); + + emit( ScaledTime_SigId, GetScaledTime() ); +} + +void +cAdjustableClock::FinishInit() +{ + cHwClock::FinishInit(); +} + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +cAdjustableClock::cAdjustableClock() + : cHwClock() +{ + EpochID = 0; + + LastScaledTime = SIMTIME_ZERO; + ScalePoints[0].HwTime = SIMTIME_ZERO; + ScalePoints[0].ScaledTime = SIMTIME_ZERO; + ScalePoints[1].HwTime = SIMTIME_ZERO; + ScalePoints[1].ScaledTime = SIMTIME_ZERO; +} + +cAdjustableClock::~cAdjustableClock() +{ +} + +// ------------------------------------------------------ +// Time adjustment API +// ------------------------------------------------------ + +void +cAdjustableClock::HandleTimeJump( simtime_t Delta ) +{ + EpochID ++; +} + +void +cAdjustableClock::IncScaledTime( const simtime_t Delta ) +{ + EnterModuleSilent(); + + if( EnableAdjustments ) + { + SetScaledTime( GetScaledTime() + Delta ); + } + + LeaveModule(); +} + +void +cAdjustableClock::SetScaledTime( const simtime_t ScaledTime ) +{ + EnterModuleSilent(); + + if( EnableAdjustments ) + { + simtime_t Delta = ScaledTime - GetScaledTime(); + + SetScalePoint( GetHwTime(), ScaledTime, GetScaleFactor_ppb(), true ); + HandleTimeJump( Delta ); + } + + LeaveModule(); +} + +void +cAdjustableClock::SetScaleFactor_ppb( const int64_t ScaleFactor_ppb ) +{ + EnterModuleSilent(); + + if( EnableAdjustments ) + { + SetScalePoint( GetHwTime(), GetScaledTime(), ScaleFactor_ppb, false ); + } + + LeaveModule(); +} + +// ------------------------------------------------------ +// Clock API +// ------------------------------------------------------ +simtime_t +cAdjustableClock::GetScaledTime() +{ + LastScaledTime = GetScaledTimeAtHwTime( GetHwTime() ); + + return LastScaledTime; +} + +cLocalTimeStamp +cAdjustableClock::GetTimeStamp() +{ + return cLocalTimeStamp( EpochID, GetScaledTime() ); +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +int64_t +cAdjustableClock::GetScaleFactor_ppb() +{ + // Remark: EnterModule omitted as function only return a simple value + + return ScalePoints[1].ScaleFactor_ppb; +} + +int64_t +cAdjustableClock::GetScaleFactor_LowerBound_ppb() +{ + // Remark: EnterModule omitted as function only return a simple value + + return ScaleFactor_LowerBound_ppb; +} + +int64_t +cAdjustableClock::GetScaleFactor_UpperBound_ppb() +{ + // Remark: EnterModule omitted as function only return a simple value + + return ScaleFactor_UpperBound_ppb; +} diff --git a/src/Hardware/HwClock/AdjustableClock/AdjustableClock.h b/src/Hardware/HwClock/AdjustableClock/AdjustableClock.h new file mode 100644 index 0000000..5fec979 --- /dev/null +++ b/src/Hardware/HwClock/AdjustableClock/AdjustableClock.h @@ -0,0 +1,122 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_ADJUSTABLE_CLOCK_H_ +#define LIBPTP_ADJUSTABLE_CLOCK_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "HwClock.h" +#include "LocalTimeStamp.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cAdjustableClock: public cHwClock +{ + private: + + // Types + struct ScalePoint_t + { + simtime_t HwTime; + simtime_t ScaledTime; + int64_t ScaleFactor_ppb; + double ScaleFactor; + }; + + // Resources + uint64_t EpochID; + + // Configuration + int64_t ScaleFactor_LowerBound_ppb; + int64_t ScaleFactor_UpperBound_ppb; + + // Internal housekeeping + ScalePoint_t ScalePoints[2]; + + // Signal handling + simsignal_t ScaledTime_SigId; + simsignal_t ScaleFactor_ppb_SigId; + + // Watch variables + simtime_t LastScaledTime; + + // Debug functions + + // Internal functions + void SetScalePoint( simtime_t HwTime, simtime_t ScaledTime, int64_t ScaleFactor_ppb, bool Reset ); + + protected: + + // Configuration + bool EnableAdjustments; + + // OMNeT API + virtual void handleMessage(cMessage *msg); + virtual void finish(); + + // API for sub-classes + simtime_t GetScaledTimeAtHwTime( simtime_t HwTime ) const; + simtime_t GetHwTimeAtScaledTime( simtime_t ScaledTime ) const; + + // Clock API + simtime_t GetScaledTime(); + virtual void HandleTimeJump( simtime_t Delta ); + + // Init API + void AllocateResources(); + void ParseParameters(); + void RegisterSignals(); + void InitInternalState(); + void InitSignals(); + void FinishInit(); + + public: + + // Constructors/Destructor + cAdjustableClock(); + ~cAdjustableClock(); + + // Time adjustment API + void IncScaledTime ( const simtime_t Delta ); + void SetScaledTime ( const simtime_t ScaledTime ); + void SetScaleFactor_ppb ( const int64_t ScaleFactor_ppb ); + + // Clock API + cLocalTimeStamp GetTimeStamp(); + + // Getters + int64_t GetScaleFactor_ppb(); + int64_t GetScaleFactor_LowerBound_ppb(); + int64_t GetScaleFactor_UpperBound_ppb(); +}; + +#endif + diff --git a/src/Hardware/HwClock/AdjustableClock/AdjustableClockTypes.ned b/src/Hardware/HwClock/AdjustableClock/AdjustableClockTypes.ned new file mode 100644 index 0000000..48552da --- /dev/null +++ b/src/Hardware/HwClock/AdjustableClock/AdjustableClockTypes.ned @@ -0,0 +1,58 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.AdjustableClock; + +import libptp.Hardware.HwClock.HwClock.IPerfectHwClock; +import libptp.Hardware.HwClock.HwClock.IConstantDriftHwClock; +import libptp.Hardware.HwClock.HwClock.ISineHwClock; +import libptp.Hardware.HwClock.HwClock.ILibPLN_HwClock; + +moduleinterface IPerfectAdjustableClock extends IAdjustableClock, IPerfectHwClock +{ + parameters: + + @display("i=PTP/Components/Clock/PerfectClock"); +} + +moduleinterface IConstantDriftAdjustableClock extends IAdjustableClock, IConstantDriftHwClock +{ + parameters: + + @display("i=PTP/Components/Clock/ConstantDriftClock"); +} + +moduleinterface ISineAdjustableClock extends IAdjustableClock, ISineHwClock +{ + parameters: + + @display("i=PTP/Components/Clock/SineClock"); +} + +moduleinterface ILibPLN_AdjustableClock extends IAdjustableClock, ILibPLN_HwClock +{ + parameters: + + @display("i=PTP/Components/Clock/RealClock"); +} + + diff --git a/src/Hardware/HwClock/AdjustableClock/IAdjustableClock.ned b/src/Hardware/HwClock/AdjustableClock/IAdjustableClock.ned new file mode 100644 index 0000000..9dcafd6 --- /dev/null +++ b/src/Hardware/HwClock/AdjustableClock/IAdjustableClock.ned @@ -0,0 +1,37 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.AdjustableClock; + +import libptp.Hardware.HwClock.HwClock.IHwClock; + +moduleinterface IAdjustableClock extends IHwClock +{ + parameters: + @display("i=PTP/Components/Clock/DigitalClock"); + + // Clock configuration + bool EnableAdjustments; + int ScaleFactor_LowerBound_ppb; + int ScaleFactor_UpperBound_ppb; + double BeginScaledTime @unit(s); +} diff --git a/src/Hardware/HwClock/AdjustableClock/Internal_AdjustableClock.ned b/src/Hardware/HwClock/AdjustableClock/Internal_AdjustableClock.ned new file mode 100644 index 0000000..e8d82a7 --- /dev/null +++ b/src/Hardware/HwClock/AdjustableClock/Internal_AdjustableClock.ned @@ -0,0 +1,51 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.AdjustableClock; + +import libptp.Hardware.HwClock.HwClock.Internal_HwClock; + +simple Internal_AdjustableClock extends Internal_HwClock like IAdjustableClock +{ + parameters: + + @display("i=PTP/Components/InternalModule/InternalModule"); + @class("cAdjustableClock"); + + // Clock configuration + bool EnableAdjustments = default(true); + int ScaleFactor_LowerBound_ppb = default(-32767999); // Limit value taken from Intel i210 data sheet + int ScaleFactor_UpperBound_ppb = default(32767999); + double BeginScaledTime@unit(s) = default(uniform(0ms,5ms)); + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + @signal[ScaledTime](type=double); + @signal[ScaleFactor_ppb](type=long); + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- + @statistic[ScaledTime](record=vector?); + @statistic[ScaleFactor_ppb](record=stats?,vector?); +} diff --git a/src/Hardware/HwClock/ClockEvents/ClockEvent.cc b/src/Hardware/HwClock/ClockEvents/ClockEvent.cc new file mode 100644 index 0000000..e36fda2 --- /dev/null +++ b/src/Hardware/HwClock/ClockEvents/ClockEvent.cc @@ -0,0 +1,187 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "ClockEvent.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cClockEvent::cClockEvent() +{ + this->ID1 = 0; + this->ID2 = 0; + this->ID3 = 0; + this->ID4 = 0; + this->ContextPtr = nullptr; +} + +cClockEvent::cClockEvent( const int ID1, const int ID2, const int ID3, const int ID4, void *ContextPtr ) +{ + this->ID1 = ID1; + this->ID2 = ID2; + this->ID3 = ID3; + this->ID4 = ID4; + this->ContextPtr = ContextPtr; +} + +cClockEvent::cClockEvent( const cClockEvent &other ) +{ + this->ID1 = other.ID1; + this->ID2 = other.ID2; + this->ID3 = other.ID3; + this->ID4 = other.ID4; + this->ContextPtr = other.ContextPtr; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cClockEvent::~cClockEvent() +{ +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +const int +cClockEvent::GetID1() +{ + return this->ID1; +} + +const int +cClockEvent::GetID2() +{ + return this->ID2; +} + +const int +cClockEvent::GetID3() +{ + return this->ID3; +} + +const int +cClockEvent::GetID4() +{ + return this->ID4; +} + +void * +cClockEvent::GetContextPtr() +{ + return this->ContextPtr; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cClockEvent::SetID1( int ID ) +{ + this->ID1 = ID; +} + +void +cClockEvent::SetID2( int ID ) +{ + this->ID2 = ID; +} + +void +cClockEvent::SetID3( int ID ) +{ + this->ID3 = ID; +} + +void +cClockEvent::SetID4( int ID ) +{ + this->ID4 = ID; +} + +void +cClockEvent::SetContextPtr( void *ContextPtr ) +{ + this->ContextPtr = ContextPtr; +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ + +bool +cClockEvent::operator== (const cClockEvent& other) const +{ + if + ( + ( other.ID1 == this->ID1 ) && + ( other.ID2 == this->ID2 ) && + ( other.ID3 == this->ID3 ) && + ( other.ID4 == this->ID4 ) && + ( other.ContextPtr == this->ContextPtr ) + ) + { + return true; + } + else + { + return false; + } +} + +cClockEvent& +cClockEvent::operator=( const cClockEvent& other ) +{ + this->ID1 = other.ID1; + this->ID2 = other.ID2; + this->ID3 = other.ID3; + this->ID4 = other.ID4; + this->ContextPtr = other.ContextPtr; + + // By convention, always return *this + return *this; +} diff --git a/src/Hardware/HwClock/ClockEvents/ClockEvent.h b/src/Hardware/HwClock/ClockEvents/ClockEvent.h new file mode 100644 index 0000000..d32e2b8 --- /dev/null +++ b/src/Hardware/HwClock/ClockEvents/ClockEvent.h @@ -0,0 +1,78 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_CLOCK_EVENT_H_ +#define LIBPTP_CLOCK_EVENT_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cClockEvent +{ + private: + + // Resources + int ID1; + int ID2; + int ID3; + int ID4; + void *ContextPtr; + + public: + + // Constructor + cClockEvent(); + cClockEvent( const int ID1, const int ID2, const int ID3, const int ID4, void *ContextPtr ); + cClockEvent( const cClockEvent &other ); + ~cClockEvent(); + + // Getters + const int GetID1(); + const int GetID2(); + const int GetID3(); + const int GetID4(); + void *GetContextPtr(); + + // Setters + void SetID1( const int ID ); + void SetID2( const int ID ); + void SetID3( const int ID ); + void SetID4( const int ID ); + void SetContextPtr( void *ContextPtr ); + + // Operators + bool operator== (const cClockEvent& other) const; + cClockEvent& operator=( const cClockEvent& other ); +}; + +#endif diff --git a/src/Hardware/HwClock/ClockEvents/IClockEventSink.cc b/src/Hardware/HwClock/ClockEvents/IClockEventSink.cc new file mode 100644 index 0000000..9cbf666 --- /dev/null +++ b/src/Hardware/HwClock/ClockEvents/IClockEventSink.cc @@ -0,0 +1,55 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "IClockEventSink.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +void +IClockEventSink::ClockEventCallback( cClockEvent &ClockEvent ) +{ + EnterModuleSilent(); + HandleClockEvent( ClockEvent ); + LeaveModule(); +} diff --git a/src/Hardware/HwClock/ClockEvents/IClockEventSink.h b/src/Hardware/HwClock/ClockEvents/IClockEventSink.h new file mode 100644 index 0000000..1caee12 --- /dev/null +++ b/src/Hardware/HwClock/ClockEvents/IClockEventSink.h @@ -0,0 +1,56 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_ICLOCK_EVENT_SINK_H_ +#define LIBPTP_ICLOCK_EVENT_SINK_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "ClockEvent.h" +#include "ICallableBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class IClockEventSink : public virtual ICallableBase +{ + private: + + protected: + + public: + + virtual void HandleClockEvent( cClockEvent &ClockEvent ) = 0; + + void ClockEventCallback( cClockEvent &ClockEvent ); +}; + +#endif diff --git a/src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.cc b/src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.cc new file mode 100644 index 0000000..8da410f --- /dev/null +++ b/src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.cc @@ -0,0 +1,187 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "ScheduledClockEvent.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cScheduledClockEvent::cScheduledClockEvent() + : ClockEvent() +{ + this->pSink = nullptr; + this->ScaledTime = SIMTIME_ZERO; + this->UniqueId = -1; +} + +cScheduledClockEvent::cScheduledClockEvent( IClockEventSink *pSink, simtime_t ScaledTime, cClockEvent ClockEvent, uint64_t UniqueId ) + : ClockEvent( ClockEvent ) +{ + this->pSink = pSink; + this->ScaledTime = ScaledTime; + this->UniqueId = UniqueId; +} + +cScheduledClockEvent::cScheduledClockEvent( const cScheduledClockEvent &other ) +{ + this->pSink = other.pSink; + this->ScaledTime = other.ScaledTime; + this->ClockEvent = other.ClockEvent; + this->UniqueId = other.UniqueId; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cScheduledClockEvent::~cScheduledClockEvent() +{ +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +cClockEvent +cScheduledClockEvent::GetClockEvent() const +{ + return this->ClockEvent; +} + +simtime_t +cScheduledClockEvent::GetScaledTime() const +{ + return this->ScaledTime; +} + +IClockEventSink * +cScheduledClockEvent::GetClockEventSink() const +{ + return this->pSink; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cScheduledClockEvent::SetScaledTime( simtime_t ScaledTime ) +{ + this->ScaledTime = ScaledTime; +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +bool +cScheduledClockEvent::operator<(const cScheduledClockEvent& rhs) const +{ + /// Because we want to have the element with the *smallest* + /// timestamp to be the one with the highest priority, we + /// swap > and < + return this->ScaledTime > rhs.ScaledTime; +} + +bool +cScheduledClockEvent::operator>(const cScheduledClockEvent& rhs) const +{ + /// Because we want to have the element with the *smallest* + /// timestamp to be the one with the highest priority, we + /// swap > and < + return this->ScaledTime < rhs.ScaledTime; +} + +bool +cScheduledClockEvent::operator== (const cScheduledClockEvent& other) const +{ + // Time is not compared, as it might change for an already scheduled event + // if the clock jumps + + if + ( + ( other.pSink == this->pSink ) && + ( other.ClockEvent == this->ClockEvent ) && + ( other.UniqueId == this->UniqueId ) + ) + { + return true; + } + else + { + return false; + } +} + +cScheduledClockEvent& +cScheduledClockEvent::operator=( const cScheduledClockEvent& other ) +{ + this->pSink = other.pSink; + this->ScaledTime = other.ScaledTime; + this->ClockEvent = other.ClockEvent; + this->UniqueId = other.UniqueId; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cScheduledClockEvent::Execute() +{ + pSink->ClockEventCallback( ClockEvent ); +} + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cScheduledClockEvent::Print() +{ + EV << " ScaledTime: " << ScaledTime << endl; + EV << " ID1: " << ClockEvent.GetID1() << endl; + EV << " ID2: " << ClockEvent.GetID2() << endl; + EV << " ID3: " << ClockEvent.GetID3() << endl; + EV << " ID4: " << ClockEvent.GetID4() << endl; +} diff --git a/src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.h b/src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.h new file mode 100644 index 0000000..6a61824 --- /dev/null +++ b/src/Hardware/HwClock/ClockEvents/ScheduledClockEvent.h @@ -0,0 +1,82 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_SCHEDULED_CLOCK_EVENT_H_ +#define LIBPTP_SCHEDULED_CLOCK_EVENT_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "IClockEventSink.h" +#include "ClockEvent.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cScheduledClockEvent +{ + private: + + // Resources + IClockEventSink *pSink; + simtime_t ScaledTime; + cClockEvent ClockEvent; + uint64_t UniqueId; + + public: + + // Constructor + cScheduledClockEvent(); + cScheduledClockEvent( IClockEventSink *pSink, simtime_t ScaledTime, cClockEvent ClockEvent, uint64_t UniqueId ); + cScheduledClockEvent( const cScheduledClockEvent &other ); + ~cScheduledClockEvent(); + + // Getters + cClockEvent GetClockEvent() const; + simtime_t GetScaledTime() const; + IClockEventSink *GetClockEventSink() const; + + // Setters + void SetScaledTime( simtime_t ScaledTime ); + + // Operators + bool operator<(const cScheduledClockEvent& rhs) const; + bool operator>(const cScheduledClockEvent& rhs) const; + bool operator==(const cScheduledClockEvent& other) const; + cScheduledClockEvent& operator=( const cScheduledClockEvent& other ); + + // API functions + void Execute(); + + // Debug functions + void Print(); +}; + +#endif diff --git a/src/Hardware/HwClock/HwClock/HwClock.cc b/src/Hardware/HwClock/HwClock/HwClock.cc new file mode 100644 index 0000000..20249a6 --- /dev/null +++ b/src/Hardware/HwClock/HwClock/HwClock.cc @@ -0,0 +1,303 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "HwClock.h" + +#include "PerfectTdGen.h" +#include "ConstDriftTdGen.h" +#include "SineTdGen.h" +#include "libPLN_TdGen.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(cHwClock); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// OMNeT API +// ------------------------------------------------------ +void +cHwClock::handleMessage(cMessage *pMsg) +{ + // Handle self-message (can only be schedule App msgs) + if( pMsg->isSelfMessage() ) + { + if( pTimeTraceMsg == pMsg ) + { + emit( TimeDeviation_SigId, pTdGen->GetTD( simTime() ) ); + emit( HwTime_SigId, GetHwTime() ); + + scheduleAt( simTime() + TimeTraceInterval, pTimeTraceMsg ); + } + else + { + throw cRuntimeError( "HwClock: Received unexpected message" ); + delete pMsg; + } + } + else + { + throw cRuntimeError( "HwClock: Received unexpected message" ); + delete pMsg; + } +} + +void +cHwClock::finish() +{ +} + +// ------------------------------------------------------ +// Time conversion +// ------------------------------------------------------ +simtime_t +cHwClock::GetRealTimeBeforeHwTime( const simtime_t HwTimeRequ ) +{ + simtime_t NowHwTime = GetHwTime(); + simtime_t HwDiff = HwTimeRequ - NowHwTime; + + assert( HwDiff >= SIMTIME_ZERO ); + + simtime_t RealDiff = HwDiff / 2; + simtime_t RealTime = simTime() + RealDiff; + simtime_t HwTime = GetHwTimeAtRealTime( RealTime ); + + while( HwTime > HwTimeRequ ) + { + RealDiff /= 2; + RealTime = simTime() + RealDiff; + HwTime = GetHwTimeAtRealTime( simTime() + RealDiff ); + } + + return RealTime; +} + +simtime_t +cHwClock::GetHwTimeAtRealTime( const simtime_t RealTime ) +{ + uint64_t Cnt = (RealTime + T0 + pTdGen->GetTD( RealTime )) / TickLenNom; + + return Cnt * TickLenNom; +} + +simtime_t +cHwClock::RoundDownToTickLen( const simtime_t HwTime ) const +{ + // TODO: Use standard function 'remainder(a,b)' instead + + // Round to multiple of nominal TickLen + int64_t HwTime_ps = HwTime.inUnit( SIMTIME_PS ); + int64_t Fraction_ps = HwTime_ps % TickLenNom_ps; + + simtime_t HwTimeRounded = simtime_t( (HwTime_ps - Fraction_ps), SIMTIME_PS ); + + assert( HwTimeRounded <= HwTime ); + assert( HwTimeRounded.inUnit(SIMTIME_PS) % TickLenNom_ps == 0 ); + + return HwTimeRounded; +} + +simtime_t +cHwClock::RoundUpToTickLen( const simtime_t HwTime ) const +{ + simtime_t HwTimeRounded = RoundDownToTickLen( HwTime ); + + if( HwTime != HwTimeRounded ) + { + HwTimeRounded += TickLenNom; + } + + assert( HwTimeRounded >= HwTime ); + assert( HwTimeRounded.inUnit(SIMTIME_PS) % TickLenNom_ps == 0 ); + + return HwTimeRounded; +} + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ +void +cHwClock::ParseResourceParameters() +{ + TdGenType = cHwClock_ParameterParser::ParseTdGenType( par("TdGenType").stringValue() ); + TimeTraceInterval = simtime_t( par("TimeTraceInterval").doubleValue() ); + + if( TimeTraceInterval != SIMTIME_ZERO ) + { + EnableTimeTracing = true; + } +} + +void +cHwClock::AllocateResources() +{ + switch( TdGenType ) + { + case TDGEN_TYPE_PERFECT: pTdGen = new cPerfectTdGen(); + break; + + case TDGEN_TYPE_CONST_DRIFT: pTdGen = new cConstDriftTdGen(); + break; + + case TDGEN_TYPE_SINE: pTdGen = new cSineTdGen(); + break; + + case TDGEN_TYPE_LIBPLN: // Check if we were configured to use libPLN + #ifdef HAS_LIBPLN + pTdGen = new cLibPLN_TdGen(); + #else + EV << "===================================================================================================" << endl; + EV << "Warning: TdGen was configured to use libPLN, but it is not available. Using PerfectTdGen instead." << endl; + EV << "===================================================================================================" << endl; + pTdGen = new cPerfectTdGen(); + #endif + + break; + + default: throw cRuntimeError( "Unsupported TdGenType" ); + break; + } + + if( EnableTimeTracing ) + { + pTimeTraceMsg = new cMessage("HwClock: Trace time values" ); + } +} + +void +cHwClock::InitHierarchy() +{ + pTdGen->SetParentModule( this ); +} + +void +cHwClock::ParseParameters() +{ + TickLenNom = simtime_t( par("TickLenNom").doubleValue() ); + TickLenNom_ps = TickLenNom.inUnit( SIMTIME_PS ); + + if( TickLenNom_ps == 0 ) + { + throw cRuntimeError( "Invalid value for parameter TickLenNom" ); + } + + T0 = TickLenNom * par("InitialPhaseNom").doubleValue(); +} + +void +cHwClock::RegisterSignals() +{ + TimeDeviation_SigId = registerSignal( "TimeDeviation" ); + HwTime_SigId = registerSignal( "HwTime" ); + RequHwTime_SigId = registerSignal( "RequestedHwTime" ); +} + +void +cHwClock::InitInternalState() +{ + if( EnableTimeTracing ) + { + scheduleAt( simulation.getWarmupPeriod() + TimeTraceInterval, pTimeTraceMsg ); + } +} + +void +cHwClock::InitSignals() +{ + emit( HwTime_SigId, GetHwTime() ); + emit( TimeDeviation_SigId, pTdGen->GetTD( simTime() ) ); +} + +void +cHwClock::FinishInit() +{ +} + +void +cHwClock::ForwardInit( int stage ) +{ + pTdGen->initialize( stage ); +} + +// ------------------------------------------------------ +// Debug API +// ------------------------------------------------------ +simtime_t +cHwClock::GetTD( simtime_t RealTime ) +{ + return pTdGen->GetTD( RealTime ); +} + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +cHwClock::cHwClock() +{ + pTdGen = nullptr; + pTimeTraceMsg = nullptr; + EnableTimeTracing = false; +} + +cHwClock::~cHwClock() +{ + delete pTdGen; + + if( EnableTimeTracing ) + { + cancelAndDelete( pTimeTraceMsg ); + } +} + +// ------------------------------------------------------ +// HwClock API +// ------------------------------------------------------ +simtime_t +cHwClock::GetHwTime() +{ + return GetHwTimeAtRealTime( simTime() ); +} diff --git a/src/Hardware/HwClock/HwClock/HwClock.h b/src/Hardware/HwClock/HwClock/HwClock.h new file mode 100644 index 0000000..da2d059 --- /dev/null +++ b/src/Hardware/HwClock/HwClock/HwClock.h @@ -0,0 +1,110 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_HW_CLOCK_H_ +#define LIBPTP_HW_CLOCK_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "ModuleInitBase.h" +#include "ITdGen.h" +#include "HwClock_ParameterParser.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cHwClock: public cModuleInitBase +{ + private: + + // Resources + ITdGen *pTdGen; + + // Configuration + TdGenType_t TdGenType; + simtime_t TimeTraceInterval; + bool EnableTimeTracing; + + // Internal housekeeping + + // Signal handling + simsignal_t TimeDeviation_SigId; + simsignal_t HwTime_SigId; + simsignal_t RequHwTime_SigId; + + // Debug functions + + protected: + + // Resources + cMessage *pTimeTraceMsg; + + // Configuration + uint64_t TickLenNom_ps; + simtime_t TickLenNom; + simtime_t T0; // Initial phase offset + + // OMNeT API + virtual void handleMessage(cMessage *pMsg); + virtual void finish(); + + // API for sub-classes + simtime_t GetRealTimeBeforeHwTime( const simtime_t HwTime ); + simtime_t GetHwTimeAtRealTime( const simtime_t RealTime ); + simtime_t RoundDownToTickLen( const simtime_t HwTime ) const; + simtime_t RoundUpToTickLen( const simtime_t HwTime ) const; + + // Init API + void ParseResourceParameters(); + void AllocateResources(); + void InitHierarchy(); + void ParseParameters(); + void RegisterSignals(); + void InitInternalState(); + void InitSignals(); + void FinishInit(); + void ForwardInit( int stage ); + + // Debug API + simtime_t GetTD( simtime_t RealTime ); + + // Time readout + simtime_t GetHwTime(); + + public: + + cHwClock(); + ~cHwClock(); + +}; + +#endif + diff --git a/src/Hardware/HwClock/HwClock/HwClockTypes.ned b/src/Hardware/HwClock/HwClock/HwClockTypes.ned new file mode 100644 index 0000000..bac03a6 --- /dev/null +++ b/src/Hardware/HwClock/HwClock/HwClockTypes.ned @@ -0,0 +1,59 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.HwClock; + +moduleinterface IPerfectHwClock extends IHwClock +{ + parameters: + @display("i=PTP/Components/Clock/PerfectClock"); +} + +moduleinterface IConstantDriftHwClock extends IHwClock +{ + parameters: + + @display("i=PTP/Components/Clock/ConstantDriftClock"); + + double ConstDriftTdGen_k; +} + +moduleinterface ISineHwClock extends IHwClock +{ + parameters: + + @display("i=PTP/Components/Clock/SineClock"); + + double SineTdGen_f; + double SineTdGen_A; + double SineTdGen_phi; +} + +moduleinterface ILibPLN_HwClock extends IHwClock +{ + parameters: + + @display("i=PTP/Components/Clock/RealClock"); + + int libPLN_TdGen_Seed; + bool libPLN_TdGen_AllowSkipping; +} diff --git a/src/Hardware/HwClock/HwClock/HwClock_ParameterParser.cc b/src/Hardware/HwClock/HwClock/HwClock_ParameterParser.cc new file mode 100644 index 0000000..4ee01d7 --- /dev/null +++ b/src/Hardware/HwClock/HwClock/HwClock_ParameterParser.cc @@ -0,0 +1,61 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "HwClock_ParameterParser.h" +#include "ParameterParser.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +ParseType TdGenTypeParse[] = +{ + { TDGEN_TYPE_PERFECT, "TDGEN_TYPE_PERFECT" }, + { TDGEN_TYPE_CONST_DRIFT, "TDGEN_TYPE_CONST_DRIFT" }, + { TDGEN_TYPE_SINE, "TDGEN_TYPE_SINE" }, + { TDGEN_TYPE_LIBPLN, "TDGEN_TYPE_LIBPLN" }, +}; + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// TdGen type +// ------------------------------------------------------ +TdGenType_t +cHwClock_ParameterParser::ParseTdGenType(const char *Str) +{ + return Parse( TdGenTypeParse, ArrayLen(TdGenTypeParse), Str ); +} diff --git a/src/Hardware/HwClock/HwClock/HwClock_ParameterParser.h b/src/Hardware/HwClock/HwClock/HwClock_ParameterParser.h new file mode 100644 index 0000000..8d0b9bc --- /dev/null +++ b/src/Hardware/HwClock/HwClock/HwClock_ParameterParser.h @@ -0,0 +1,55 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_CLOCK_PARAMETER_PARSER_H_ +#define LIBPTP_CLOCK_PARAMETER_PARSER_H_ + +// ====================================================== +// Includes +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +typedef enum +{ + TDGEN_TYPE_PERFECT, + TDGEN_TYPE_CONST_DRIFT, + TDGEN_TYPE_SINE, + TDGEN_TYPE_LIBPLN, +} +TdGenType_t; + +class cHwClock_ParameterParser +{ + public: + + static TdGenType_t ParseTdGenType(const char *Str); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif + diff --git a/src/Hardware/HwClock/HwClock/IHwClock.ned b/src/Hardware/HwClock/HwClock/IHwClock.ned new file mode 100644 index 0000000..10cafef --- /dev/null +++ b/src/Hardware/HwClock/HwClock/IHwClock.ned @@ -0,0 +1,41 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.HwClock; + +moduleinterface IHwClock +{ + parameters: + @display("i=PTP/Components/Clock/DigitalClock"); + + // Clock configuration + double TickLenNom @unit(s); + + double InitialPhaseNom; + + // Time Deviation configuration + string TdGenType; + + // Trace configuration + double TimeTraceInterval @unit(s); // 0 -> tracing disabled + // 1 -> tracing enabled, this will result in an infinite simulation run if not stopped manually or via sim-time-limit +} diff --git a/src/Hardware/HwClock/HwClock/Internal_HwClock.ned b/src/Hardware/HwClock/HwClock/Internal_HwClock.ned new file mode 100644 index 0000000..2b70508 --- /dev/null +++ b/src/Hardware/HwClock/HwClock/Internal_HwClock.ned @@ -0,0 +1,55 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.HwClock; + +simple Internal_HwClock like IHwClock +{ + parameters: + + @display("i=PTP/Components/InternalModule/InternalModule"); + @class("cHwClock"); + + // Clock configuration + double TickLenNom @unit(s) = default(50ns); + double InitialPhaseNom = default(uniform(0.0,1.0)); // Initial phase offset, 0.0-1.0 + + // Time Deviation configuration + string TdGenType = default("TDGEN_TYPE_PERFECT"); + + // Trace configuration + double TimeTraceInterval @unit(s) = default(0); + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + @signal[TimeDeviation](type=simtime_t); + @signal[HwTime](type=simtime_t); + @signal[RequestedHwTime](type=simtime_t); + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- + @statistic[TimeDeviation](record=stats,vector?); + @statistic[HwTime](record=vector?); + @statistic[RequestedHwTime](record=vector?); +} diff --git a/src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.cc b/src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.cc new file mode 100644 index 0000000..3c3e728 --- /dev/null +++ b/src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.cc @@ -0,0 +1,145 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "LocalTimeStamp.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cLocalTimeStamp::cLocalTimeStamp() +{ + this->EpochID = 0; + this->Time = SIMTIME_ZERO; +} + +cLocalTimeStamp::cLocalTimeStamp(uint64_t EpochID, simtime_t Time) +{ + this->EpochID = EpochID; + this->Time = Time; +} + +cLocalTimeStamp::cLocalTimeStamp( const cLocalTimeStamp &other ) +{ + this->EpochID = other.EpochID; + this->Time = other.Time; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cLocalTimeStamp::~cLocalTimeStamp() +{ +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +uint64_t +cLocalTimeStamp::GetEpochID() const +{ + return this->EpochID; +} + +simtime_t +cLocalTimeStamp::GetTime() const +{ + return this->Time; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +bool +cLocalTimeStamp::operator== (const cLocalTimeStamp& other) const +{ + if + ( + ( this->EpochID == other.EpochID ) && + ( this->Time == other.Time ) + ) + { + return true; + } + else + { + return false; + } +} + +cLocalTimeStamp& +cLocalTimeStamp::operator=( const cLocalTimeStamp& other ) +{ + this->EpochID = other.EpochID; + this->Time = other.Time; + + // By convention, always return *this + return *this; +} + +simtime_t +cLocalTimeStamp::operator-( const cLocalTimeStamp& other ) +{ + if( this->EpochID == other.EpochID ) + { + return this->Time - other.Time; + } + else + { + return SIMTIME_ZERO; + } +} + +std::ostream& +operator<<(std::ostream& os, const cLocalTimeStamp& o ) +{ + os << "Epoch: " << o.GetEpochID() << ", Time: " << o.GetTime(); + + return os; +} diff --git a/src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.h b/src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.h new file mode 100644 index 0000000..7d03669 --- /dev/null +++ b/src/Hardware/HwClock/LocalTimeStamp/LocalTimeStamp.h @@ -0,0 +1,73 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_LOCAL_TIMESTAMP_H_ +#define LIBPTP_LOCAL_TIMESTAMP_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cLocalTimeStamp +{ + private: + + // Resources + uint64_t EpochID; + simtime_t Time; + + public: + + // Constructor + cLocalTimeStamp(); + cLocalTimeStamp(uint64_t EpochID, simtime_t Time); + cLocalTimeStamp( const cLocalTimeStamp &other ); + ~cLocalTimeStamp(); + + // Getters + uint64_t GetEpochID() const; + simtime_t GetTime() const; + + // Setters + + // Operators + bool operator== (const cLocalTimeStamp& other) const; + cLocalTimeStamp& operator=( const cLocalTimeStamp& other ); + simtime_t operator-( const cLocalTimeStamp& other ); +}; + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& operator<<(std::ostream& os, const cLocalTimeStamp& o ); + +#endif diff --git a/src/Hardware/HwClock/ScheduleClock/IScheduleClock.ned b/src/Hardware/HwClock/ScheduleClock/IScheduleClock.ned new file mode 100644 index 0000000..dc0fb9b --- /dev/null +++ b/src/Hardware/HwClock/ScheduleClock/IScheduleClock.ned @@ -0,0 +1,37 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.ScheduleClock; + +import libptp.Hardware.HwClock.AdjustableClock.IAdjustableClock; + +moduleinterface IScheduleClock extends IAdjustableClock +{ + parameters: + @display("i=PTP/Components/Clock/DigitalClock"); + + // Clock configuration + double ScheduleEdgePrecision @unit(s); + + // Debug parameters + bool EnableScheduleDebugOutput; +} diff --git a/src/Hardware/HwClock/ScheduleClock/Internal_ScheduleClock.ned b/src/Hardware/HwClock/ScheduleClock/Internal_ScheduleClock.ned new file mode 100644 index 0000000..5b43b2c --- /dev/null +++ b/src/Hardware/HwClock/ScheduleClock/Internal_ScheduleClock.ned @@ -0,0 +1,47 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.ScheduleClock; + +import libptp.Hardware.HwClock.AdjustableClock.Internal_AdjustableClock; + +simple Internal_ScheduleClock extends Internal_AdjustableClock like IScheduleClock +{ + parameters: + + @display("i=PTP/Components/InternalModule/InternalModule"); + @class("cScheduleClock"); + + // Clock configuration + double ScheduleEdgePrecision @unit(s) = default(1ns); + + // Debug parameters + bool EnableScheduleDebugOutput = default(false); + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- +} diff --git a/src/Hardware/HwClock/ScheduleClock/ScheduleClock.cc b/src/Hardware/HwClock/ScheduleClock/ScheduleClock.cc new file mode 100644 index 0000000..c8d97ba --- /dev/null +++ b/src/Hardware/HwClock/ScheduleClock/ScheduleClock.cc @@ -0,0 +1,405 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "ScheduleClock.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(cScheduleClock); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cScheduleClock::PrintEventQueue() +{ + ClockEventQueue_t Queue = EventQueue; + size_t cnt = 1; + + EV << endl; + EV << "Iterating over EventQueue, size = " << Queue.size() << endl; + while( !Queue.empty() ) + { + cScheduledClockEvent Event = Queue.top(); + Queue.pop(); + + EV << cnt << ". element: " << endl; + Event.Print(); + + cnt ++; + } + EV << endl; +} + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ + +void +cScheduleClock::ShiftQueuedEvents( simtime_t Delta ) +{ + ClockEventQueue_t NewQueue; + + while( !EventQueue.empty() ) + { + cScheduledClockEvent ClockEvent( EventQueue.top() ); + + ClockEvent.SetScaledTime( ClockEvent.GetScaledTime() + Delta ); + + NewQueue.push( ClockEvent ); + + EventQueue.pop(); + } + + EventQueue = NewQueue; + + UpdateSchedule(); +} + +void +cScheduleClock::RemoveQueuedEvent( const cScheduledClockEvent &ScheduledEvent ) +{ + // If there are no messages, there is nothing to cancel + if( EventQueue.empty() ) + { + return; + } + + // Get first message + cScheduledClockEvent TopEvent = EventQueue.top(); + EventQueue.pop(); + + // If it is the event we were looking for, we are done + if( TopEvent == ScheduledEvent ) + { + return; + } + // Otherwise, we continue to search recursively + else + { + RemoveQueuedEvent( ScheduledEvent ); + + EventQueue.push( TopEvent ); + } +} + +void +cScheduleClock::UpdateSchedule() +{ + if( EnableScheduleDebugOutput ) + { + EV << "Updating event schedule" << endl; + } + + cancelEvent(pNextScheduledEvent); + + if( !EventQueue.empty() ) + { + simtime_t NowScaledTime = cAdjustableClock::GetScaledTime(); + simtime_t NowHwTime = cAdjustableClock::GetHwTime(); + simtime_t NowRealTime = simTime(); + + simtime_t RequScaledTime = EventQueue.top().GetScaledTime(); + simtime_t RequHwTime = GetHwTimeAtScaledTime( RequScaledTime ); + simtime_t RequRealTime = GetRealTimeBeforeHwTime( RequHwTime ); + + if( EnableScheduleDebugOutput ) + { + EV << "Scheduling next event:" << endl; + EV << " Current time: " << endl; + EV << " ScaledTime: " << NowScaledTime << endl; + EV << " HwTime: " << NowHwTime << endl; + EV << " RealTime: " << NowRealTime << endl; + EV << " Next event: " << endl; + EV << " ScaledTime: " << RequScaledTime << endl; + EV << " HwTime: " << RequHwTime << endl; + EV << " Next RealTime: " << RequRealTime << endl; + } + + if + ( + ( NowRealTime + EdgePrecision >= RequRealTime ) && + ( NowScaledTime < RequScaledTime ) + ) + { + RequRealTime = NowRealTime + EdgePrecision; + } + + if( EnableScheduleDebugOutput ) + { + EV << "Scheduling at " << RequRealTime << endl; + } + + scheduleAt( RequRealTime, pNextScheduledEvent ); + } + + NumScheduledEvents = EventQueue.size(); +} + +// ------------------------------------------------------ +// OMNeT API +// ------------------------------------------------------ + +void +cScheduleClock::handleMessage(cMessage *pMsg) +{ + // Handle self-message (can only be schedule App msgs) + if( pMsg->isSelfMessage() ) + { + if( pMsg == pNextScheduledEvent ) + { + if( EventQueue.empty() ) + { + throw cRuntimeError( "Clock: Found an empty EventQueue" ); + } + + // Remove current top entry from queue + cScheduledClockEvent Event = EventQueue.top(); + + simtime_t Now = cAdjustableClock::GetScaledTime(); + + if( Now > Event.GetScaledTime() + 2*TickLenNom ) + { + std::stringstream ss; + + ss << "Oh noes!!11 We missed a tick! This should not happen." << endl; + ss << "Now: " << Now << endl; + ss << "Requested: " << Event.GetScaledTime() << endl; + ss << "TickLen: " << TickLenNom << endl; + + throw cRuntimeError( ss.str().c_str() ); + } + else if( Now >= Event.GetScaledTime() ) + { + if( EnableScheduleDebugOutput ) + { + EV << "Executing event!" << endl; + EV << " Scheduled ScaledTime: " << Event.GetScaledTime() << endl; + EV << " Actual ScaledTime: " << Now << endl; + } + + EventQueue.pop(); + Event.Execute(); + } + + UpdateSchedule(); + } + else + { + cAdjustableClock::handleMessage( pMsg ); + } + } + else + { + throw cRuntimeError( "HwClock: Received unexpected message" ); + delete pMsg; + } +} + +void +cScheduleClock::finish() +{ +} + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ +void +cScheduleClock::AllocateResources() +{ + cAdjustableClock::AllocateResources(); + + pNextScheduledEvent = new cMessage( "Scheduled Clock Event" ); +} + +void +cScheduleClock::ParseParameters() +{ + cAdjustableClock::ParseParameters(); + + EdgePrecision = simtime_t( par("ScheduleEdgePrecision").doubleValue() ); + + if( EdgePrecision > TickLenNom ) + { + throw cRuntimeError( "Configured edge precision for scheduling is larger than the tick length, that does not make sense." ); + } + + EnableScheduleDebugOutput = par( "EnableScheduleDebugOutput" ).boolValue(); +} + +void +cScheduleClock::RegisterSignals() +{ + cAdjustableClock::RegisterSignals(); + + WATCH(NumScheduledEvents); +} + +void +cScheduleClock::InitInternalState() +{ + cAdjustableClock::InitInternalState(); + + UniqueIdCnt = 0; +} + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +cScheduleClock::cScheduleClock() + : cAdjustableClock() +{ + pNextScheduledEvent = nullptr; + NumScheduledEvents = 0; +} + +cScheduleClock::~cScheduleClock() +{ + while( !EventQueue.empty() ) + { + EventQueue.pop(); + } + + cancelAndDelete( pNextScheduledEvent ); +} + +// ------------------------------------------------------ +// Event scheduling API +// ------------------------------------------------------ + +cScheduledClockEvent +cScheduleClock::ScheduleAbsoluteEvent( simtime_t ScaledTime, IClockEventSink *pSink, cClockEvent ClockEvent ) +{ + EnterModuleSilent(); + + cScheduledClockEvent NewEvent = cScheduledClockEvent( pSink, ScaledTime, ClockEvent, UniqueIdCnt++ ); + + EventQueue.push( NewEvent ); + + UpdateSchedule(); + + LeaveModule(); + + return NewEvent; +} + +cScheduledClockEvent +cScheduleClock::ScheduleRelativeEvent( simtime_t Delta, IClockEventSink *pSink, cClockEvent ClockEvent ) +{ + EnterModuleSilent(); + + cScheduledClockEvent ScheduledEvent = ScheduleAbsoluteEvent( cAdjustableClock::GetScaledTime() + Delta, pSink, ClockEvent ); + + LeaveModule(); + + return ScheduledEvent; +} + +void +cScheduleClock::CancelEvent( const cScheduledClockEvent &ScheduledEvent ) +{ + EnterModuleSilent(); + + RemoveQueuedEvent( ScheduledEvent ); + UpdateSchedule(); + + LeaveModule(); +} + +// ------------------------------------------------------ +// Time adjustment API +// ------------------------------------------------------ +void +cScheduleClock::HandleTimeJump( simtime_t Delta ) +{ + cAdjustableClock::HandleTimeJump( Delta ); + + ShiftQueuedEvents( Delta ); +} + +void +cScheduleClock::SetScaleFactor_ppb( const int64_t ScaleFactor_ppb ) +{ + EnterModuleSilent(); + + if( EnableAdjustments ) + { + cAdjustableClock::SetScaleFactor_ppb( ScaleFactor_ppb ); + UpdateSchedule(); + } + + LeaveModule(); +} + +// ------------------------------------------------------ +// Clock API +// ------------------------------------------------------ +simtime_t +cScheduleClock::GetScaledTime() +{ + EnterModuleSilent(); + + simtime_t ScaledTime = cAdjustableClock::GetScaledTime(); + UpdateSchedule(); + + LeaveModule(); + + return ScaledTime; +} + +cLocalTimeStamp +cScheduleClock::GetTimeStamp() +{ + EnterModuleSilent(); + + cLocalTimeStamp TimeStamp = cAdjustableClock::GetTimeStamp(); + UpdateSchedule(); + + LeaveModule(); + + return TimeStamp; +} diff --git a/src/Hardware/HwClock/ScheduleClock/ScheduleClock.h b/src/Hardware/HwClock/ScheduleClock/ScheduleClock.h new file mode 100644 index 0000000..8f6a439 --- /dev/null +++ b/src/Hardware/HwClock/ScheduleClock/ScheduleClock.h @@ -0,0 +1,114 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_SCHEDULE_CLOCK_H_ +#define LIBPTP_SCHEDULE_CLOCK_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "AdjustableClock.h" +#include "IClockEventSink.h" +#include "ScheduledClockEvent.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cScheduleClock: public cAdjustableClock +{ + private: + + // Types + typedef std::priority_queue< cScheduledClockEvent, std::vector > ClockEventQueue_t; + + // Resources + ClockEventQueue_t EventQueue; + cMessage *pNextScheduledEvent; + + // Configuration + simtime_t EdgePrecision; + + // Internal housekeeping + uint64_t UniqueIdCnt; + + // Debug configuration + bool EnableScheduleDebugOutput; + + // Signal handling + + // Watch variables + size_t NumScheduledEvents; + + // Debug functions + void PrintEventQueue(); + + // Event Scheduling + void ShiftQueuedEvents( simtime_t Delta ); + void RemoveQueuedEvent( const cScheduledClockEvent &ScheduledEvent ); + + void UpdateSchedule(); + + protected: + + // Configuration + + // OMNeT API + virtual void handleMessage(cMessage *pMsg); + virtual void finish(); + + // Init API + void AllocateResources(); + void ParseParameters(); + void RegisterSignals(); + void InitInternalState(); + + // Time adjustment API + virtual void HandleTimeJump( simtime_t Delta ); + + public: + + // Constructors/Destructor + cScheduleClock(); + ~cScheduleClock(); + + // Event scheduling API + cScheduledClockEvent ScheduleAbsoluteEvent( simtime_t ScaledTime, IClockEventSink *pSink, cClockEvent ClockEvent ); + cScheduledClockEvent ScheduleRelativeEvent( simtime_t Delta, IClockEventSink *pSink, cClockEvent ClockEvent ); + void CancelEvent(const cScheduledClockEvent &ScheduledEvent); + + // Time adjustment API + void SetScaleFactor_ppb( const int64_t ScaleFactor_ppb ); + + // Clock API + simtime_t GetScaledTime(); + cLocalTimeStamp GetTimeStamp(); +}; + +#endif diff --git a/src/Hardware/HwClock/ScheduleClock/ScheduleClockTypes.ned b/src/Hardware/HwClock/ScheduleClock/ScheduleClockTypes.ned new file mode 100644 index 0000000..6f3c197 --- /dev/null +++ b/src/Hardware/HwClock/ScheduleClock/ScheduleClockTypes.ned @@ -0,0 +1,81 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.HwClock.ScheduleClock; + +import libptp.Hardware.HwClock.AdjustableClock.IPerfectAdjustableClock; +import libptp.Hardware.HwClock.AdjustableClock.IConstantDriftAdjustableClock; +import libptp.Hardware.HwClock.AdjustableClock.ISineAdjustableClock; +import libptp.Hardware.HwClock.AdjustableClock.ILibPLN_AdjustableClock; + +simple PerfectScheduleClock extends Internal_ScheduleClock like IScheduleClock, IPerfectAdjustableClock +{ + parameters: + + @display("i=PTP/Components/Clock/PerfectClock"); + + // Time Deviation configuration + TdGenType = "TDGEN_TYPE_PERFECT"; + + // HwClockConfig + InitialPhaseNom = default(0.0); +} + +simple ConstantDriftScheduleClock extends Internal_ScheduleClock like IScheduleClock, IConstantDriftAdjustableClock +{ + parameters: + + @display("i=PTP/Components/Clock/ConstantDriftClock"); + + // Time Deviation configuration + TdGenType = "TDGEN_TYPE_CONST_DRIFT"; + + double ConstDriftTdGen_k = default(0.5); +} + +simple SineScheduleClock extends Internal_ScheduleClock like IScheduleClock, ISineAdjustableClock +{ + parameters: + + @display("i=PTP/Components/Clock/SineClock"); + + // Time Deviation configuration + TdGenType = "TDGEN_TYPE_SINE"; + + double SineTdGen_f = default(0.01); + double SineTdGen_A = default(-1.0); + double SineTdGen_phi = default(0.0); +} + +simple LibPLN_ScheduleClock extends Internal_ScheduleClock like IScheduleClock, ILibPLN_AdjustableClock +{ + parameters: + + @display("i=PTP/Components/Clock/RealClock"); + + // Time Deviation configuration + TdGenType = "TDGEN_TYPE_LIBPLN"; + + int libPLN_TdGen_Seed = default(0); + bool libPLN_TdGen_AllowSkipping = default(true); + string libPLN_TdGen_Example = default("AVG_OSC"); +} diff --git a/src/Hardware/HwClock/SeedProvider/SeedProvider.cc b/src/Hardware/HwClock/SeedProvider/SeedProvider.cc new file mode 100644 index 0000000..c290991 --- /dev/null +++ b/src/Hardware/HwClock/SeedProvider/SeedProvider.cc @@ -0,0 +1,61 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +static cNEDValue SeedProvider(cComponent *context, cNEDValue argv[], int argc) +{ + static long Seed = 0; + + if( Seed == 0 ) + { + Seed = argv[0].longValue(); + } + + return Seed++; +} + +// ====================================================== +// Declarations +// ====================================================== + +Define_NED_Function(SeedProvider, "int SeedProvider(int Offset)"); diff --git a/src/Hardware/HwClock/TdGen/ConstDriftTdGen.cc b/src/Hardware/HwClock/TdGen/ConstDriftTdGen.cc new file mode 100644 index 0000000..98ebd32 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/ConstDriftTdGen.cc @@ -0,0 +1,90 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "ConstDriftTdGen.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ + +void +cConstDriftTdGen::ParseParameters() +{ + k = pParentModule->par( "ConstDriftTdGen_k" ).doubleValue(); + + if( k <= -1.0 ) + { + throw cRuntimeError( "Invalid parameters" ); + } +} + +void +cConstDriftTdGen::RegisterSignals() +{ +} + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +cConstDriftTdGen::cConstDriftTdGen() + : ITdGen( "ConstDriftTdGen" ) +{ +} + +cConstDriftTdGen::~cConstDriftTdGen() +{ +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +simtime_t +cConstDriftTdGen::GetTD( simtime_t RealTime ) +{ + return k * RealTime; +} diff --git a/src/Hardware/HwClock/TdGen/ConstDriftTdGen.h b/src/Hardware/HwClock/TdGen/ConstDriftTdGen.h new file mode 100644 index 0000000..0bf723c --- /dev/null +++ b/src/Hardware/HwClock/TdGen/ConstDriftTdGen.h @@ -0,0 +1,73 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_CONST_DRIFT_TD_GEN_H_ +#define LIBPTP_CONST_DRIFT_TD_GEN_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "ITdGen.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cConstDriftTdGen: public ITdGen +{ + private: + + // Resources + + // Configuration + double k; + + // Internal housekeeping + + // Debug functions + + protected: + + // Debugging + + // Configuration + + // Init API + void ParseParameters(); + void RegisterSignals(); + + public: + + cConstDriftTdGen(); + ~cConstDriftTdGen(); + + // Time readout + simtime_t GetTD( simtime_t RealTime ); +}; + +#endif + diff --git a/src/Hardware/HwClock/TdGen/ITdGen.cc b/src/Hardware/HwClock/TdGen/ITdGen.cc new file mode 100644 index 0000000..952fd56 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/ITdGen.cc @@ -0,0 +1,70 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "ITdGen.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +ITdGen::ITdGen( std::string TdGenType ) + : cSubmoduleInitBase(), TdGenType( TdGenType ) +{ +} + +ITdGen::~ITdGen() +{ +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +std::string +ITdGen::GetTdGenType() +{ + return TdGenType; +} diff --git a/src/Hardware/HwClock/TdGen/ITdGen.h b/src/Hardware/HwClock/TdGen/ITdGen.h new file mode 100644 index 0000000..5fcb270 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/ITdGen.h @@ -0,0 +1,68 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_ITD_GEN_H_ +#define LIBPTP_ITD_GEN_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class ITdGen: public cSubmoduleInitBase +{ + private: + + // Resources + const std::string TdGenType; + + // Configuration + + // Internal housekeeping + + // Debug functions + + protected: + + public: + + ITdGen( std::string TdGenType ); + virtual ~ITdGen(); + + // Time readout + virtual simtime_t GetTD( simtime_t RealTime ) = 0; + + std::string GetTdGenType(); + +}; + +#endif + diff --git a/src/Hardware/HwClock/TdGen/PerfectTdGen.cc b/src/Hardware/HwClock/TdGen/PerfectTdGen.cc new file mode 100644 index 0000000..362fbad --- /dev/null +++ b/src/Hardware/HwClock/TdGen/PerfectTdGen.cc @@ -0,0 +1,70 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PerfectTdGen.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +cPerfectTdGen::cPerfectTdGen() + : ITdGen( "PerfectTdGen" ) +{ +} + +cPerfectTdGen::~cPerfectTdGen() +{ +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +simtime_t +cPerfectTdGen::GetTD( simtime_t RealTime ) +{ + return SIMTIME_ZERO; +} diff --git a/src/Hardware/HwClock/TdGen/PerfectTdGen.h b/src/Hardware/HwClock/TdGen/PerfectTdGen.h new file mode 100644 index 0000000..f514fea --- /dev/null +++ b/src/Hardware/HwClock/TdGen/PerfectTdGen.h @@ -0,0 +1,57 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PERFECT_TD_GEN_H_ +#define LIBPTP_PERFECT_TD_GEN_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "ITdGen.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cPerfectTdGen: public ITdGen +{ + private: + + protected: + + + public: + + cPerfectTdGen(); + ~cPerfectTdGen(); + + // Time readout + simtime_t GetTD( simtime_t RealTime ); +}; + +#endif + diff --git a/src/Hardware/HwClock/TdGen/SineTdGen.cc b/src/Hardware/HwClock/TdGen/SineTdGen.cc new file mode 100644 index 0000000..e2e2b41 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/SineTdGen.cc @@ -0,0 +1,92 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "SineTdGen.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ + +void +cSineTdGen::ParseParameters() +{ + f = pParentModule->par( "SineTdGen_f" ).doubleValue(); + A = pParentModule->par( "SineTdGen_A" ).doubleValue(); + phi = pParentModule->par( "SineTdGen_phi" ).doubleValue(); + + if( A * f > 1.0 ) + { + throw cRuntimeError( "Invalid parameters" ); + } +} + +void +cSineTdGen::RegisterSignals() +{ +} + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +cSineTdGen::cSineTdGen() + : ITdGen( "SineTdGen" ) +{ +} + +cSineTdGen::~cSineTdGen() +{ +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +simtime_t +cSineTdGen::GetTD( simtime_t RealTime ) +{ + return simtime_t( A * sin(RealTime.dbl() * f + phi) ); +} diff --git a/src/Hardware/HwClock/TdGen/SineTdGen.h b/src/Hardware/HwClock/TdGen/SineTdGen.h new file mode 100644 index 0000000..cbb11d7 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/SineTdGen.h @@ -0,0 +1,75 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_SINE_TD_GEN_H_ +#define LIBPTP_SINE_TD_GEN_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "ITdGen.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cSineTdGen: public ITdGen +{ + private: + + // Resources + + // Configuration + double f; + double A; + double phi; + + // Internal housekeeping + + // Debug functions + + protected: + + // Debugging + + // Configuration + + // Init API + void ParseParameters(); + void RegisterSignals(); + + public: + + cSineTdGen(); + ~cSineTdGen(); + + // Time readout + simtime_t GetTD( simtime_t RealTime ); +}; + +#endif + diff --git a/src/Hardware/HwClock/TdGen/TdGen_ParameterParser.cc b/src/Hardware/HwClock/TdGen/TdGen_ParameterParser.cc new file mode 100644 index 0000000..4eeb422 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/TdGen_ParameterParser.cc @@ -0,0 +1,59 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "TdGen_ParameterParser.h" +#include "ParameterParser.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +ParseType LibPLN_ExampleParse[] = +{ + { LIBPLN_EXAMPLE_AVG_OSC, "AVG_OSC" }, + { LIBPLN_EXAMPLE_WATCH_QUARTZ, "WATCH_QUARTZ" }, +}; + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// TdGen type +// ------------------------------------------------------ +LibPLN_Example_t +cTdGen_ParameterParser::ParseLibPLN_Example(const char *Str) +{ + return Parse( LibPLN_ExampleParse, ArrayLen(LibPLN_ExampleParse), Str ); +} diff --git a/src/Hardware/HwClock/TdGen/TdGen_ParameterParser.h b/src/Hardware/HwClock/TdGen/TdGen_ParameterParser.h new file mode 100644 index 0000000..66d80b5 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/TdGen_ParameterParser.h @@ -0,0 +1,53 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_TD_GEN_PARAMETER_PARSER_H_ +#define LIBPTP_TD_GEN_PARAMETER_PARSER_H_ + +// ====================================================== +// Includes +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +typedef enum +{ + LIBPLN_EXAMPLE_AVG_OSC, + LIBPLN_EXAMPLE_WATCH_QUARTZ, +} +LibPLN_Example_t; + +class cTdGen_ParameterParser +{ + public: + + static LibPLN_Example_t ParseLibPLN_Example(const char *Str); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif + diff --git a/src/Hardware/HwClock/TdGen/libPLN_TdGen.cc b/src/Hardware/HwClock/TdGen/libPLN_TdGen.cc new file mode 100644 index 0000000..082527c --- /dev/null +++ b/src/Hardware/HwClock/TdGen/libPLN_TdGen.cc @@ -0,0 +1,120 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#ifdef HAS_LIBPLN + +#include "libPLN_TdGen.h" + +#include "Examples/AverageOscillator_20MHz/AverageOscillator_20MHz.hpp" +#include "Examples/WatchQuartz_20MHz/WatchQuartz_20MHz.hpp" + +using namespace LibPLN_Examples; + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ + +void +cLibPLN_TdGen::ParseResourceParameters() +{ + Seed = pParentModule->par( "libPLN_TdGen_Seed" ).longValue(); + AllowSkipping = pParentModule->par( "libPLN_TdGen_AllowSkipping" ).boolValue(); + + if( Seed == 0 ) + { + Seed = pParentModule->getId() * 100; + pParentModule->par( "libPLN_TdGen_Seed" ).setLongValue( Seed ); + } + + ExampleType = cTdGen_ParameterParser::ParseLibPLN_Example( pParentModule->par( "libPLN_TdGen_Example" ).stringValue() ); +} + +void +cLibPLN_TdGen::AllocateResources() +{ + switch( ExampleType ) + { + case LIBPLN_EXAMPLE_AVG_OSC: pTdGen = new cAvgOsc20MHz( Seed, AllowSkipping ); + break; + + case LIBPLN_EXAMPLE_WATCH_QUARTZ: pTdGen = new cWatchQuartz_20MHz( Seed, AllowSkipping ); + break; + } + +} + +void +cLibPLN_TdGen::ParseParameters() +{ +} + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ + +cLibPLN_TdGen::cLibPLN_TdGen() + : ITdGen( "libPLN_TdGen" ) +{ + Seed = 0; + pTdGen = nullptr; +} + +cLibPLN_TdGen::~cLibPLN_TdGen() +{ + delete pTdGen; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +simtime_t +cLibPLN_TdGen::GetTD( simtime_t RealTime ) +{ + return pTdGen->EstimateTD( simTime().dbl(), RealTime.dbl() ); +} + +#endif diff --git a/src/Hardware/HwClock/TdGen/libPLN_TdGen.h b/src/Hardware/HwClock/TdGen/libPLN_TdGen.h new file mode 100644 index 0000000..2693db7 --- /dev/null +++ b/src/Hardware/HwClock/TdGen/libPLN_TdGen.h @@ -0,0 +1,77 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_LIBPLN_TD_GEN_H_ +#define LIBPTP_LIBPLN_TD_GEN_H_ + +#ifdef HAS_LIBPLN + +// ====================================================== +// Includes +// ====================================================== + +#include "ITdGen.h" + +#include "libPLN.hpp" + +#include "TdGen_ParameterParser.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cLibPLN_TdGen: public ITdGen +{ + private: + + // Resources + TdOracle *pTdGen; + + // Config + unsigned int Seed; + bool AllowSkipping; + LibPLN_Example_t ExampleType; + + protected: + + // Init API + void ParseResourceParameters(); + void AllocateResources(); + void ParseParameters(); + + public: + + cLibPLN_TdGen(); + ~cLibPLN_TdGen(); + + // Time readout + simtime_t GetTD( simtime_t RealTime ); +}; + +#endif + +#endif + diff --git a/src/Hardware/PTP_EtherEncap/IPTP_EtherEncap.ned b/src/Hardware/PTP_EtherEncap/IPTP_EtherEncap.ned new file mode 100644 index 0000000..0d8cd7c --- /dev/null +++ b/src/Hardware/PTP_EtherEncap/IPTP_EtherEncap.ned @@ -0,0 +1,34 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.PTP_EtherEncap; + +import inet.linklayer.ethernet.IEtherEncap; + +moduleinterface IPTP_EtherEncap extends IEtherEncap +{ + parameters: + @display("i=PTP/Components/Encap/Encap"); + + // PTP configuration + bool PTP_Enable; +} diff --git a/src/Hardware/PTP_EtherEncap/PTP_Eth_Msgs/PtpEthernet.msg b/src/Hardware/PTP_EtherEncap/PTP_Eth_Msgs/PtpEthernet.msg new file mode 100644 index 0000000..3001498 --- /dev/null +++ b/src/Hardware/PTP_EtherEncap/PTP_Eth_Msgs/PtpEthernet.msg @@ -0,0 +1,94 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ================================================= +// Make needed C++ stuff available +// ================================================= + +cplusplus {{ +#include "EtherFrame_m.h" +}}; + +// ================================================= +// packet announcements +// ================================================= + +class noncobject EthernetIIFrame; + +// ================================================= +// Type definitions +// ================================================= + + +// ================================================= +// Message definitions +// ================================================= + +packet PtpEtherAnnounce extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/Announce"; +} + +packet PtpEtherSync extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/Sync"; +} + +packet PtpEtherFollow extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/SyncFU"; +} + +packet PtpEtherDelayReq extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/DelayReq"; +} + +packet PtpEtherDelayResp extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/DelayResp"; +} + +packet PtpEtherPDelayReq extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/PDelayReq"; +} + +packet PtpEtherPDelayResp extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/PDelayResp"; +} + +packet PtpEtherPDelayRespF extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/PDelayRespFU"; +} + +packet PtpEtherMgmt extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/Management"; +} + +packet PtpEtherSignaling extends EthernetIIFrame +{ + string displayString = "i=PTP/Messages/Signaling"; +} diff --git a/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.cc b/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.cc new file mode 100644 index 0000000..7a3ad15 --- /dev/null +++ b/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.cc @@ -0,0 +1,218 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_EtherEncap.h" + +#include "PTP_Ethernet.h" + +#include "EtherFrame_m.h" +#include "PTPv2_m.h" +#include "PtpEthernet_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module( PTP_EtherEncap ); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Encapsulate PTP frames +// ------------------------------------------------------ +void +PTP_EtherEncap::processPacketFromHigherLayer(cPacket *msg) +{ + PTPv2_Frame *pPtpFrame; + + pPtpFrame = dynamic_cast(msg); + + if + ( + ( PTP_Enable ) && + ( pPtpFrame != nullptr ) + ) + { + EthernetIIFrame *pEthFrame; + tPtpMessageType MsgType; + + MsgType = (tPtpMessageType) pPtpFrame->getMessageType(); + + switch( MsgType ) + { + case PTP_TYPE_SYNC: pEthFrame = new PtpEtherSync; + break; + + case PTP_TYPE_DELAY_REQ: pEthFrame = new PtpEtherDelayReq; + break; + + case PTP_TYPE_PDELAY_REQ: pEthFrame = new PtpEtherPDelayReq; + break; + + case PTP_TYPE_PDELAY_RESP: pEthFrame = new PtpEtherPDelayResp; + break; + + case PTP_TYPE_FOLLOW_UP: pEthFrame = new PtpEtherFollow; + break; + + case PTP_TYPE_DELAY_RESP: pEthFrame = new PtpEtherDelayResp; + break; + + case PTP_TYPE_PDELAY_RESP_FU: pEthFrame = new PtpEtherPDelayRespF; + break; + + case PTP_TYPE_ANNOUNCE: pEthFrame = new PtpEtherAnnounce; + break; + + case PTP_TYPE_SIGNALING: pEthFrame = new PtpEtherSignaling; + break; + + case PTP_TYPE_MANAGEMENT: pEthFrame = new PtpEtherMgmt; + break; + + default: pEthFrame = new EthernetIIFrame; + break; + } + + Ieee802Ctrl *etherctrl = check_and_cast(msg->removeControlInfo()); + + // Set up Eth frame + encapsulate PTP frame + pEthFrame->setSrc(etherctrl->getSrc()); // if blank, will be filled in by MAC + pEthFrame->setDest(etherctrl->getDest()); + pEthFrame->setEtherType(etherctrl->getEtherType()); + pEthFrame->setByteLength(ETHER_MAC_FRAME_BYTES); + + pEthFrame->setControlInfo(etherctrl); + + pEthFrame->encapsulate( msg ); + + if (pEthFrame->getByteLength() < MIN_ETHERNET_FRAME_BYTES) + { + pEthFrame->setByteLength(MIN_ETHERNET_FRAME_BYTES); + } + + send( pEthFrame, "lowerLayerOut" ); + } + else + { + EtherEncap::processPacketFromHigherLayer( msg ); + } +} + +void +PTP_EtherEncap::processFrameFromMAC(EtherFrame *frame) +{ + EthernetIIFrame *pEthFrame = dynamic_cast(frame); + + if( pEthFrame != nullptr ) + { + if + ( + ( PTP_Enable ) && + ( pEthFrame->getEtherType() == PTP_ETH_TYPE ) + ) + { + // decapsulate and attach control info + cPacket *higherlayermsg = frame->decapsulate(); + + // add Ieee802Ctrl to packet + Ieee802Ctrl *etherctrl = check_and_cast(pEthFrame->removeControlInfo()); + etherctrl->setSrc(pEthFrame->getSrc()); + etherctrl->setDest(pEthFrame->getDest()); + etherctrl->setEtherType(pEthFrame->getEtherType()); + + higherlayermsg->setControlInfo(etherctrl); + + // pass up to higher layers. + send(higherlayermsg, "upperLayerOut"); + delete frame; + } + else + { + EtherEncap::processFrameFromMAC(frame); + } + } + else + { + EtherEncap::processFrameFromMAC(frame); + } +} + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ +void +PTP_EtherEncap::ParseParameters() +{ + PTP_Enable = par( "PTP_Enable" ).boolValue(); +} + +// ------------------------------------------------------ +// OMNeT API +// ------------------------------------------------------ + +int +PTP_EtherEncap::numInitStages() const +{ + return std::max( IInitBase::numInitStages(), EtherEncap::numInitStages() ); +} + +void +PTP_EtherEncap::initialize(int stage) +{ + // Forward call + if( stage < EtherEncap::numInitStages() ) + { + EtherEncap::initialize(); + } + + if( stage < IInitBase::numInitStages() ) + { + IInitBase::initialize( stage ); + } +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +PTP_EtherEncap::PTP_EtherEncap() + : EtherEncap(), PtpMcMAC(PTP_ETH_MC_DEFAULT_MAC), PtpMcPDelayMAC(PTP_ETH_MC_PDELAY_MAC) +{ +} diff --git a/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.h b/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.h new file mode 100644 index 0000000..e303432 --- /dev/null +++ b/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.h @@ -0,0 +1,70 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_ETHER_ENCAP_H_ +#define LIBPTP_PTP_ETHER_ENCAP_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "EtherEncap.h" +#include "MACAddress.h" + +#include "IInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_EtherEncap : public EtherEncap, public IInitBase +{ + private: + + // Configuration + bool PTP_Enable; + const MACAddress PtpMcMAC; + const MACAddress PtpMcPDelayMAC; + + // Inherited EtherEncap API + void processPacketFromHigherLayer(cPacket *msg); + void processFrameFromMAC(EtherFrame *frame); + + protected: + + // Init API + void ParseParameters(); + + // OMNeT API + int numInitStages() const; + void initialize(int stage); + + public: + PTP_EtherEncap(); +}; + +#endif + diff --git a/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.ned b/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.ned new file mode 100644 index 0000000..915bf82 --- /dev/null +++ b/src/Hardware/PTP_EtherEncap/PTP_EtherEncap.ned @@ -0,0 +1,35 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.PTP_EtherEncap; + +import inet.linklayer.ethernet.EtherEncap; + +simple PTP_EtherEncap extends EtherEncap like IPTP_EtherEncap +{ + parameters: + @display("i=PTP/Components/Encap/Encap"); + @class(PTP_EtherEncap); + + // PTP configuration + bool PTP_Enable = default(true); +} diff --git a/src/Hardware/PTP_MAC/IPTP_MAC.ned b/src/Hardware/PTP_MAC/IPTP_MAC.ned new file mode 100644 index 0000000..2b43eda --- /dev/null +++ b/src/Hardware/PTP_MAC/IPTP_MAC.ned @@ -0,0 +1,54 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.PTP_MAC; + +import inet.linklayer.IEtherMAC; + +moduleinterface IPTP_MAC extends IEtherMAC +{ + parameters: + + @display("i=PTP/Components/MAC/MAC"); + @class(PTP_MAC); + + // PTP configuration + bool PTP_Enable; + string PTP_ClockType; + bool PTP_TwoStepFlag; + string PTP_DelayMechanism; + + // Hardware setup + string PTP_NIC_CtrlPath; + string ClockPath; + + // Debug configuration + bool EnableDebugOutput; + + // Fault simulation + bool SimulateFault; + double FaultTime @unit(s); + double FaultDuration @unit(s); + + gates: +} + diff --git a/src/Hardware/PTP_MAC/PTP_MAC.cc b/src/Hardware/PTP_MAC/PTP_MAC.cc new file mode 100644 index 0000000..3aa80ec --- /dev/null +++ b/src/Hardware/PTP_MAC/PTP_MAC.cc @@ -0,0 +1,965 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_MAC.h" + +#include "PTP_Parser.h" +#include "PTP_Ethernet.h" + +#include "EtherFrame_m.h" +#include "PTPv2_m.h" +#include "PTP_Ctrl_m.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(PTP_MAC); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Egress utilities +// ------------------------------------------------------ + +simtime_t +PTP_MAC::CalcResidenceTime( PTPv2_Frame *pPtpFrame, int inputPort ) +{ + cLocalTimeStamp IngressTimeStamp = pPtpFrame->getIngressTimeStamp(); + cLocalTimeStamp CurrentTimeStamp = pClock->GetTimeStamp(); + simtime_t ResidenceTime = CurrentTimeStamp - IngressTimeStamp; + simtime_t PathDelay = SIMTIME_ZERO; + + if( PTP_DelayMechanism == DELAY_MECH_P2P ) + { + PathDelay = pNIC_Ctrl->GetMeanPathDelay( inputPort ); + } + ResidenceTime += PathDelay; + + if( EnableDebugOutput ) + { + EV << "Calculating ResidenceTime for " << pPtpFrame->getMessageType() << " message" << endl; + EV << "Ingress: " << IngressTimeStamp << endl; + EV << "Now: " << CurrentTimeStamp << endl; + EV << "PathDelay: " << PathDelay << endl; + EV << "ResidenceTime: " << ResidenceTime << endl; + } + + if( ResidenceTime < SIMTIME_ZERO ) + { + throw cRuntimeError( "Frame has a negative residence time, there must something fundamentally wrong." ); + } + + return ResidenceTime; +} + +void +PTP_MAC::AddCorrection( PTPv2_Frame *pPtpFrame, simtime_t Correction ) +{ + pPtpFrame->setCorrectionField( pPtpFrame->getCorrectionField().GetSimTime() + Correction ); +} + +void +PTP_MAC::AddAsymmetry( PTPv2_Frame *pPtpFrame ) +{ + AddAsymmetry( pPtpFrame, getIndex() ); +} + +void +PTP_MAC::AddAsymmetry( PTPv2_Frame *pPtpFrame, int inputPort ) +{ + AddCorrection( pPtpFrame, pNIC_Ctrl->GetAsymmetry( inputPort ) ); +} + +void +PTP_MAC::SubstractAsymmetry( PTPv2_Frame *pPtpFrame ) +{ + SubstractAsymmetry( pPtpFrame, getIndex() ); +} + +void +PTP_MAC::SubstractAsymmetry( PTPv2_Frame *pPtpFrame, int inputPort ) +{ + AddCorrection( pPtpFrame, -pNIC_Ctrl->GetAsymmetry( inputPort ) ); +} + +// ------------------------------------------------------ +// Egress of own frames +// ------------------------------------------------------ + +void +PTP_MAC::HandleEgressOwnPtp( PTPv2_Frame *pPtpFrame ) +{ + switch( pPtpFrame->getMessageType() ) + { + case PTP_TYPE_SYNC: HandleEgressOwnSync( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_FOLLOW_UP: HandleEgressOwnFollowUp( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_DELAY_REQ: HandleEgressOwnDelayReq( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_DELAY_RESP: HandleEgressOwnDelayResp(check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_PDELAY_REQ: HandleEgressOwnPDelayReq( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_PDELAY_RESP: HandleEgressOwnPDelayResp( check_and_cast(pPtpFrame) ); + break; + + + case PTP_TYPE_PDELAY_RESP_FU: HandleEgressOwnPDelayRespFU( check_and_cast(pPtpFrame) ); + break; + + default: + case PTP_TYPE_ANNOUNCE: + case PTP_TYPE_SIGNALING: + case PTP_TYPE_MANAGEMENT: // These frames don't need to be handled by the MAC at egress + break; + } +} + +void +PTP_MAC::HandleEgressOwnSync( PTPv2_SyncFrame *pSync ) +{ + if( PTP_TwoStepFlag != pSync->getFlagField().twoStepFlag ) + { + HandleUnexpectedEgress( pSync, "TwoStepFlag mismatch" ); + } + + if( PTP_TwoStepFlag ) + { + pNIC_Ctrl->SyncFuMatcher().push( pSync->getSourcePortIdentity(), pSync->getSequenceId(), pClock->GetScaledTime() ); + } + else + { + pSync->setOriginTimestamp( pClock->GetScaledTime() ); + } +} + +void +PTP_MAC::HandleEgressOwnFollowUp( PTPv2_Follow_UpFrame *pFollowUp ) +{ + if( !PTP_TwoStepFlag ) + { + HandleUnexpectedEgress( pFollowUp, "Egress of own follow up on 1-step clock" ); + } + + PTP_FrameMatch::MatchResult_t res; + + res = pNIC_Ctrl->SyncFuMatcher().pop( pFollowUp->getSourcePortIdentity(), pFollowUp->getSequenceId() ); + + if( res.valid ) + { + simtime_t SyncEgressTime = res.Time; + + pFollowUp->setPreciseOriginTimestamp( SyncEgressTime ); + } +} + +void +PTP_MAC::HandleEgressOwnDelayReq( PTPv2_Delay_ReqFrame *pDelReq ) +{ + if( PTP_DelayMechanism != DELAY_MECH_E2E ) + { + HandleUnexpectedEgress( pDelReq, "Delay mechanism is not E2E" ); + } + + pNIC_Ctrl->DelayReqRespMatcher().push( PortIdentity, pDelReq->getSequenceId(), pClock->GetScaledTime() ); + + // 1588 11.6.3b) + SubstractAsymmetry(pDelReq); +} + +void +PTP_MAC::HandleEgressOwnDelayResp( PTPv2_Delay_RespFrame *pDelResp ) +{ + if( PTP_DelayMechanism != DELAY_MECH_E2E ) + { + HandleUnexpectedEgress( pDelResp, "Delay mechanism is not E2E" ); + } +} + +void +PTP_MAC::HandleEgressOwnPDelayReq( PTPv2_PDelay_ReqFrame *pPDelReq ) +{ + if( PTP_DelayMechanism != DELAY_MECH_P2P ) + { + HandleUnexpectedEgress( pPDelReq, "Delay mechanism is not P2P" ); + } + + // 1588 11.6.4b) + SubstractAsymmetry(pPDelReq); + + // Remember PDelayReq Tx timestamp + pNIC_Ctrl->PDelayReqRespMatcher().push( PortIdentity, pPDelReq->getSequenceId(), pClock->GetScaledTime() ); +} + +void +PTP_MAC::HandleEgressOwnPDelayResp( PTPv2_PDelay_RespFrame *pPDelResp ) +{ + if( PTP_TwoStepFlag != pPDelResp->getFlagField().twoStepFlag ) + { + HandleUnexpectedEgress( pPDelResp, "TwoStepFlag mismatch" ); + } + + // Try to get T2 + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->PDelayRespFuMatcher().pop( pPDelResp->getRequestingPortIdentity(), + pPDelResp->getSequenceId() ); + + if( res.valid ) + { + // Generate T3, step b.7.ii) + simtime_t T3_T2 = pClock->GetScaledTime() - res.Time; + + if( PTP_TwoStepFlag ) + { + pNIC_Ctrl->PDelayRespFuMatcher().push( pPDelResp->getRequestingPortIdentity(), pPDelResp->getSequenceId(), T3_T2 ); + } + else + { + AddCorrection( pPDelResp, T3_T2 ); + } + } +} + +void +PTP_MAC::HandleEgressOwnPDelayRespFU( PTPv2_PDelay_Resp_FU_Frame *pPDelRespFU ) +{ + if( !PTP_TwoStepFlag ) + { + HandleUnexpectedEgress( pPDelRespFU, "Egress of own follow up on 1-step clock" ); + } + + pPDelRespFU->setResponseOriginTimestamp( cTimeStamp(0, 0) ); + + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->PDelayRespFuMatcher().pop( pPDelRespFU->getRequestingPortIdentity(), pPDelRespFU->getSequenceId() ); + + if( res.valid ) + { + simtime_t T3_T2 = res.Time; + AddCorrection( pPDelRespFU, T3_T2 ); + } +} + +// ------------------------------------------------------ +// Egress of foreign frames +// ------------------------------------------------------ + +void +PTP_MAC::HandleEgressForeignPtp( PTPv2_Frame *pPtpFrame, int inputPort ) +{ + switch( pPtpFrame->getMessageType() ) + { + case PTP_TYPE_SYNC: HandleEgressForeignSync( check_and_cast(pPtpFrame), inputPort ); + break; + + case PTP_TYPE_FOLLOW_UP: HandleEgressForeignFollowUp( check_and_cast(pPtpFrame), inputPort ); + break; + + case PTP_TYPE_DELAY_REQ: HandleEgressForeignDelayReq( check_and_cast(pPtpFrame), inputPort ); + break; + + case PTP_TYPE_DELAY_RESP: HandleEgressForeignDelayResp(check_and_cast(pPtpFrame), inputPort ); + break; + + case PTP_TYPE_PDELAY_REQ: HandleEgressForeignPDelayReq( check_and_cast(pPtpFrame), inputPort ); + break; + + case PTP_TYPE_PDELAY_RESP: HandleEgressForeignPDelayResp( check_and_cast(pPtpFrame), inputPort ); + break; + + + case PTP_TYPE_PDELAY_RESP_FU: HandleEgressForeignPDelayRespFU( check_and_cast(pPtpFrame), inputPort ); + break; + + default: + case PTP_TYPE_ANNOUNCE: + case PTP_TYPE_SIGNALING: + case PTP_TYPE_MANAGEMENT: // These frames don't need to be handled by the MAC at egress + break; + } +} + +void +PTP_MAC::HandleEgressForeignSync( PTPv2_SyncFrame *pSync, int inputPort ) +{ + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + HandleUnexpectedEgress( pSync, "Foreign sync exgress on ordinary/boundary clock" ); + } + + if( PTP_TwoStepFlag ) + { + // Remember residence time + simtime_t Correction = CalcResidenceTime( pSync, inputPort ); + + if(PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT) + { + // 1588 11.6.2c.1) + // Add asymmetry of sync frame to follow up frame + Correction += pNIC_Ctrl->GetAsymmetry( inputPort ); + } + + pNIC_Ctrl->SyncFuMatcher().push( pSync->getSourcePortIdentity(), pSync->getSequenceId(), Correction ); + + // Request Follow Up if necessary + if( pSync->getFlagField().twoStepFlag == false ) + { + pSync->getFlagField().twoStepFlag = true; + + pNIC_Ctrl->RequestSyncFollowUp( getIndex(), pSync ); + } + } + else + { + AddCorrection( pSync, CalcResidenceTime( pSync, inputPort ) ); + } +} + +void +PTP_MAC::HandleEgressForeignFollowUp( PTPv2_Follow_UpFrame *pFollowUp, int inputPort ) +{ + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + HandleUnexpectedEgress( pFollowUp, "Foreign follow up exgress on ordinary/boundary clock" ); + } + + if( PTP_TwoStepFlag ) + { + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->SyncFuMatcher().pop( pFollowUp->getSourcePortIdentity(), pFollowUp->getSequenceId() ); + + if( res.valid ) + { + AddCorrection( pFollowUp, res.Time ); + } + } +} + +void +PTP_MAC::HandleEgressForeignDelayReq( PTPv2_Delay_ReqFrame *pDelReq, int inputPort ) +{ + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + HandleUnexpectedEgress( pDelReq, "Foreign E2E exgress on ordinary/boundary clock" ); + } + + if + ( + ( PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( PTP_DelayMechanism == DELAY_MECH_P2P ) + ) + { + HandleUnexpectedEgress( pDelReq, "Foreign E2E egress on P2P transparent clock" ); + } + + simtime_t Correction; + Correction = CalcResidenceTime( pDelReq, inputPort ); + Correction -= pNIC_Ctrl->GetAsymmetry( getIndex() ); // 1588 11.6.3c.1) + 11.6.3c.2) + + if( PTP_TwoStepFlag ) + { + pNIC_Ctrl->DelayReqRespMatcher().push( pDelReq->getSourcePortIdentity(), pDelReq->getSequenceId(), Correction ); + } + else + { + AddCorrection( pDelReq, Correction ); + } +} + +void +PTP_MAC::HandleEgressForeignDelayResp( PTPv2_Delay_RespFrame *pDelResp, int inputPort ) +{ + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + HandleUnexpectedEgress( pDelResp, "Foreign E2E on ordinary/boundary clock" ); + } + + if + ( + ( PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( PTP_DelayMechanism == DELAY_MECH_P2P ) + ) + { + HandleUnexpectedEgress( pDelResp, "Foreign E2E egress on P2P transparent clock" ); + } + + // Add correction of DelayReq's residence time + if( PTP_TwoStepFlag ) + { + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->DelayReqRespMatcher().pop( pDelResp->getRequestingPortIdentity(), pDelResp->getSequenceId() ); + + if( res.valid ) + { + AddCorrection( pDelResp, res.Time ); + } + } +} + +void +PTP_MAC::HandleEgressForeignPDelayReq( PTPv2_PDelay_ReqFrame *pPDelReq, int inputPort ) +{ + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + HandleUnexpectedEgress( pPDelReq, "Foreign P2P exgress on ordinary/boundary clock" ); + } + + if + ( + ( PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( PTP_DelayMechanism == DELAY_MECH_P2P ) + ) + { + HandleUnexpectedEgress( pPDelReq, "Foreign P2P egress on P2P transparent clock" ); + } + + // 1588 11.6.4b) + SubstractAsymmetry(pPDelReq); + + simtime_t ResidenceTime = CalcResidenceTime(pPDelReq, inputPort); + if( PTP_TwoStepFlag ) + { + pNIC_Ctrl->PDelayRespFuMatcher().push( pPDelReq->getSourcePortIdentity(), pPDelReq->getSequenceId(), ResidenceTime ); + } + else + { + AddCorrection( pPDelReq, ResidenceTime ); + } +} + +void +PTP_MAC::HandleEgressForeignPDelayResp( PTPv2_PDelay_RespFrame *pPDelResp, int inputPort ) +{ + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + HandleUnexpectedEgress( pPDelResp, "Foreign P2P exgress on ordinary/boundary clock" ); + } + + if + ( + ( PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( PTP_DelayMechanism == DELAY_MECH_P2P ) + ) + { + HandleUnexpectedEgress( pPDelResp, "Foreign P2P egress on P2P transparent clock" ); + } + + if( PTP_TwoStepFlag ) + { + // Latch residence time of both Request + Response + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->PDelayRespFuMatcher().pop( pPDelResp->getSourcePortIdentity(), pPDelResp->getSequenceId() ); + + if( res.valid ) + { + simtime_t PDelayReqResTime = res.Time; + + pNIC_Ctrl->PDelayRespFuMatcher().push( pPDelResp->getSourcePortIdentity(), pPDelResp->getSequenceId(), PDelayReqResTime + CalcResidenceTime(pPDelResp, inputPort) ); + } + + // Request Follow Up if necessary + if( pPDelResp->getFlagField().twoStepFlag == false ) + { + pPDelResp->getFlagField().twoStepFlag = true; + + pNIC_Ctrl->RequestPDelayFollowUp( getIndex(), pPDelResp ); + } + } + else + { + AddCorrection( pPDelResp, CalcResidenceTime(pPDelResp, inputPort) ); + } +} + +void +PTP_MAC::HandleEgressForeignPDelayRespFU( PTPv2_PDelay_Resp_FU_Frame *pPDelRespFU, int inputPort ) +{ + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + HandleUnexpectedEgress( pPDelRespFU, "Foreign P2P exgress on ordinary/boundary clock" ); + } + + if + ( + ( PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( PTP_DelayMechanism == DELAY_MECH_P2P ) + ) + { + HandleUnexpectedEgress( pPDelRespFU, "Foreign P2P egress on P2P transparent clock" ); + } + + // Correct residence time of Requ and Resp + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->PDelayRespFuMatcher().pop( pPDelRespFU->getSourcePortIdentity(), pPDelRespFU->getSequenceId() ); + + if( res.valid ) + { + AddCorrection( pPDelRespFU, res.Time ); + } +} + +// ------------------------------------------------- +// PTP specific extension +// Ingress handling +// ------------------------------------------------- +void +PTP_MAC::TimestampOnIngress( PTPv2_Frame *pPtpFrame ) +{ + pPtpFrame->setIngressTimeStamp( pClock->GetTimeStamp() ); +} + +void +PTP_MAC::HandleIngressAnnounce( PTPv2_AnnounceFrame *pAnn ) +{ + TimestampOnIngress( pAnn ); +} + +void +PTP_MAC::HandleIngressSync( PTPv2_SyncFrame *pSync ) +{ + // 1588 11.6.2b) + if( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) + { + AddAsymmetry( pSync ); + } + + // 1588 11.6.2c) + if( PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) + { + // 1588 11.6.2c.1) + if( PTP_TwoStepFlag == false ) + { + AddAsymmetry( pSync ); + } + } + + TimestampOnIngress( pSync ); +} + +void +PTP_MAC::HandleIngressDelayReq( PTPv2_Delay_ReqFrame *pDelReq ) +{ + TimestampOnIngress( pDelReq ); +} + +void +PTP_MAC::HandleIngressDelayResp( PTPv2_Delay_RespFrame *pDelResp ) +{ + if( PortIdentity == pDelResp->getRequestingPortIdentity() ) + { + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->DelayReqRespMatcher().pop( PortIdentity, pDelResp->getSequenceId() ); + + if( res.valid ) + { + simtime_t DelayReqEgress = res.Time; + + pDelResp->setReqEgressTime( DelayReqEgress ); + } + } +} + +void +PTP_MAC::HandleIngressPDelayReq( PTPv2_PDelay_ReqFrame *pPDelReq ) +{ + // Generate T2, step b.1) + pNIC_Ctrl->PDelayRespFuMatcher().push( pPDelReq->getSourcePortIdentity(), pPDelReq->getSequenceId(), pClock->GetScaledTime() ); +} + +void +PTP_MAC::HandleIngressPDelayResp( PTPv2_PDelay_RespFrame *pPDelResp ) +{ + // Generate T4, step d.1) + TimestampOnIngress( pPDelResp ); + + // 1588 11.6.5b) + AddAsymmetry(pPDelResp); + + if( PortIdentity == pPDelResp->getRequestingPortIdentity() ) + { + PTP_FrameMatch::MatchResult_t res = pNIC_Ctrl->PDelayReqRespMatcher().pop( PortIdentity, pPDelResp->getSequenceId() ); + + if( res.valid ) + { + simtime_t PDelayReqEgress = res.Time; + + pPDelResp->setReqEgressTime( PDelayReqEgress ); + } + } +} + +void +PTP_MAC::HandleEgress() +{ + EthernetIIFrame *pEthFrame = dynamic_cast(curTxFrame); + + if( pEthFrame != nullptr ) + { + if( PTP_ETH_TYPE == pEthFrame->getEtherType() ) + { + PTPv2_Frame *pPtp = check_and_cast(pEthFrame->getEncapsulatedPacket()); + + if( PortIdentity == pPtp->getSourcePortIdentity() ) + { + HandleEgressOwnPtp( pPtp ); + } + else + { + PTP_Ctrl *pCtrl = check_and_cast(pEthFrame->removeControlInfo()); + int inputPort = pCtrl->getRxPort(); + + delete pCtrl; + + HandleEgressForeignPtp( pPtp, inputPort ); + } + } + } +} + +PTP_MAC::FrameHandling_t +PTP_MAC::HandleIngress( EtherFrame *pEthFrame ) +{ + // Filter out our own frames + // We could receive them in a ring without STP + if( pEthFrame->getSrc() == address ) + { + return DROP_FRAME; + } + + PTPv2_Frame *pPtpFrame; + + pPtpFrame = dynamic_cast(pEthFrame->getEncapsulatedPacket()); + + if( pPtpFrame != nullptr ) + { + tPtpMessageType MsgType = (tPtpMessageType) pPtpFrame->getMessageType(); + + // Dump frames of wrong delay mode + if + ( + ( + ( DELAY_MECH_P2P == PTP_DelayMechanism ) && + ( + ( MsgType == PTP_TYPE_DELAY_REQ ) || + ( MsgType == PTP_TYPE_DELAY_RESP ) + ) + ) || + ( + ( DELAY_MECH_E2E == PTP_DelayMechanism ) && + ( + ( MsgType == PTP_TYPE_PDELAY_REQ ) || + ( MsgType == PTP_TYPE_PDELAY_RESP ) || + ( MsgType == PTP_TYPE_PDELAY_RESP_FU ) + ) + ) + ) + { + return DROP_FRAME; + } + + assert( pEthFrame->getControlInfo() == nullptr ); + + PTP_Ctrl *pCtrl = new PTP_Ctrl; + + pCtrl->setRxPort( this->getIndex() ); + pEthFrame->setControlInfo( pCtrl ); + + // Handle ingress event + switch( MsgType ) + { + case PTP_TYPE_ANNOUNCE: HandleIngressAnnounce( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_SYNC: HandleIngressSync( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_DELAY_REQ: HandleIngressDelayReq( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_DELAY_RESP: HandleIngressDelayResp( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_PDELAY_REQ: HandleIngressPDelayReq( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_PDELAY_RESP: HandleIngressPDelayResp( check_and_cast(pPtpFrame) ); + break; + + default: + case PTP_TYPE_FOLLOW_UP: + + case PTP_TYPE_PDELAY_RESP_FU: + case PTP_TYPE_SIGNALING: + case PTP_TYPE_MANAGEMENT: + { + // Ignore other PTP frames + break; + } + } + } + + return HANDLE_FRAME; +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +PTP_MAC::PTP_MAC() + : EtherMAC(), IInitBase() +{ + PTP_Enable = true; + + FaultMsg = NULL; + RecoveryMsg = NULL; + + EnableDebugOutput = false; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +PTP_MAC::ParseResourceParameters() +{ + PTP_Enable = par( "PTP_Enable" ).boolValue(); + ClockPath = par("ClockPath").stringValue(); + PTP_NIC_CtrlPath = par("PTP_NIC_CtrlPath").stringValue(); +} + +void +PTP_MAC::AllocateResources() +{ + if( PTP_Enable ) + { + pClock = check_and_cast(getModuleByPath( ClockPath.c_str() )); + pNIC_Ctrl = check_and_cast(getModuleByPath( PTP_NIC_CtrlPath.c_str() )); + } +} + +void +PTP_MAC::ParseParameters() +{ + if( PTP_Enable ) + { + PTP_ClockType = cPTP_Parser::ParsePtpClockType( par( "PTP_ClockType" ).stringValue() ); + PTP_DelayMechanism = cPTP_Parser::ParseDelayMechanism( par( "PTP_DelayMechanism" ).stringValue() ); + PTP_TwoStepFlag = par( "PTP_TwoStepFlag" ).boolValue(); + + if( par( "SimulateFault" ).boolValue() ) + { + simtime_t FaultTime = simtime_t( par( "FaultTime" ).doubleValue() ); + simtime_t FaultDuration = simtime_t( par( "FaultDuration" ).doubleValue() ); + + if( FaultTime < SIMTIME_ZERO ) + { + error( "Real time value for the simulated fault occurrence must be strictly positive." ); + } + + if( FaultDuration < SIMTIME_ZERO ) + { + error( "Real time value for the simulated fault duration must be strictly positive." ); + } + + FaultMsg = new cMessage( "MAC Fault Event" ); + RecoveryMsg = new cMessage( "MAC Recovery Event" ); + + scheduleAt( FaultTime, FaultMsg ); + scheduleAt( FaultTime + FaultDuration, RecoveryMsg ); + } + + EnableDebugOutput = par( "EnableDebugOutput" ).boolValue(); + } +} + +void +PTP_MAC::InitInternalState() +{ + if( PTP_Enable ) + { + PortIdentity.ClockIdentity() = address; + PortIdentity.SetPortNumber( getIndex() + 1 ); + } +} + +void +PTP_MAC::handleSelfMessage(cMessage *msg) +{ + if( msg == FaultMsg ) + { + updateOperationalFlag(false); + delete msg; + FaultMsg = NULL; + + pNIC_Ctrl->ReportFault( getIndex() ); + } + else + { + EtherMAC::handleSelfMessage( msg ); + } +} + +void +PTP_MAC::handleMessageWhenDown(cMessage *msg) +{ + if( msg == RecoveryMsg ) + { + updateOperationalFlag(true); + RecoveryMsg = NULL; + + pNIC_Ctrl->ReportRecovery( getIndex() ); + } + + delete msg; +} + +void +PTP_MAC::HandleUnexpectedEgress( PTPv2_Frame *pFrame, std::string Reason ) +{ + std::stringstream ss; + + ss << "Error: Caught unexpected frame exgress!" << endl; + ss << endl; + ss << "Reason: " << Reason << endl; + ss << endl; + ss << "------------------------------------------" << endl; + ss << "Debug info: " << endl; + ss << "------------------------------------------" << endl; + ss << "Frame type: "; + switch( (tPtpMessageType) pFrame->getMessageType() ) + { + case PTP_TYPE_SYNC: ss << "Sync" << endl; break; + case PTP_TYPE_DELAY_REQ: ss << "DelayReq" << endl; break; + case PTP_TYPE_PDELAY_REQ: ss << "PDelayReq" << endl; break; + case PTP_TYPE_PDELAY_RESP: ss << "PDeleayResp" << endl; break; + case PTP_TYPE_FOLLOW_UP: ss << "FollowUp" << endl; break; + case PTP_TYPE_DELAY_RESP: ss << "DelayResp" << endl; break; + case PTP_TYPE_PDELAY_RESP_FU: ss << "PDelayRespFollowUp" << endl; break; + case PTP_TYPE_ANNOUNCE: ss << "Announce" << endl; break; + case PTP_TYPE_SIGNALING: ss << "Signaling" << endl; break; + case PTP_TYPE_MANAGEMENT: ss << "Management" << endl; break; + case PTP_TYPE_INVALID: ss << "Invalid" << endl; break; + } + ss << "ClockType: " << PTP_ClockType << endl; + ss << "Local twoStepFlag: " << PTP_TwoStepFlag << endl; + ss << "Frame's twoStepFlag: " << pFrame->getFlagField().twoStepFlag << endl; + ss << "DelayMechanism: " << PTP_DelayMechanism << endl; + ss << "Local port identity: " << PortIdentity << endl; + ss << "Frame's source port identity: " << pFrame->getSourcePortIdentity() << endl; + + const std::string tmp = ss.str(); + + throw cRuntimeError( tmp.c_str() ); +} + +int +PTP_MAC::numInitStages() const +{ + return std::max( IInitBase::numInitStages(), EtherMAC::numInitStages() ); +} + +void +PTP_MAC::initialize(int stage) +{ + // MACBase does not play well with InitBase, as it relies on the value of numInitStages + // and has code that is executed when stage == numInitStage-1. + // Thus we must forward as many stages as there are in IInitBase::numInitStages, not only + // as many as EtherMAC::numInitStages() requests. + if( stage < IInitBase::numInitStages() ) + { + EtherMAC::initialize( stage ); + } + + if( stage < IInitBase::numInitStages() ) + { + IInitBase::initialize( stage ); + } +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +PTP_MAC::SetMACAddress( MACAddress MAC ) +{ + address = MAC; + PortIdentity.ClockIdentity() = MAC; + + par( "address" ).setStringValue( MAC.str() ); +} + +// ------------------------------------------------------ +// Derived functions of base class +// ------------------------------------------------------ +void +PTP_MAC::startFrameTransmission() +{ + if( PTP_Enable ) + { + HandleEgress(); + } + + EtherMAC::startFrameTransmission(); +} + +void +PTP_MAC::processMsgFromNetwork(EtherTraffic *msg) +{ + if( PTP_Enable ) + { + EtherFrame *frame = dynamic_cast(msg); + + if( frame != nullptr ) + { + FrameHandling_t FrameHandling; + + FrameHandling = HandleIngress( frame ); + + if( FrameHandling == DROP_FRAME ) + { + delete frame; + return; + } + } + } + + EtherMAC::processMsgFromNetwork( msg ); +} diff --git a/src/Hardware/PTP_MAC/PTP_MAC.h b/src/Hardware/PTP_MAC/PTP_MAC.h new file mode 100644 index 0000000..6b4097c --- /dev/null +++ b/src/Hardware/PTP_MAC/PTP_MAC.h @@ -0,0 +1,169 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_MAC_H_ +#define LIBPTP_PTP_MAC_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "EtherMAC.h" + +#include "PTP.h" +#include "ScheduleClock.h" +#include "PTP_NIC_Ctrl.h" +#include "IInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_MAC : public EtherMAC, public IInitBase +{ + + private: + + // ------------------------------------------------------------ + // Types + // ------------------------------------------------------------ + typedef enum + { + DROP_FRAME, + HANDLE_FRAME, + } + FrameHandling_t; + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + std::string ClockPath; + std::string PTP_NIC_CtrlPath; + + cScheduleClock *pClock; + PTP_NIC_Ctrl *pNIC_Ctrl; + + cMessage *FaultMsg; + cMessage *RecoveryMsg; + + // ------------------------------------------------------------ + // Internal housekeeping + // ------------------------------------------------------------ + cPortIdentity PortIdentity; + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + bool PTP_Enable; + PTP_ClockType_t PTP_ClockType; + delayMechanism_t PTP_DelayMechanism; // P2P or E2E + bool PTP_TwoStepFlag; // On-the-fly mode or FollowUp frames + + // ------------------------------------------------------------ + // Debug configuration + // ------------------------------------------------------------ + bool EnableDebugOutput; + + // ------------------------------------------------- + // Internal functions + // ------------------------------------------------- + + // Egress handling + simtime_t CalcResidenceTime( PTPv2_Frame *pPtpFrame, int inputPort ); + void AddCorrection( PTPv2_Frame *pPtpFrame, simtime_t Correction ); + void AddAsymmetry( PTPv2_Frame *pPtpFrame ); + void AddAsymmetry( PTPv2_Frame *pPtpFrame, int inputPort ); + void SubstractAsymmetry( PTPv2_Frame *pPtpFrame ); + void SubstractAsymmetry( PTPv2_Frame *pPtpFrame, int inputPort ); + + void HandleEgress(); + + void HandleEgressOwnPtp ( PTPv2_Frame *pPtpFrame ); + void HandleEgressOwnSync ( PTPv2_SyncFrame *pSync ); + void HandleEgressOwnFollowUp ( PTPv2_Follow_UpFrame *pFollowUp ); + void HandleEgressOwnDelayReq ( PTPv2_Delay_ReqFrame *pDelReq ); + void HandleEgressOwnDelayResp ( PTPv2_Delay_RespFrame *pDelResp ); + void HandleEgressOwnPDelayReq ( PTPv2_PDelay_ReqFrame *pPDelReq ); + void HandleEgressOwnPDelayResp ( PTPv2_PDelay_RespFrame *pPDelResp ); + void HandleEgressOwnPDelayRespFU ( PTPv2_PDelay_Resp_FU_Frame *pPDelRespFU ); + + void HandleEgressForeignPtp ( PTPv2_Frame *pPtpFrame, int inputPort ); + void HandleEgressForeignSync ( PTPv2_SyncFrame *pSync, int inputPort ); + void HandleEgressForeignFollowUp ( PTPv2_Follow_UpFrame *pFollowUp, int inputPort ); + void HandleEgressForeignDelayReq ( PTPv2_Delay_ReqFrame *pDelReq, int inputPort ); + void HandleEgressForeignDelayResp ( PTPv2_Delay_RespFrame *pDelResp, int inputPort ); + void HandleEgressForeignPDelayReq ( PTPv2_PDelay_ReqFrame *pPDelReq, int inputPort ); + void HandleEgressForeignPDelayResp ( PTPv2_PDelay_RespFrame *pPDelResp, int inputPort ); + void HandleEgressForeignPDelayRespFU ( PTPv2_PDelay_Resp_FU_Frame *pPDelRespFU, int inputPort ); + + // Ingress handling + void TimestampOnIngress( PTPv2_Frame *pPtpFrame ); + + FrameHandling_t HandleIngress( EtherFrame *pEthFrame ); + void HandleIngressAnnounce ( PTPv2_AnnounceFrame *pAnn ); + void HandleIngressSync ( PTPv2_SyncFrame *pSync ); + void HandleIngressDelayReq ( PTPv2_Delay_ReqFrame *pDelReq ); + void HandleIngressDelayResp ( PTPv2_Delay_RespFrame *pDelResp ); + void HandleIngressPDelayReq ( PTPv2_PDelay_ReqFrame *pPDelReq ); + void HandleIngressPDelayResp ( PTPv2_PDelay_RespFrame *pPDelResp ); + + // Init API + void ParseResourceParameters(); + void AllocateResources(); + void ParseParameters(); + void InitInternalState(); + + // Error handling + void HandleUnexpectedEgress( PTPv2_Frame *pFrame, std::string Reason ); + + protected: + + virtual void handleSelfMessage(cMessage *msg); + virtual void handleMessageWhenDown(cMessage *msg); + + public: + + // Constructor + PTP_MAC(); + + // Setters + void SetMACAddress( MACAddress MAC ); + + protected: + + // OMNeT API + int numInitStages() const; + void initialize(int stage); + + // ------------------------------------------------- + // Derived functions of base class + // ------------------------------------------------- + void startFrameTransmission(); + void processMsgFromNetwork(EtherTraffic *msg); +}; + +#endif + diff --git a/src/Hardware/PTP_MAC/PTP_MAC.ned b/src/Hardware/PTP_MAC/PTP_MAC.ned new file mode 100644 index 0000000..7804db5 --- /dev/null +++ b/src/Hardware/PTP_MAC/PTP_MAC.ned @@ -0,0 +1,54 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.PTP_MAC; + +import inet.linklayer.ethernet.EtherMAC; + +simple PTP_MAC extends EtherMAC like IPTP_MAC +{ + parameters: + + @display("i=PTP/Components/MAC/MAC"); + @class(PTP_MAC); + + // PTP configuration + bool PTP_Enable = default(true); + string PTP_ClockType = default("PTP_CLOCK_TYPE_ORDINARY"); + bool PTP_TwoStepFlag = default(false); + string PTP_DelayMechanism = default("DELAY_MECH_E2E"); + + // Hardware setup + string PTP_NIC_CtrlPath = default("^.PTP_NIC_Ctrl"); + string ClockPath = default("^.Clock"); + + // Debug configuration + bool EnableDebugOutput = default( false ); + + // Fault simulation + bool SimulateFault = default( false ); + double FaultTime @unit(s) = default( 0s ); + double FaultDuration @unit(s) = default( 1s ); + + gates: +} + diff --git a/src/Hardware/PTP_NIC.ned b/src/Hardware/PTP_NIC.ned new file mode 100644 index 0000000..d68859f --- /dev/null +++ b/src/Hardware/PTP_NIC.ned @@ -0,0 +1,187 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Hardware; + +// ====================================================== +// Imports +// ====================================================== + +import libptp.Hardware.HwClock.ScheduleClock.IScheduleClock; +import libptp.Hardware.PTP_RelayUnit.IPTP_MACRelayUnit; +import libptp.Hardware.PTP_RelayUnit.PTP_MACRelayUnit; +import libptp.Hardware.PTP_MAC.PTP_MAC; +import libptp.Hardware.PTP_MAC.IPTP_MAC; +import libptp.Hardware.PTP_NIC_Ctrl.*; +import libptp.Hardware.PTP_EtherEncap.IPTP_EtherEncap; +import libptp.Hardware.PTP_EtherEncap.PTP_EtherEncap; +import libptp.Hardware.PTP_EtherEncap.*; +import libptp.Hardware.DualDelayer.IDualDelayer; +import libptp.Hardware.DualDelayer.DualDelayer; +import libptp.Hardware.EtherPhy.IEtherPhy; +import libptp.Hardware.EtherPhy.EtherPhy; + +import inet.nodes.ethernet.Eth1G; + +import inet.networklayer.common.InterfaceTable; +import inet.base.NotificationBoard; +import inet.linklayer.IMACAddressTable; + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +module PTP_NIC +{ + parameters: + @display("i=PTP/Components/NIC/NIC"); + @labels(node,ethernet-node); + @node(); + + string PTP_ClockType = default("PTP_CLOCK_TYPE_ORDINARY"); + bool PTP_TwoStepFlag = default(false); + string PTP_DelayMechanism = default("DELAY_MECH_E2E"); + + string EncapType = default("PTP_EtherEncap"); + string RelayUnitType = default("PTP_MACRelayUnit"); + string DelayerType = default("DualDelayer"); + string MACType = default("PTP_MAC"); + string EtherPhyType = default("EtherPhy"); + string ClockType = default("PerfectScheduleClock"); + string MacTableType = default("MACAddressTable"); + + gates: + input upperLayerIn @labels(Ieee802Ctrl/down); + output upperLayerOut @labels(Ieee802Ctrl/up); + + input PortConfig[]; + output PortRequest[]; + + inout ethg[] @labels(EtherFrame-conn); + + submodules: + + PTP_NIC_Ctrl: PTP_NIC_Ctrl { + + parameters: + @display("p=100,70"); + } + + Clock: like IScheduleClock { + parameters: + @display("p=100,180"); + } + + interfaceTable: InterfaceTable { + parameters: + @display("p=100,350;is=s"); + } + + notificationBoard: NotificationBoard { + parameters: + @display("p=100,400;is=s"); + } + + macTable: like IMACAddressTable { + parameters: + @display("p=100,450;is=s"); + } + + Encap: like IPTP_EtherEncap { + parameters: + @display("p=250,70"); + } + + RelayUnit: like IPTP_MACRelayUnit { + + parameters: + @display("p=250,180"); + + internalPort = 0; + + PTP_ClockType = PTP_ClockType; + + gates: + ifIn[sizeof(ethg)+1]; // Add 1 additional gate for App + ifOut[sizeof(ethg)+1]; // Add 1 additional gate for App + } + + Delayer[sizeof(ethg)]: like IDualDelayer { + parameters: + @display("p=250,290,r,110"); + + UpDelay = default(uniform(1us, 10us)); + DownDelay = default(uniform(1us, 10us)); + + EnableUpDelay = default(true); + EnableDownDelay = default(true); + } + + MAC[sizeof(ethg)]: like IPTP_MAC { + + parameters: + + @display("p=250,400,r,110"); + + promiscuous = true; + queueModule = ""; + + PTP_ClockType = PTP_ClockType; + PTP_TwoStepFlag = PTP_TwoStepFlag; + PTP_DelayMechanism = PTP_DelayMechanism; + } + + PHY[sizeof(ethg)]: like IEtherPhy { + parameters: + @display("p=250,510,r,110"); + } + + connections allowunconnected: + + upperLayerIn --> Encap.upperLayerIn; + upperLayerOut <-- Encap.upperLayerOut; + + Encap.lowerLayerIn <-- RelayUnit.ifOut[0]; + Encap.lowerLayerOut --> RelayUnit.ifIn[0]; + + for i=0..sizeof(ethg)-1 { + RelayUnit.ifOut[i+1] --> Delayer[i].upperLayerIn; + Delayer[i].lowerLayerOut --> MAC[i].upperLayerIn; + + MAC[i].upperLayerOut --> Delayer[i].lowerLayerIn; + Delayer[i].upperLayerOut --> RelayUnit.ifIn[i+1]; + + MAC[i].phys <--> PHY[i].mii; + PHY[i].phy <--> ethg[i]; + + PortConfig[i] --> PTP_NIC_Ctrl.PortConfig++; + PortRequest[i] <-- PTP_NIC_Ctrl.PortRequest++; + } +} diff --git a/src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.cc b/src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.cc new file mode 100644 index 0000000..84d0a05 --- /dev/null +++ b/src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.cc @@ -0,0 +1,149 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_FrameMatch.h" +#include "PTP_NIC_Ctrl.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +PTP_FrameMatch::PTP_FrameMatch( std::string InstanceName ) + : cSubmoduleInitBase(), InstanceName( InstanceName ) +{ + initialized = false; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +PTP_FrameMatch::RegisterSignals() +{ + Push_SigId = pParentModule->registerSignal( (std::string( "Push" ) + InstanceName).c_str() ); + Pop_SigId = pParentModule->registerSignal( (std::string( "Pop" ) + InstanceName).c_str() ); +} + +void +PTP_FrameMatch::InitSignals() +{ +} + +void +PTP_FrameMatch::FinishInit() +{ + initialized = true; +} + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ + +void +PTP_FrameMatch::push( cPortIdentity sourcePortIdentity, uint16_t sequenceId, simtime_t time ) +{ + if( !initialized ) + throw cRuntimeError( "FrameMatcher was not initialized before use" ); + + pParentModule->emit( Push_SigId, sequenceId ); + + FrameMatch_t Match; + + Match.sourcePortIdentity = sourcePortIdentity; + Match.sequenceId = sequenceId; + Match.Time = time; + + Queue.push_back(Match); +} + +PTP_FrameMatch::MatchResult_t +PTP_FrameMatch::pop( cPortIdentity sourcePortIdentity, uint16_t sequenceId ) +{ + if( !initialized ) + throw cRuntimeError( "FrameMatcher was not initialized before use" ); + + pParentModule->emit( Pop_SigId, sequenceId ); + + MatchResult_t res; + + std::vector::iterator it = Queue.begin(); + while( it != Queue.end() ) + { + if + ( + ( it->sourcePortIdentity == sourcePortIdentity) && + ( it->sequenceId < sequenceId ) + ) + { + it = Queue.erase(it); + } + else if + ( + ( it->sourcePortIdentity == sourcePortIdentity) && + ( it->sequenceId == sequenceId ) + ) + { + res.valid = true; + res.Time = it->Time; + + it = Queue.erase(it); + + return res; + } + else + { + ++it; + } + } + + EV << InstanceName << ": failed to match frames" << endl; + + res.valid = false; + res.Time = SIMTIME_ZERO; + + return res; +} + + diff --git a/src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.h b/src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.h new file mode 100644 index 0000000..e5bd000 --- /dev/null +++ b/src/Hardware/PTP_NIC_Ctrl/FrameMatch/PTP_FrameMatch.h @@ -0,0 +1,101 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_FRAME_MATCH_H_ +#define LIBPTP_PTP_FRAME_MATCH_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP.h" +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_NIC_Ctrl; + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_FrameMatch : public cSubmoduleInitBase +{ + private: + + struct FrameMatch_t + { + cPortIdentity sourcePortIdentity; + uint16_t sequenceId; + simtime_t Time; + }; + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + std::vector Queue; + + bool initialized; + + simsignal_t Push_SigId; + simsignal_t Pop_SigId; + + // ------------------------------------------------------------ + // Internal housekeeping + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + std::string InstanceName; + + // ------------------------------------------------------------ + // Internal functions + // ------------------------------------------------------------ + + // Init API + void RegisterSignals(); + void InitSignals(); + void FinishInit(); + + protected: + + public: + + struct MatchResult_t + { + bool valid; + simtime_t Time; + }; + + // Constructor + PTP_FrameMatch( std::string InstanceName ); + + // API + void push( cPortIdentity sourcePortIdentity, uint16_t sequenceId, simtime_t time ); + MatchResult_t pop( cPortIdentity sourcePortIdentity, uint16_t sequenceId ); +}; + +#endif + diff --git a/src/Hardware/PTP_NIC_Ctrl/PTP_Config_Msg/PtpPortConfig.msg b/src/Hardware/PTP_NIC_Ctrl/PTP_Config_Msg/PtpPortConfig.msg new file mode 100644 index 0000000..9ec62f2 --- /dev/null +++ b/src/Hardware/PTP_NIC_Ctrl/PTP_Config_Msg/PtpPortConfig.msg @@ -0,0 +1,81 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ================================================= +// Make needed C++ stuff available +// ================================================= + +cplusplus {{ +#include +#include "MACAddress.h" +}}; + +// ================================================= +// Class announcements +// ================================================= + +class noncobject simtime_t; +class noncobject MACAddress; + +// ================================================= +// Type definitions +// ================================================= + +enum tPtpNicConf +{ + PTP_NIC_CONF_TYPE_MAC = 0; +}; + +enum tPtpPortConf +{ + PTP_PORT_CONF_TYPE_PATH_DELAY = 0; + PTP_PORT_CONF_TYPE_ASYMMETRY = 1; +}; + +// ================================================= +// Message definitions +// ================================================= + +message PtpNicConfig +{ + int Type @enum(tPtpNicConf); +} + +message PtpNicConfig_MAC extends PtpNicConfig +{ + MACAddress MAC; +} + +message PtpPortConfig +{ + int Type @enum(tPtpPortConf); +} + +message PtpPortConfig_PathDelay extends PtpPortConfig +{ + simtime_t MeanPathDelay; +} + +message PtpPortConfig_Asymmetry extends PtpPortConfig +{ + simtime_t Asymmetry; +} diff --git a/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.cc b/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.cc new file mode 100644 index 0000000..eeefd08 --- /dev/null +++ b/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.cc @@ -0,0 +1,332 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_NIC_Ctrl.h" + +#include "PTP_MAC.h" +#include "PTP_MACRelayUnit.h" + +#include "PtpPortRequ_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(PTP_NIC_Ctrl); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Handle config messages +// ------------------------------------------------------ +void +PTP_NIC_Ctrl::HandlePtpConfig( PtpPortConfig *pConfig ) +{ + tPtpPortConf Type = (tPtpPortConf) pConfig->getType(); + int PortIdx = pConfig->getArrivalGateId() - ConfigGateID; + + CheckPortIdx( PortIdx ); + + switch( Type ) + { + case PTP_PORT_CONF_TYPE_PATH_DELAY: + { + PtpPortConfig_PathDelay *pPD = check_and_cast(pConfig); + Ports[ PortIdx ].peerMeanPathDelay = pPD->getMeanPathDelay(); + break; + } + + case PTP_PORT_CONF_TYPE_ASYMMETRY: + { + PtpPortConfig_Asymmetry *pA = check_and_cast(pConfig); + Ports[ PortIdx ].Asymmetry = pA->getAsymmetry(); + break; + } + + default: + { + break; + } + } +} + +void +PTP_NIC_Ctrl::HandleNicConfig( PtpNicConfig *pConfig ) +{ + tPtpNicConf Type = (tPtpNicConf) pConfig->getType(); + + switch( Type ) + { + case PTP_NIC_CONF_TYPE_MAC: + { + PtpNicConfig_MAC *pMAC_Config = check_and_cast(pConfig); + + for( cModule::SubmoduleIterator it(getParentModule()); !it.end(); it++ ) + { + if( strcmp( it()->getName(), "MAC" ) == 0 ) + { + PTP_MAC *pMod = check_and_cast(it()); + + pMod->SetMACAddress( pMAC_Config->getMAC() ); + } + + if( strcmp( it()->getName(), "RelayUnit" ) == 0 ) + { + PTP_MACRelayUnit *pMod = check_and_cast(it()); + + pMod->SetMACAddress( pMAC_Config->getMAC() ); + } + } + + break; + } + + default: + { + break; + } + } +} + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +PTP_NIC_Ctrl::CheckPortIdx( int PortIdx ) +{ + if( PortIdx < 0 ) + { + error( "Port index must be positive" ); + } + + if( PortIdx >= NumPorts ) + { + error( "Port index exceeds allowed range" ); + } +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +PTP_NIC_Ctrl::ParseResourceParameters() +{ + RequestGateID = gateBaseId( "PortRequest" ); + ConfigGateID = gateBaseId( "PortConfig" ); + NumPorts = gateSize( "PortRequest" ); + + if( gateSize( "PortRequest" ) != gateSize( "PortConfig" ) ) + { + error( "Size of vector gates must match." ); + } +} + +void +PTP_NIC_Ctrl::AllocateResources() +{ + Ports.reserve( NumPorts ); +} + +void +PTP_NIC_Ctrl::InitHierarchy() +{ + SyncFU_Matcher.SetParentModule ( this ); + DelayReqResp_Matcher.SetParentModule ( this ); + PDelayReqResp_Matcher.SetParentModule( this ); + PDelayRespFu_Matcher.SetParentModule ( this ); +} + +void +PTP_NIC_Ctrl::InitInternalState() +{ + for( int PortIdx = 0; PortIdx < NumPorts; PortIdx ++ ) + { + Ports[ PortIdx ].peerMeanPathDelay = SIMTIME_ZERO; + } +} + +void +PTP_NIC_Ctrl::ForwardInit( int stage ) +{ + SyncFU_Matcher.initialize ( stage ); + DelayReqResp_Matcher.initialize ( stage ); + PDelayReqResp_Matcher.initialize( stage ); + PDelayRespFu_Matcher.initialize ( stage ); +} + +// ------------------------------------------------------ +// Handle messages +// ------------------------------------------------------ +void +PTP_NIC_Ctrl::handleMessage(cMessage *pMsg) +{ + PtpPortConfig *pPortConfig = dynamic_cast(pMsg); + PtpNicConfig *pNicConfig = dynamic_cast(pMsg); + + if( pPortConfig != NULL ) + { + HandlePtpConfig( pPortConfig ); + } + else if( pNicConfig != NULL ) + { + HandleNicConfig( pNicConfig ); + } + + delete pMsg; +} + +// ------------------------------------------------------ +// Public API +// ------------------------------------------------------ +PTP_NIC_Ctrl::PTP_NIC_Ctrl() + : SyncFU_Matcher ( "SyncFu" ), + DelayReqResp_Matcher ( "DelayReqResp" ), + PDelayReqResp_Matcher( "PDelayReqResp" ), + PDelayRespFu_Matcher ( "PDelayRespFu" ) +{ +} + +// ------------------------------------------------------ +// Public API +// ------------------------------------------------------ + +PTP_FrameMatch & +PTP_NIC_Ctrl::SyncFuMatcher() +{ + return this->SyncFU_Matcher; +} + +PTP_FrameMatch & +PTP_NIC_Ctrl::DelayReqRespMatcher() +{ + return this->DelayReqResp_Matcher; +} + +PTP_FrameMatch & +PTP_NIC_Ctrl::PDelayReqRespMatcher() +{ + return this->PDelayReqResp_Matcher; +} + +PTP_FrameMatch & +PTP_NIC_Ctrl::PDelayRespFuMatcher() +{ + return this->PDelayRespFu_Matcher; +} + +void +PTP_NIC_Ctrl::RequestSyncFollowUp( uint16_t PortIdx, PTPv2_SyncFrame *pSync ) +{ + EnterModuleSilent(); + + CheckPortIdx( PortIdx ); + + PtpPortRequ_TrigSyncFU *pRequ = new PtpPortRequ_TrigSyncFU( "PTP: request Sync Follow Up" ); + + pRequ->setType( PTP_PORT_REQU_TYPE_TRIG_SFU ); + pRequ->setSyncFrame( *pSync ); + + send( pRequ, RequestGateID + PortIdx ); + + LeaveModule(); +} + +void +PTP_NIC_Ctrl::RequestPDelayFollowUp( uint16_t PortIdx, PTPv2_PDelay_RespFrame *pPDelResp ) +{ + EnterModuleSilent(); + + CheckPortIdx( PortIdx ); + + PtpPortRequ_TrigPDelayFU *pRequ = new PtpPortRequ_TrigPDelayFU( "PTP: request PDelay Resp Follow Up"); + + pRequ->setType( PTP_PORT_REQU_TYPE_TRIG_PDFU ); + pRequ->setPDelayFrame( *pPDelResp ); + + send( pRequ, RequestGateID + PortIdx ); + + LeaveModule(); +} + +void +PTP_NIC_Ctrl::ReportFault( uint16_t PortIdx ) +{ + EnterModuleSilent(); + + CheckPortIdx( PortIdx ); + + PtpPortRequ *pRequ = new PtpPortRequ( "PTP: Fault at MAC"); + pRequ->setType( PTP_PORT_REQU_TYPE_FAULT ); + + send( pRequ, RequestGateID + PortIdx ); + + LeaveModule(); +} + +void +PTP_NIC_Ctrl::ReportRecovery( uint16_t PortIdx ) +{ + EnterModuleSilent(); + + CheckPortIdx( PortIdx ); + + PtpPortRequ *pRequ = new PtpPortRequ( "PTP: Fault at MAC"); + pRequ->setType( PTP_PORT_REQU_TYPE_RECOVERY ); + + send( pRequ, RequestGateID + PortIdx ); + + LeaveModule(); +} + +simtime_t +PTP_NIC_Ctrl::GetMeanPathDelay( uint16_t PortIdx ) +{ + CheckPortIdx( PortIdx ); + + return Ports[PortIdx].peerMeanPathDelay; +} + +simtime_t +PTP_NIC_Ctrl::GetAsymmetry( uint16_t PortIdx ) +{ + CheckPortIdx( PortIdx ); + + return Ports[PortIdx].Asymmetry; +} diff --git a/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.h b/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.h new file mode 100644 index 0000000..d964282 --- /dev/null +++ b/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.h @@ -0,0 +1,108 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_NIC_CTRL_H_ +#define LIBPTP_PTP_NIC_CTRL_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_FrameMatch.h" +#include "ModuleInitBase.h" + +#include "PtpPortConfig_m.h" + +// ====================================================== +// Types +// ====================================================== + +struct Port_t +{ + simtime_t peerMeanPathDelay; + simtime_t Asymmetry; +}; + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_NIC_Ctrl : public cModuleInitBase +{ + private: + + // Resources + PTP_FrameMatch SyncFU_Matcher; + PTP_FrameMatch DelayReqResp_Matcher; + PTP_FrameMatch PDelayReqResp_Matcher; + PTP_FrameMatch PDelayRespFu_Matcher; + + std::vector Ports; + + int RequestGateID; + int ConfigGateID; + int NumPorts; + + // Config + + // ------------------------------------------------- + // Internal functions + // ------------------------------------------------- + + // Init API + void ParseResourceParameters(); + void AllocateResources(); + void InitHierarchy(); + void InitInternalState(); + void ForwardInit( int stage ); + + void HandlePtpConfig( PtpPortConfig *pConfig ); + void HandleNicConfig( PtpNicConfig *pConfig ); + + void CheckPortIdx( int PortIdx ); + + protected: + + void handleMessage(cMessage *msg); + + public: + + // Constructors + PTP_NIC_Ctrl(); + + // Instance functions + PTP_FrameMatch &SyncFuMatcher(); + PTP_FrameMatch &DelayReqRespMatcher(); + PTP_FrameMatch &PDelayReqRespMatcher(); + PTP_FrameMatch &PDelayRespFuMatcher(); + + void RequestSyncFollowUp( uint16_t PortIdx, PTPv2_SyncFrame *pSync ); + void RequestPDelayFollowUp( uint16_t PortIdx, PTPv2_PDelay_RespFrame *pPDelResp ); + void ReportFault( uint16_t PortIdx ); + void ReportRecovery( uint16_t PortIdx ); + simtime_t GetMeanPathDelay( uint16_t PortIdx ); + simtime_t GetAsymmetry( uint16_t PortIdx ); +}; + +#endif diff --git a/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.ned b/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.ned new file mode 100644 index 0000000..8ff2d62 --- /dev/null +++ b/src/Hardware/PTP_NIC_Ctrl/PTP_NIC_Ctrl.ned @@ -0,0 +1,81 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Hardware.PTP_NIC_Ctrl; + +// ====================================================== +// Imports +// ====================================================== + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +simple PTP_NIC_Ctrl +{ + parameters: + + @display("i=PTP/Components/NIC_Ctrl/NIC_Ctrl"); + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + @signal[PushSync](type=unsigned long); + @signal[PopSync](type=unsigned long); + + @signal[PushDelayReqResp](type=unsigned long); + @signal[PopDelayReqResp](type=unsigned long); + + @signal[PushPDelayReqResp](type=unsigned long); + @signal[PopPDelayReqResp](type=unsigned long); + + @signal[PushPDelayRespFu](type=unsigned long); + @signal[PopPDelayRespFu](type=unsigned long); + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- + @statistic[PushSync](record=vector?); + @statistic[PopSync](record=vector?); + + @statistic[PushDelayReqResp](record=vector?); + @statistic[PopDelayReqResp](record=vector?); + + @statistic[PushPDelayReqResp](record=vector?); + @statistic[PopPDelayReqResp](record=vector?); + + @statistic[PushPDelayRespFu](record=vector?); + @statistic[PopPDelayRespFu](record=vector?); + + gates: + + input PortConfig[]; + output PortRequest[]; +} diff --git a/src/Hardware/PTP_NIC_Ctrl/PTP_Requ_Msg/PtpPortRequ.msg b/src/Hardware/PTP_NIC_Ctrl/PTP_Requ_Msg/PtpPortRequ.msg new file mode 100644 index 0000000..475d521 --- /dev/null +++ b/src/Hardware/PTP_NIC_Ctrl/PTP_Requ_Msg/PtpPortRequ.msg @@ -0,0 +1,67 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ================================================= +// Make needed C++ stuff available +// ================================================= + +cplusplus {{ +#include "PTPv2_m.h" +}}; + +// ================================================= +// Class announcements +// ================================================= + +class noncobject PTPv2_SyncFrame; +class noncobject PTPv2_PDelay_RespFrame; + +// ================================================= +// Type definitions +// ================================================= + +enum tPtpPortRequ +{ + PTP_PORT_REQU_TYPE_TRIG_SFU = 0; + PTP_PORT_REQU_TYPE_TRIG_PDFU = 1; + PTP_PORT_REQU_TYPE_FAULT = 2; + PTP_PORT_REQU_TYPE_RECOVERY = 3; +}; + +// ================================================= +// Message definitions +// ================================================= + +message PtpPortRequ +{ + int Type @enum(tPtpPortRequ); +} + +message PtpPortRequ_TrigSyncFU extends PtpPortRequ +{ + PTPv2_SyncFrame SyncFrame; +} + +message PtpPortRequ_TrigPDelayFU extends PtpPortRequ +{ + PTPv2_PDelay_RespFrame PDelayFrame; +} diff --git a/src/Hardware/PTP_RelayUnit/IPTP_MACRelayUnit.ned b/src/Hardware/PTP_RelayUnit/IPTP_MACRelayUnit.ned new file mode 100644 index 0000000..1191e46 --- /dev/null +++ b/src/Hardware/PTP_RelayUnit/IPTP_MACRelayUnit.ned @@ -0,0 +1,45 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.PTP_RelayUnit; + +import inet.linklayer.IMACRelayUnit; + +moduleinterface IPTP_MACRelayUnit extends IMACRelayUnit +{ + parameters: + + @display("i=PTP/Components/RelayUnit/RelayUnit"); + + bool PTP_Enable; + string PTP_ClockType; + + string MAC_Address; + + // Defines the internal port of a node + // The value -1 means that there is no internal port configured + int internalPort; + + gates: + +} + diff --git a/src/Hardware/PTP_RelayUnit/MACRelayUnitBase.cc b/src/Hardware/PTP_RelayUnit/MACRelayUnitBase.cc new file mode 100644 index 0000000..7f94ace --- /dev/null +++ b/src/Hardware/PTP_RelayUnit/MACRelayUnitBase.cc @@ -0,0 +1,128 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "MACRelayUnitBase.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +void +MACRelayUnitBase::handleAndDispatchFrame_Prefix(EtherFrame *frame, int &inputport) +{ + inputport = frame->getArrivalGate()->getIndex(); + + numProcessedFrames++; + + // update address table + addressTable->updateTableWithAddress(inputport, frame->getSrc()); +} + +void +MACRelayUnitBase::handleAndDispatchFrame_Dispatch(EtherFrame *frame, int inputport) +{ + if (frame->getDest().isBroadcast()) + { + broadcastFrame(frame, inputport); + } + else if (frame->getDest().isMulticast()) + { + multicastFrame(frame, inputport); + } + else + { + unicastFrame( frame, inputport ); + } +} + +void +MACRelayUnitBase::handleAndDispatchFrame(EtherFrame *frame) +{ + int inputport; + + handleAndDispatchFrame_Prefix ( frame, inputport ); + handleAndDispatchFrame_Dispatch( frame, inputport ); +} + +void +MACRelayUnitBase::unicastFrame(EtherFrame *frame, int inputport) +{ + // Finds output port of destination address and sends to output port + // if not found then broadcasts to all other ports instead + int outputport = addressTable->getPortForAddress(frame->getDest()); + // should not send out the same frame on the same ethernet port + // (although wireless ports are ok to receive the same message) + if (inputport == outputport) + { + EV << "Output port is same as input port, " << frame->getFullName() << + " dest " << frame->getDest() << ", discarding frame\n"; + numDiscardedFrames++; + delete frame; + return; + } + + if (outputport >= 0) + { + send(frame, "ifOut", outputport); + } + else + { + broadcastFrame(frame, inputport); + } +} + +void +MACRelayUnitBase::multicastFrame(EtherFrame *frame, int inputport) +{ + // Emulating multicast via broadcast + broadcastFrame( frame, inputport ); +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +MACRelayUnitBase::MACRelayUnitBase() + : MACRelayUnit() +{ +} diff --git a/src/Hardware/PTP_RelayUnit/MACRelayUnitBase.h b/src/Hardware/PTP_RelayUnit/MACRelayUnitBase.h new file mode 100644 index 0000000..fd10c70 --- /dev/null +++ b/src/Hardware/PTP_RelayUnit/MACRelayUnitBase.h @@ -0,0 +1,59 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_MAC_RELAY_UNIT_BASE_H_ +#define LIBPTP_MAC_RELAY_UNIT_BASE_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + + +class MACRelayUnitBase : public MACRelayUnit +{ + protected: + + virtual void handleAndDispatchFrame_Prefix(EtherFrame *frame, int &inputport); + virtual void handleAndDispatchFrame_Dispatch(EtherFrame *frame, int inputport); + virtual void handleAndDispatchFrame(EtherFrame *frame); + + virtual void unicastFrame(EtherFrame *frame, int inputport); + virtual void multicastFrame(EtherFrame *frame, int inputport); + + public: + + // Constructor + MACRelayUnitBase(); +}; + +#endif + diff --git a/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.cc b/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.cc new file mode 100644 index 0000000..21030d4 --- /dev/null +++ b/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.cc @@ -0,0 +1,200 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_MACRelayUnit.h" + +#include + +#include "PTP_Ethernet.h" +#include "PTP_Parser.h" + +#include "PTPv2_m.h" +#include "PTP_Ctrl_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module( PTP_MACRelayUnit ); + +// ====================================================== +// Definitions +// ====================================================== + +void +PTP_MACRelayUnit::handleAndDispatchFrame_Dispatch(EtherFrame *frame, int inputport) +{ + EthernetIIFrame *pEthFrame = dynamic_cast(frame); + + if( internalPort >= 0 ) + { + if( pEthFrame->getDest() == MAC ) + { + send(frame, "ifOut", internalPort); + return; + } + } + + if + ( + ( PTP_Enable ) && + ( pEthFrame != nullptr ) && + ( PTP_ETH_TYPE == pEthFrame->getEtherType() ) + ) + { + PTPv2_Frame *pPtpFrame = check_and_cast( pEthFrame->getEncapsulatedPacket() ); + tPtpMessageType MsgType = (tPtpMessageType) pPtpFrame->getMessageType(); + + // Check if frame comes from local application + if( inputport == internalPort ) + { + PTP_Ctrl *pCtrl = check_and_cast(pEthFrame->getControlInfo()); + + int outputport = pCtrl->getTxPort(); + + if( outputport >= internalPort ) + { + outputport++; + } + + send(frame, "ifOut", outputport ); + } + else if // Filter out frames for local application + ( + ( PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) || + ( MsgType == PTP_TYPE_PDELAY_REQ ) || + ( MsgType == PTP_TYPE_PDELAY_RESP ) || + ( MsgType == PTP_TYPE_PDELAY_RESP_FU ) + ) + { + if( internalPort >= 0 ) + { + send(frame, "ifOut", internalPort); + } + } + else + { + multicastFrame( frame, inputport ); + } + } + else + { + MACRelayUnitBase::handleAndDispatchFrame_Dispatch( frame, inputport ); + } +} + +void +PTP_MACRelayUnit::broadcastFrame(EtherFrame *frame, int inputport) +{ + // Broadcast frame + // Ensure that original frame gets forwarded to the internal port, + // so that the attached control info reaches the application + for (int i=0; idup(); + + if( frame->getControlInfo() != nullptr ) + { + d->setControlInfo(frame->getControlInfo()->dup()); + } + + send(d, "ifOut", i); + } + } + + delete frame; +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +PTP_MACRelayUnit::PTP_MACRelayUnit() + : MACRelayUnitBase(), IInitBase() +{ + PTP_Enable = true; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +PTP_MACRelayUnit::ParseParameters() +{ + PTP_Enable = par( "PTP_Enable" ).boolValue(); + PTP_ClockType = cPTP_Parser::ParsePtpClockType( par( "PTP_ClockType" ).stringValue() ); + internalPort = par( "internalPort" ).longValue(); + + MAC.setAddress( par( "MAC_Address" ).stringValue() ); + + // Write back parsed values + par( "MAC_Address" ).setStringValue( MAC.str() ); +} + +int +PTP_MACRelayUnit::numInitStages() const +{ + return std::max( IInitBase::numInitStages(), MACRelayUnitBase::numInitStages() ); +} + +void +PTP_MACRelayUnit::initialize(int stage) +{ + // Forward call + if( stage < MACRelayUnitBase::numInitStages() ) + { + MACRelayUnitBase::initialize(stage); + } + + if( stage < IInitBase::numInitStages() ) + { + IInitBase::initialize( stage ); + } +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +PTP_MACRelayUnit::SetMACAddress( MACAddress MAC ) +{ + this->MAC = MAC; + + par( "MAC_Address" ).setStringValue( MAC.str() ); +} diff --git a/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.h b/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.h new file mode 100644 index 0000000..93c52a6 --- /dev/null +++ b/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.h @@ -0,0 +1,80 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_MAC_RELAY_UNIT_H_ +#define LIBPTP_PTP_MAC_RELAY_UNIT_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "MACRelayUnitBase.h" + +#include "PTP.h" +#include "IInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + + +class PTP_MACRelayUnit : public MACRelayUnitBase, public IInitBase +{ + private: + + // Configuration + bool PTP_Enable; + PTP_ClockType_t PTP_ClockType; + MACAddress MAC; + + // Init API + void ParseParameters(); + + protected: + + // Configuration + int internalPort; + + // Internal functions + virtual void handleAndDispatchFrame_Dispatch(EtherFrame *frame, int inputport); + + virtual void broadcastFrame(EtherFrame *frame, int inputport); + + public: + + // Constructor + PTP_MACRelayUnit(); + + // OMNeT API + int numInitStages() const; + void initialize(int stage); + + // Setters + void SetMACAddress( MACAddress MAC ); +}; + +#endif + diff --git a/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.ned b/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.ned new file mode 100644 index 0000000..c7f117e --- /dev/null +++ b/src/Hardware/PTP_RelayUnit/PTP_MACRelayUnit.ned @@ -0,0 +1,40 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Hardware.PTP_RelayUnit; + +import inet.linklayer.ethernet.switch.MACRelayUnit; + +simple PTP_MACRelayUnit extends MACRelayUnit like IPTP_MACRelayUnit +{ + parameters: + @display("i=PTP/Components/RelayUnit/RelayUnit"); + @class(PTP_MACRelayUnit); + + bool PTP_Enable = default(true); + string PTP_ClockType = default("PTP_CLOCK_TYPE_ORDINARY"); + string MAC_Address = default("C0:FF:EE:BA:D0:1D"); + int internalPort = default( 0 ); + + gates: +} + diff --git a/src/Makefrag_Boost b/src/Makefrag_Boost new file mode 100755 index 0000000..4171738 --- /dev/null +++ b/src/Makefrag_Boost @@ -0,0 +1,38 @@ +# =================================================================== +# Instructions +# =================================================================== +# +# This file contains configuration options to link against your +# local boost installation. It needs to be customized to your +# local setup. +# +# Changes to this file should be kept local, and should not be +# commited upstream. +# +# To avoid checking in your local changes to this file, the +# following git command might be useful: +# +# git update-index --assume-unchanged src/Makefrag_Boost +# +# The effect of this command can be inverted using the option +# --no-assume-unchanged +# + +# =================================================================== +# Compiler flags +# =================================================================== + +# =================================================================== +# Include paths +# =================================================================== + +# Include path for Boost library +#INCLUDE_PATH += -I"C:/boost" + +# =================================================================== +# Library paths +# =================================================================== + +# =================================================================== +# Libraries +# =================================================================== diff --git a/src/Makefrag_GenericBuildOptions b/src/Makefrag_GenericBuildOptions new file mode 100755 index 0000000..5b790ee --- /dev/null +++ b/src/Makefrag_GenericBuildOptions @@ -0,0 +1,28 @@ + +# =================================================================== +# Instructions +# =================================================================== +# +# This file contains generic configuration for the entire project. +# Changes to this file should commited upstream. +# +# Local changes to the configuration should be kept in separate +# files. +# + +# =================================================================== +# Compiler flags +# =================================================================== +#CXXFLAGS += -pedantic + +# =================================================================== +# Include paths +# =================================================================== + +# =================================================================== +# Library paths +# =================================================================== + +# =================================================================== +# Libraries +# =================================================================== diff --git a/src/Makefrag_libPLN b/src/Makefrag_libPLN new file mode 100755 index 0000000..196128b --- /dev/null +++ b/src/Makefrag_libPLN @@ -0,0 +1,128 @@ + +# =================================================================== +# Instructions +# =================================================================== +# +# This file contains configuration options to link against your +# local libPLN installation. It needs to be customized to your +# local setup. +# +# Changes to this file should be kept local, and should not be +# commited upstream. +# +# To avoid checking in your local changes to this file, the +# following git command might be useful: +# +# git update-index --assume-unchanged src/Makefrag_libPLN +# +# The effect of this command can be inverted using the option +# --no-assume-unchanged +# + +# =================================================================== +# Compiler flags +# =================================================================== + +# =================================================================== +# Macro definitions +# =================================================================== + +# ------------------------------------------------------------------- +# HAS_LIBPLN flag +# ------------------------------------------------------------------- + +# Enable this define to use the libPLN library with the libPTP library. +# If this option is set, the following dependancies have to be fulfilled: +# +# FFTW library +# Boost library +# libPLN and libPLN_Examples libraries +# +# The paths for includes and libraries, as well as the library names +# have to be adapted to your local settings below. + +#CXXFLAGS += -DHAS_LIBPLN + +# =================================================================== +# Include paths +# =================================================================== + +# ------------------------------------------------------------------- +# Include path for FFTW library +# ------------------------------------------------------------------- + +# --------------------------------------------------- +# Wolfgang's machine (Linux): +# --------------------------------------------------- +# Not needed (default include path on linux) + +# --------------------------------------------------- +# Wolfgang's machine (Windows): +# --------------------------------------------------- +# INCLUDE_PATH += -I"C:/FFTW32" + +# ------------------------------------------------------------------- +# Include paths for libPLN +# ------------------------------------------------------------------- + +# --------------------------------------------------- +# Wolfgang's machine (Linux): +# --------------------------------------------------- +# INCLUDE_PATH += -I/main/TU/act/libPLN +# INCLUDE_PATH += -I/main/TU/act/libPLN/src + +# --------------------------------------------------- +# Wolfgang's machine (Windows): +# --------------------------------------------------- +# INCLUDE_PATH += -IC:/Projects/libPLN +# INCLUDE_PATH += -IC:/Projects/libPLN/src + +# =================================================================== +# Library paths +# =================================================================== + +# ------------------------------------------------------------------- +# Library path for libPLN +# ------------------------------------------------------------------- + +# --------------------------------------------------- +# Wolfgang's machine (Linux): +# --------------------------------------------------- +# LIBS += -L/main/TU/act/libPLN/build/lib/static + +# --------------------------------------------------- +# Wolfgang's machine (Windows): +# --------------------------------------------------- +# LIBS += -LC:/Projects/libPLN/build/lib/static + +# ------------------------------------------------------------------- +# Library path for FFTW +# ------------------------------------------------------------------- + +# --------------------------------------------------- +# Wolfgang's machine (Linux): +# --------------------------------------------------- +# Not needed (default include path on linux) + +# --------------------------------------------------- +# Wolfgang's machine (Windows): +# --------------------------------------------------- +# LIBS += -L"C:/FFTW32" + +# =================================================================== +# Libraries +# =================================================================== + +# ------------------------------------------------------------------- +# Libraries for libPLN +# ------------------------------------------------------------------- + +# --------------------------------------------------- +# Wolfgang's machine (Linux): +# --------------------------------------------------- +# LIBS += -lPLN_Examples -lPLN -lfftw3 + +# --------------------------------------------------- +# Wolfgang's machine (Windows): +# --------------------------------------------------- +# LIBS += -lPLN_Examples -lPLN -lfftw3-3 diff --git a/src/Software/ClockServo/IClockServo.cc b/src/Software/ClockServo/IClockServo.cc new file mode 100644 index 0000000..591286d --- /dev/null +++ b/src/Software/ClockServo/IClockServo.cc @@ -0,0 +1,477 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "IClockServo.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const IClockServo::ClockServoState& o) +{ + switch(o) + { + case IClockServo::ClockServoState::DISABLED: os << "Disabled"; break; + case IClockServo::ClockServoState::INIT: os << "Init"; break; + case IClockServo::ClockServoState::SYNTONIZE_INIT: os << "Syntonize (Init)"; break; + case IClockServo::ClockServoState::SYNTONIZE_START: os << "Syntonize (Start)"; break; + case IClockServo::ClockServoState::SYNTONIZE: os << "Syntonize"; break; + case IClockServo::ClockServoState::JUMP: os << "Jump"; break; + case IClockServo::ClockServoState::SCALE: os << "Scaling"; break; + + default: os << "Unknown"; break; + } + + return os; +} + + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +IClockServo::CalcMaxFrequEstCnt() +{ + if( SyncInterval == SIMTIME_ZERO ) + { + throw cRuntimeError( "Can't calculate maximum frequency estimation count if SyncInterval is not set." ); + } + + MaxFreqEstCnt = std::min( (1 << FrequEstShift)-1, static_cast(MaxFrequEstInterval/SyncInterval) ); + MaxFreqEstCnt = std::max( MaxFreqEstCnt, 1 ); + + if( EnableDebugOutput ) + { + EV << "MaxFreqEstCnt: " << MaxFreqEstCnt << endl; + } +} + + +// ------------------------------------------------------ +// Set servo state +// ------------------------------------------------------ +void +IClockServo::SetServoState( ClockServoState NewServoState ) +{ + if( ServoState != NewServoState ) + { + emit( ClockServoState_SigId, static_cast(NewServoState) ); + } + + ServoState = NewServoState; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +void +IClockServo::ParseParameters() +{ + EnableSyntonize = par( "EnableSyntonize" ).boolValue(); + EnableTimeJump = par( "EnableTimeJump" ).boolValue(); + EnableSynchronize = par( "EnableSynchronize" ).boolValue(); + FrequEstShift = par( "FrequEstShift" ).longValue(); + MaxFrequEstInterval = par( "MaxFrequEstInterval" ).doubleValue(); + OffsetThreshForReset = par( "OffsetThreshForReset" ).doubleValue(); + EnableDebugOutput = par( "EnableDebugOutput" ).boolValue(); +} + +void +IClockServo::RegisterSignals() +{ + ClockServoState_SigId = registerSignal( "ClockServoState" ); + SyncInterval_SigId = registerSignal( "SyncInterval" ); + OffsetFromMaster_SigId = registerSignal( "OffsetFromMaster" ); + + Decision_EnableJump_SigId = registerSignal( "Decision_EnableJump" ); + Decision_JumpDelta_SigId = registerSignal( "Decision_JumpDelta" ); + Decision_EnableScale_SigId = registerSignal( "Decision_EnableScale" ); + Decision_ScaleFactor_ppb_SigId = registerSignal( "Decision_ScaleFactor_ppb" ); + + WATCH(ScaleFactor_LowerBound_ppb); + WATCH(ScaleFactor_UpperBound_ppb); + WATCH(ServoState); + WATCH(SyncInterval); + WATCH(SampleDec.EnableJump); + WATCH(SampleDec.Delta); + WATCH(SampleDec.EnableScale); + WATCH(SampleDec.ScaleFactor_ppb); +} + +void +IClockServo::InitInternalState() +{ + Disable(); +} + +void +IClockServo::InitSignals() +{ + emit( ClockServoState_SigId, static_cast(ClockServoState::DISABLED) ); +} + +// ------------------------------------------------------ +// Handle messages +// ------------------------------------------------------ +void +IClockServo::handleMessage(cMessage *pMsg) +{ +} + +// ------------------------------------------------------ +// Finish +// ------------------------------------------------------ +void +IClockServo::finish() +{ +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +IClockServo::IClockServo() +{ + ScaleFactor_LowerBound_ppb = 0; + ScaleFactor_UpperBound_ppb = 0; + FrequEstShift = 0; + EnableSyntonize = false; + EnableTimeJump = false; + EnableSynchronize = false; + OffsetThreshForReset = SIMTIME_ZERO; + SyncInterval = SIMTIME_ZERO; + ServoState = ClockServoState::DISABLED; + + Reset(); +} + +// ------------------------------------------------------ +// Desctructor +// ------------------------------------------------------ +IClockServo::~IClockServo() +{ +} + +// ------------------------------------------------------ +// Sample API +// ------------------------------------------------------ +void +IClockServo::Reset() +{ + FreqEstCnt = 0; + Remote[0] = SIMTIME_ZERO; + Remote[1] = SIMTIME_ZERO; + Local [0] = SIMTIME_ZERO; + Local [1] = SIMTIME_ZERO; + + SampleDec.EnableJump = false; + SampleDec.Delta = SIMTIME_ZERO; + SampleDec.EnableScale = false; + SampleDec.ScaleFactor_ppb = 0; +} + +void +IClockServo::SetSyncInterval( simtime_t SyncInterval ) +{ + EnterModuleSilent(); + + if( this->SyncInterval != SyncInterval ) + { + this->SyncInterval = SyncInterval; + + emit( SyncInterval_SigId, this->SyncInterval ); + } + + LeaveModule(); +} + +void +IClockServo::SetScaleFactor_ppb( int64_t ScaleFactor_ppb ) +{ + EnterModuleSilent(); + + if( ServoState != ClockServoState::DISABLED ) + { + throw cRuntimeError( "Can't configure already enabled clock servo." ); + } + + SampleDec.ScaleFactor_ppb = ScaleFactor_ppb; + + LeaveModule(); +} + +void +IClockServo::SetScaleFactorBounds( int64_t ScaleFactor_LowerBound_ppb, int64_t ScaleFactor_UpperBound_ppb ) +{ + EnterModuleSilent(); + + if( ServoState != ClockServoState::DISABLED ) + { + throw cRuntimeError( "Can't configure already enabled clock servo." ); + } + + this->ScaleFactor_LowerBound_ppb = ScaleFactor_LowerBound_ppb; + this->ScaleFactor_UpperBound_ppb = ScaleFactor_UpperBound_ppb; + + LeaveModule(); +} + +void +IClockServo::Enable() +{ + EnterModuleSilent(); + + if( ServoState == ClockServoState::DISABLED ) + { + SetServoState( ClockServoState::INIT ); + } + + LeaveModule(); +} + +void +IClockServo::Disable() +{ + EnterModuleSilent(); + + SetServoState( ClockServoState::DISABLED ); + Reset(); + + LeaveModule(); +} + +bool +IClockServo::IsEnabled() +{ + EnterModuleSilent(); + + if( ServoState == ClockServoState::DISABLED ) + { + return false; + } + else + { + return true; + } + + LeaveModule(); +} + +SampleDecision_t +IClockServo::Sample( simtime_t offsetFromMaster, simtime_t Ingress ) +{ + EnterModuleSilent(); + + emit( OffsetFromMaster_SigId, offsetFromMaster ); + + if( EnableDebugOutput ) + { + EV << "Sampling, current state: " << ServoState << endl; + } + + switch( ServoState ) + { + case ClockServoState::DISABLED: + { + // Nothing to do while disabled + break; + } + + case ClockServoState::INIT: + { + if( EnableSyntonize ) + { + SetServoState( ClockServoState::SYNTONIZE_INIT ); + } + else if( EnableTimeJump ) + { + SetServoState( ClockServoState::JUMP ); + } + else if( EnableSynchronize ) + { + SetServoState( ClockServoState::SCALE ); + } + break; + } + + case ClockServoState::SYNTONIZE_INIT: + { + SampleDec.EnableJump = false; + SampleDec.Delta = 0; + SampleDec.EnableScale = true; + SampleDec.ScaleFactor_ppb = 0; + + FreqEstCnt = 0; + + CalcMaxFrequEstCnt(); + + SetServoState( ClockServoState::SYNTONIZE_START ); + break; + } + + case ClockServoState::SYNTONIZE_START: + { + if( FreqEstCnt >= MaxFreqEstCnt ) + { + Remote[0] = offsetFromMaster + Ingress; + Local [0] = Ingress; + FreqEstCnt = 1; + + if( EnableDebugOutput ) + { + EV << "Starting syntonizing" << endl; + EV << "Estimated remote time: " << Remote[0] << endl; + EV << "Corresponding local time: " << Local [0] << endl; + } + + SetServoState( ClockServoState::SYNTONIZE ); + } + else + { + FreqEstCnt ++; + } + break; + } + + case ClockServoState::SYNTONIZE: + { + Remote[1] = offsetFromMaster + Ingress; + Local [1] = Ingress; + + if( FreqEstCnt >= MaxFreqEstCnt ) + { + simtime_t LocalDiff = Local [1] - Local [0]; + simtime_t RemoteDiff = Remote[1] - Remote[0]; + + int64_t ScaleFactor_ppb = (( (1E9L+SampleDec.ScaleFactor_ppb) * LocalDiff.dbl()) / RemoteDiff.dbl() ) - 1E9L; + + if( EnableDebugOutput ) + { + EV << "Finshed syntonizing" << endl; + EV << "Estimated remote time: " << Remote[1] << endl; + EV << "Corresponding local time: " << Local [1] << endl; + EV << endl; + EV << "LocalDiff: " << LocalDiff << endl; + EV << "RemoteDiff: " << RemoteDiff << endl; + EV << ""; + EV << "ScaleFactor_ppb: " << ScaleFactor_ppb << endl; + } + + if( ScaleFactor_ppb > ScaleFactor_UpperBound_ppb ) + ScaleFactor_ppb = ScaleFactor_UpperBound_ppb; + if( ScaleFactor_ppb < ScaleFactor_LowerBound_ppb ) + ScaleFactor_ppb = ScaleFactor_LowerBound_ppb; + + SampleDec.EnableJump = false; + SampleDec.Delta = 0; + SampleDec.EnableScale = true; + SampleDec.ScaleFactor_ppb = ScaleFactor_ppb; + + if( EnableSynchronize ) + { + if( EnableTimeJump ) + { + SetServoState( ClockServoState::JUMP ); + } + else + { + SetServoState( ClockServoState::SCALE ); + } + } + else + { + SetServoState( ClockServoState::SYNTONIZE_START ); + } + } + else + { + FreqEstCnt ++; + } + break; + } + + case ClockServoState::JUMP: + { + SampleDec.EnableScale = false; + SampleDec.EnableJump = true; + SampleDec.Delta = -offsetFromMaster; + + if( EnableSynchronize ) + { + SetServoState( ClockServoState::SCALE ); + } + break; + } + + case ClockServoState::SCALE: + { + // Check if we are close enough to the master for scaling the clock + // Reset state machine otherwise + if(( fabs(offsetFromMaster) <= OffsetThreshForReset ) || (!EnableTimeJump)) + { + SampleInternal( -offsetFromMaster, Ingress ); + } + else + { + Reset(); + SetServoState( ClockServoState::INIT ); + } + + break; + } + } + + emit( Decision_EnableJump_SigId, SampleDec.EnableJump ); + emit( Decision_JumpDelta_SigId, SampleDec.Delta ); + emit( Decision_EnableScale_SigId, SampleDec.EnableScale ); + emit( Decision_ScaleFactor_ppb_SigId, (long int)SampleDec.ScaleFactor_ppb ); + + LeaveModule(); + + return SampleDec; +} diff --git a/src/Software/ClockServo/IClockServo.h b/src/Software/ClockServo/IClockServo.h new file mode 100644 index 0000000..3daf8d0 --- /dev/null +++ b/src/Software/ClockServo/IClockServo.h @@ -0,0 +1,145 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_ICLOCK_SERVO_H_ +#define LIBPTP_ICLOCK_SERVO_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "ScheduleClock.h" +#include "ModuleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ------------------------------------------------------------ +// Types +// ------------------------------------------------------------ + +struct SampleDecision_t +{ + bool EnableJump; + simtime_t Delta; + bool EnableScale; + int64_t ScaleFactor_ppb; +}; + +// ====================================================== +// Declarations +// ====================================================== + +class IClockServo: public cModuleInitBase +{ + public: + + // Type definitions + enum class ClockServoState + { + DISABLED = 0, + INIT = 10, + SYNTONIZE_INIT = 20, + SYNTONIZE_START = 21, + SYNTONIZE = 22, + JUMP = 30, + SCALE = 40, + }; + + private: + + // Internal functions + void CalcMaxFrequEstCnt(); + + protected: + + // Resources + + // Configuration + int64_t ScaleFactor_LowerBound_ppb; + int64_t ScaleFactor_UpperBound_ppb; + int FrequEstShift; + bool EnableSyntonize; + bool EnableTimeJump; + bool EnableSynchronize; + simtime_t MaxFrequEstInterval; + simtime_t OffsetThreshForReset; + + // Debug config + bool EnableDebugOutput; + + // Servo state handling + int MaxFreqEstCnt; + int FreqEstCnt; + ClockServoState ServoState; + simtime_t Remote[2]; + simtime_t Local[2]; + simtime_t SyncInterval; + SampleDecision_t SampleDec; + + // Signals + simsignal_t ClockServoState_SigId; + simsignal_t SyncInterval_SigId; + simsignal_t OffsetFromMaster_SigId; + + simsignal_t Decision_EnableJump_SigId; + simsignal_t Decision_JumpDelta_SigId; + simsignal_t Decision_EnableScale_SigId; + simsignal_t Decision_ScaleFactor_ppb_SigId; + + // API for sub-classes + virtual void SetServoState( ClockServoState NewServoState ); + virtual void SampleInternal( simtime_t offsetFromMaster, simtime_t Ingress ) = 0; + virtual void Reset(); + + // OMNeT API + virtual void handleMessage(cMessage *msg); + virtual void finish(); + + // Init API + void ParseParameters(); + void RegisterSignals(); + void InitInternalState(); + void InitSignals(); + + public: + + // Constructor + IClockServo(); + + // Destructor + ~IClockServo(); + + // Basic clock servo API + void Enable(); + void Disable(); + bool IsEnabled(); + SampleDecision_t Sample( simtime_t offsetFromMaster, simtime_t Ingress ); + virtual void SetSyncInterval( simtime_t SyncInterval ); + virtual void SetScaleFactor_ppb( int64_t ScaleFactor_ppb ); + virtual void SetScaleFactorBounds( int64_t ScaleFactor_LowerBound_ppb, int64_t ScaleFactor_UpperBound_ppb ); +}; + +#endif diff --git a/src/Software/ClockServo/IClockServo.ned b/src/Software/ClockServo/IClockServo.ned new file mode 100644 index 0000000..5a39e6d --- /dev/null +++ b/src/Software/ClockServo/IClockServo.ned @@ -0,0 +1,40 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Software.ClockServo; + +moduleinterface IClockServo +{ + parameters: + @display("i=PTP/Components/ClockServo/ClockServo"); + + // Control configuration + bool EnableSyntonize; + bool EnableTimeJump; + bool EnableSynchronize; + int FrequEstShift; + double MaxFrequEstInterval @unit(s); + double OffsetThreshForReset @unit(s); + + // Debug + bool EnableDebugOutput; +} diff --git a/src/Software/ClockServo/Internal_ClockServo.ned b/src/Software/ClockServo/Internal_ClockServo.ned new file mode 100644 index 0000000..194883d --- /dev/null +++ b/src/Software/ClockServo/Internal_ClockServo.ned @@ -0,0 +1,66 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Software.ClockServo; + +simple Internal_ClockServo like IClockServo +{ + parameters: + + @display("i=PTP/Components/InternalModule/InternalModule"); + + // Control configuration + bool EnableSyntonize = default( false ); + bool EnableTimeJump = default( true ); + bool EnableSynchronize = default( true ); + int FrequEstShift = default( 4 ); + double MaxFrequEstInterval @unit(s) = default( 10s ); + double OffsetThreshForReset @unit(s) = default( 100us ); + + // Debug + bool EnableDebugOutput = default( false ); + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + @signal[ClockServoState] (type=long); + @signal[SyncInterval] (type=simtime_t); + @signal[OffsetFromMaster] (type=double); + + @signal[Decision_EnableJump] (type=bool); + @signal[Decision_JumpDelta] (type=simtime_t); + @signal[Decision_EnableScale] (type=bool); + @signal[Decision_ScaleFactor_ppb] (type=long); + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- + @statistic[ClockServoState](enum="DISABLED=0,INIT=10,SYNTONIZE_INIT=20,SYNTONIZE_START=21,SYNTONIZE=22,JUMP=30,SCALE=40";record=vector?); + @statistic[SyncInterval](record=vector?); + @statistic[OffsetFromMaster](record=vector?); + + @statistic[Decision_EnableJump] (record=vector?); + @statistic[Decision_JumpDelta] (record=vector?); + @statistic[Decision_EnableScale] (record=vector?); + @statistic[Decision_ScaleFactor_ppb](record=vector?); +} + diff --git a/src/Software/ClockServo/PI_ClockServo.ned b/src/Software/ClockServo/PI_ClockServo.ned new file mode 100644 index 0000000..ddfc8a9 --- /dev/null +++ b/src/Software/ClockServo/PI_ClockServo.ned @@ -0,0 +1,71 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Software.ClockServo; + +simple PI_ClockServo extends Internal_ClockServo +{ + parameters: + + @display("i=PTP/Components/ClockServo/PI_ClockServo"); + @class(cPI_ClockServo); + + // Control configuration + string KP_ParameterMode = default( "LIBPTP" ); + double KP_Const = default( 0.0 ); + double KP_LinuxPTP_Scale = default( 0.7 ); + double KP_LinuxPTP_Exp = default( -0.3 ); + double KP_LinuxPTP_MaxNorm = default( 0.7 ); + double KP_LibPTP_Scale = default( 0.4 ); + double KP_LibPTP_Base = default( 0.57 ); + double KP_LibPTP_Min = default( 0 ); + double KP_LibPTP_Max = default( 10 ); + + string KI_ParameterMode = default( "LIBPTP" ); + double KI_Const = default( 0.0 ); + double KI_LinuxPTP_Scale = default( 0.3 ); + double KI_LinuxPTP_Exp = default( 0.4 ); + double KI_LinuxPTP_MaxNorm = default( 0.3 ); + double KI_LibPTP_Scale = default( 0.0012 ); + double KI_LibPTP_Base = default( 1.7 ); + double KI_LibPTP_Min = default( 0.0005 ); + double KI_LibPTP_Max = default( 0.0070 ); + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + @signal[P](type=double); + @signal[I](type=double); + @signal[Integral](type=double); + @signal[KP](type=double); + @signal[KI](type=double); + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- + @statistic[P](record=vector?); + @statistic[I](record=vector?); + @statistic[Integral](record=vector?); + @statistic[KP](record=vector?); + @statistic[KI](record=vector?); +} + diff --git a/src/Software/ClockServo/PI_ClockServo/PI_ClockServo.cc b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo.cc new file mode 100644 index 0000000..9e1fdc3 --- /dev/null +++ b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo.cc @@ -0,0 +1,370 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PI_ClockServo.h" +#include "PI_ClockServo_ParameterParser.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(cPI_ClockServo); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Configuration +// ------------------------------------------------------ + +double +cPI_ClockServo::CalcK( simtime_t ControlInterval, KConfig_t Config ) +{ + double K; + double Limit; + double Interval = ControlInterval.dbl(); + + if( ControlInterval == SIMTIME_ZERO ) + { + throw cRuntimeError( "ControlInterval can't be 0." ); + } + + switch( Config.ParameterMode ) + { + case PI_PARAMETER_MODE_CONSTANT: + { + K = Config.Const; + break; + } + + case PI_PARAMETER_MODE_LINUX_PTP: + { + K = Config.LinuxPTP_Config.Scale * pow( Interval, Config.LinuxPTP_Config.Exp ); + Limit = Config.LinuxPTP_Config.MaxNorm / Interval; + + K = std::min( K, Limit ); // Enforce limit + break; + } + + case PI_PARAMETER_MODE_LIBPTP: + { + double SyncExp = log2( Interval ); + + K = Config.LibPTP_Config.Scale * pow( Config.LibPTP_Config.Base, SyncExp ); + + K = std::max( K, Config.LibPTP_Config.Min ); + K = std::min( K, Config.LibPTP_Config.Max ); + break; + } + } + + return K; +} + +// ------------------------------------------------------ +// Print config +// ------------------------------------------------------ +void +cPI_ClockServo::PrintConfig() +{ + EV << endl; + EV << "---------------------------" << endl; + EV << "ClockServo configuration:" << endl; + EV << "---------------------------" << endl; + EV << endl; + + EV << "Proportional config: " << endl; + EV << "ParameterMode: " << KP_Config.ParameterMode << endl; + EV << endl; + EV << "Const: " << KP_Config.Const << endl; + EV << endl; + EV << "LinuxPTP Config:" << endl; + EV << "Scale: " << KP_Config.LinuxPTP_Config.Scale << endl; + EV << "Exp: " << KP_Config.LinuxPTP_Config.Exp << endl; + EV << "MaxNorm: " << KP_Config.LinuxPTP_Config.MaxNorm << endl; + EV << endl; + EV << "LibPTP Config:" << endl; + EV << "Scale: " << KP_Config.LibPTP_Config.Scale << endl; + EV << "Base: " << KP_Config.LibPTP_Config.Base << endl; + EV << "Min: " << KP_Config.LibPTP_Config.Min << endl; + EV << "Max: " << KP_Config.LibPTP_Config.Max << endl; + + EV << "Integral config: " << endl; + EV << "ParameterMode: " << KI_Config.ParameterMode << endl; + EV << endl; + EV << "Const: " << KI_Config.Const << endl; + EV << endl; + EV << "LinuxPTP Config:" << endl; + EV << "Scale: " << KI_Config.LinuxPTP_Config.Scale << endl; + EV << "Exp: " << KI_Config.LinuxPTP_Config.Exp << endl; + EV << "MaxNorm: " << KI_Config.LinuxPTP_Config.MaxNorm << endl; + EV << endl; + EV << "Scale: " << KI_Config.LibPTP_Config.Scale << endl; + EV << "Base: " << KI_Config.LibPTP_Config.Base << endl; + EV << "Min: " << KI_Config.LibPTP_Config.Min << endl; + EV << "Max: " << KI_Config.LibPTP_Config.Max << endl; + + EV << "ScaleFactor UpperBound [ppb]: " << ScaleFactor_UpperBound_ppb << endl; + EV << "ScaleFactor LowerBound [ppb]: " << ScaleFactor_LowerBound_ppb << endl; + EV << endl; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +cPI_ClockServo::ParseParameters() +{ + IClockServo::ParseParameters(); + + KP_Config.ParameterMode = cPI_ClockServo_ParameterParser::ParseParameterMode( par( "KP_ParameterMode" ).stringValue() ); + KP_Config.Const = par( "KP_Const" ).doubleValue(); + + KP_Config.LinuxPTP_Config.Scale = par( "KP_LinuxPTP_Scale" ).doubleValue(); + KP_Config.LinuxPTP_Config.Exp = par( "KP_LinuxPTP_Exp" ).doubleValue(); + KP_Config.LinuxPTP_Config.MaxNorm = par( "KP_LinuxPTP_MaxNorm" ).doubleValue(); + + KP_Config.LibPTP_Config.Scale = par( "KP_LibPTP_Scale" ).doubleValue(); + KP_Config.LibPTP_Config.Base = par( "KP_LibPTP_Base" ).doubleValue(); + KP_Config.LibPTP_Config.Min = par( "KP_LibPTP_Min" ).doubleValue(); + KP_Config.LibPTP_Config.Max = par( "KP_LibPTP_Max" ).doubleValue(); + + KI_Config.ParameterMode = cPI_ClockServo_ParameterParser::ParseParameterMode( par( "KI_ParameterMode" ).stringValue() ); + KI_Config.Const = par( "KI_Const" ).doubleValue(); + + KI_Config.LinuxPTP_Config.Scale = par( "KI_LinuxPTP_Scale" ).doubleValue(); + KI_Config.LinuxPTP_Config.Exp = par( "KI_LinuxPTP_Exp" ).doubleValue(); + KI_Config.LinuxPTP_Config.MaxNorm = par( "KI_LinuxPTP_MaxNorm" ).doubleValue(); + + KI_Config.LibPTP_Config.Scale = par( "KI_LibPTP_Scale" ).doubleValue(); + KI_Config.LibPTP_Config.Base = par( "KI_LibPTP_Base" ).doubleValue(); + KI_Config.LibPTP_Config.Min = par( "KI_LibPTP_Min" ).doubleValue(); + KI_Config.LibPTP_Config.Max = par( "KI_LibPTP_Max" ).doubleValue(); +} + +void +cPI_ClockServo::RegisterSignals() +{ + IClockServo::RegisterSignals(); + + P_SigId = registerSignal( "P" ); + I_SigId = registerSignal( "I" ); + Integral_SigId = registerSignal( "Integral" ); + KP_SigId = registerSignal( "KP" ); + KI_SigId = registerSignal( "KI" ); + + WATCH(KP); + WATCH(KI); + WATCH(Integral); +} + +void +cPI_ClockServo::InitSignals() +{ + emit( KP_SigId, KP ); + emit( KI_SigId, KI ); + + emit( Integral_SigId, Integral ); +} + +void +cPI_ClockServo::PrintDebugOutput() +{ + IClockServo::PrintDebugOutput(); + + if( EnableDebugOutput ) + { + PrintConfig(); + } +} + + +void +cPI_ClockServo::SetServoState( ClockServoState NewServoState ) +{ + IClockServo::SetServoState( NewServoState ); + + switch( NewServoState ) + { + default: // Do nothing + break; + + case ClockServoState::SCALE: Integral = SampleDec.ScaleFactor_ppb; + emit( Integral_SigId, Integral ); + break; + } +} + +// ------------------------------------------------------ +// Handle messages +// ------------------------------------------------------ +void +cPI_ClockServo::handleMessage(cMessage *pMsg) +{ +} + +// ------------------------------------------------------ +// Finish +// ------------------------------------------------------ +void +cPI_ClockServo::finish() +{ +} + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cPI_ClockServo::cPI_ClockServo() +{ + KP_Config.ParameterMode = PI_PARAMETER_MODE_CONSTANT; + KP_Config.Const = 0.0; + KP_Config.LinuxPTP_Config.Exp = 0.0; + KP_Config.LinuxPTP_Config.MaxNorm = 0.0; + KP_Config.LinuxPTP_Config.Scale = 0.0; + + KI_Config.ParameterMode = PI_PARAMETER_MODE_CONSTANT; + KI_Config.Const = 0.0; + KI_Config.LinuxPTP_Config.Exp = 0.0; + KI_Config.LinuxPTP_Config.MaxNorm = 0.0; + KI_Config.LinuxPTP_Config.Scale = 0.0; + + KP = 0.0; + KI = 0.0; + Integral = 0.0; +} + +// ------------------------------------------------------ +// Desctructor +// ------------------------------------------------------ +cPI_ClockServo::~cPI_ClockServo() +{ +} + +// ------------------------------------------------------ +// Sample API +// ------------------------------------------------------ +void +cPI_ClockServo::Reset() +{ + IClockServo::Reset(); + + Integral = 0.0; +} + +void +cPI_ClockServo::SetKP( double KP ) +{ + this->KP = KP; + emit( KP_SigId, KP ); +} + +void +cPI_ClockServo::SetKI( double KI ) +{ + this->KI = KI; + emit( KI_SigId, KI ); +} + +void +cPI_ClockServo::SetSyncInterval( simtime_t SyncInterval ) +{ + if( this->SyncInterval != SyncInterval ) + { + IClockServo::SetSyncInterval( SyncInterval ); + + SetKP( CalcK( SyncInterval, KP_Config ) ); + SetKI( CalcK( SyncInterval, KI_Config ) ); + } +} + +// ------------------------------------------------------ +// Servo internal sampling function +// ------------------------------------------------------ +void +cPI_ClockServo::SampleInternal( simtime_t offsetFromMaster, simtime_t Ingress ) +{ + int64_t ScaleFactor_ppb = 0; + int64_t Offset_ns = offsetFromMaster.inUnit( SIMTIME_NS ); + + double P = KP * Offset_ns; + double I = KI * Offset_ns; + + ScaleFactor_ppb = P + I + Integral; + + if( ScaleFactor_ppb > ScaleFactor_UpperBound_ppb ) + { + ScaleFactor_ppb = ScaleFactor_UpperBound_ppb; + } + else if( ScaleFactor_ppb < ScaleFactor_LowerBound_ppb ) + { + ScaleFactor_ppb = ScaleFactor_LowerBound_ppb; + } + else + { + Integral += I; + } + + SampleDec.EnableJump = false; + SampleDec.Delta = SIMTIME_ZERO; + SampleDec.EnableScale = true; + SampleDec.ScaleFactor_ppb = ScaleFactor_ppb; + + emit( P_SigId, P ); + emit( I_SigId, I ); + emit( Integral_SigId, Integral ); +} + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const cPI_ClockServo::ParameterMode_t& o ) +{ + switch( o ) + { + default: os << "Unknown parameter mode"; break; + case cPI_ClockServo::ParameterMode_t::PI_PARAMETER_MODE_CONSTANT: os << "Constant"; break; + case cPI_ClockServo::ParameterMode_t::PI_PARAMETER_MODE_LINUX_PTP: os << "LinuxPTP"; break; + case cPI_ClockServo::ParameterMode_t::PI_PARAMETER_MODE_LIBPTP: os << "LibPTP"; break; + } + + return os; +} diff --git a/src/Software/ClockServo/PI_ClockServo/PI_ClockServo.h b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo.h new file mode 100644 index 0000000..dbd5960 --- /dev/null +++ b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo.h @@ -0,0 +1,136 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PI_CLOCK_SERVO_H_ +#define LIBPTP_PI_CLOCK_SERVO_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "IClockServo.h" +#include "IClockEventSink.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cPI_ClockServo: public IClockServo +{ + public: + // Type definitions + enum ParameterMode_t + { + PI_PARAMETER_MODE_CONSTANT, + PI_PARAMETER_MODE_LINUX_PTP, + PI_PARAMETER_MODE_LIBPTP, + }; + + private: + + // Type definitions + struct LinuxPTP_Config_t + { + double Scale; + double Exp; + double MaxNorm; + }; + + struct LibPTP_Config_t + { + double Scale; + double Base; + double Min; + double Max; + }; + + struct KConfig_t + { + ParameterMode_t ParameterMode; + double Const; + LinuxPTP_Config_t LinuxPTP_Config; + LibPTP_Config_t LibPTP_Config; + }; + + // Resources + + // Configuration + KConfig_t KP_Config; + KConfig_t KI_Config; + double KP; + double KI; + double Integral; + + // Signals + simsignal_t P_SigId; + simsignal_t I_SigId; + simsignal_t Integral_SigId; + simsignal_t KP_SigId; + simsignal_t KI_SigId; + + // ------------------------------------------------------------ + // Private functions + // ------------------------------------------------------------ + + // Configuration + double CalcK( simtime_t ControlInterval, KConfig_t Config ); + + // Sample API + void PrintConfig(); + void Reset(); + void SetKP( double KP ); + void SetKI( double KI ); + void SetSyncInterval( simtime_t SyncInterval ); + void SampleInternal( simtime_t offsetFromMaster, simtime_t Ingress ); + + // Init API + void ParseParameters(); + void RegisterSignals(); + void InitSignals(); + void PrintDebugOutput(); + + protected: + + // State handling + virtual void SetServoState( ClockServoState NewServoState ); + + // OMNeT API + virtual void handleMessage(cMessage *msg); + virtual void finish(); + + public: + + // Constructor/Desctructor + cPI_ClockServo(); + ~cPI_ClockServo(); +}; + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& operator<<(std::ostream& os, const cPI_ClockServo::ParameterMode_t& o ); + +#endif diff --git a/src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.cc b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.cc new file mode 100644 index 0000000..68a6e25 --- /dev/null +++ b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.cc @@ -0,0 +1,62 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PI_ClockServo_ParameterParser.h" +#include "ParameterParser.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +static +ParseType ParameterModeParse[] = +{ + { cPI_ClockServo::ParameterMode_t::PI_PARAMETER_MODE_CONSTANT, "CONSTANT" }, + { cPI_ClockServo::ParameterMode_t::PI_PARAMETER_MODE_LINUX_PTP, "LINUX_PTP" }, + { cPI_ClockServo::ParameterMode_t::PI_PARAMETER_MODE_LIBPTP, "LIBPTP" }, +}; + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// TdGen type +// ------------------------------------------------------ + +cPI_ClockServo::ParameterMode_t +cPI_ClockServo_ParameterParser::ParseParameterMode(const char *Str) +{ + return Parse( ParameterModeParse, ArrayLen(ParameterModeParse), Str ); +} diff --git a/src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.h b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.h new file mode 100644 index 0000000..aac6a5f --- /dev/null +++ b/src/Software/ClockServo/PI_ClockServo/PI_ClockServo_ParameterParser.h @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PI_CLOCKSERVO_PARAMETER_PARSER_H_ +#define LIBPTP_PI_CLOCKSERVO_PARAMETER_PARSER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PI_ClockServo.h" + +// ====================================================== +// Types +// ====================================================== + +class cPI_ClockServo_ParameterParser +{ + public: + + static cPI_ClockServo::ParameterMode_t ParseParameterMode(const char *Str); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif + diff --git a/src/Software/PTP_EthernetMapping/PTP_Ctrl/PTP_Ctrl.msg b/src/Software/PTP_EthernetMapping/PTP_Ctrl/PTP_Ctrl.msg new file mode 100644 index 0000000..84ad2d0 --- /dev/null +++ b/src/Software/PTP_EthernetMapping/PTP_Ctrl/PTP_Ctrl.msg @@ -0,0 +1,38 @@ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +// ================================================= +// Make needed C++ stuff available +// ================================================= +cplusplus {{ +#include "Ieee802Ctrl_m.h" +}} + +// ================================================= +// Class announcements +// ================================================= +class noncobject Ieee802Ctrl; + +// ================================================= +// Type definitions +// ================================================= + +// ================================================= +// Message definitions +// ================================================= +class PTP_Ctrl extends Ieee802Ctrl { + int rxPort = -1; + int txPort = -1; +} diff --git a/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.cc b/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.cc new file mode 100644 index 0000000..664c4eb --- /dev/null +++ b/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.cc @@ -0,0 +1,171 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_EthernetMapping.h" + +#include "PTP_Ethernet.h" + +#include "PTP_Ctrl_m.h" +#include "PTPv2_m.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(PTP_EthernetMapping); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +void +PTP_EthernetMapping::AllocateResources() +{ + LowerLayerOutGateID = gate( "lowerLayerOut" )->getId(); + LowerLayerInGateID = gate( "lowerLayerIn" )->getId(); + UpperLayerOutGateID = gateBaseId( "upperLayerOut" ); + UpperLayerInGateID = gateBaseId( "upperLayerIn" ); + + GateSize = gateSize( "upperLayerIn" ); + + if( gateSize( "upperLayerIn" ) != gateSize( "upperLayerOut" ) ) + { + error( "Size of vector gates for upperLayerIn and upperLayerOut must match." ); + } +} + +void +PTP_EthernetMapping::ParseParameters() +{ + Enable = par( "Enable" ).boolValue(); +} + +// ------------------------------------------------------ +// Handle messages +// ------------------------------------------------------ +void +PTP_EthernetMapping::handleMessage(cMessage *msg) +{ + if( !Enable ) + { + delete msg; + return; + } + + if( msg->getArrivalGateId() == LowerLayerInGateID ) + { + PTP_Ctrl *pCtrl; + int GateID; + + pCtrl = check_and_cast(msg->removeControlInfo()); + GateID = pCtrl->getRxPort(); + + delete pCtrl; + + if + ( + ( GateID >= 0 ) && + ( GateID < GateSize ) + ) + { + send( msg, UpperLayerOutGateID + GateID ); + } + else + { + error( "Received frame with invalid gate ID" ); + } + } + else + { + PTP_Ctrl *pCtrl = new PTP_Ctrl; + PTPv2_Frame *pPtp = check_and_cast(msg); + tPtpMessageType MsgType = (tPtpMessageType) pPtp->getMessageType(); + int GateID; + + GateID = msg->getArrivalGateId() - UpperLayerInGateID; + + assert( GateID >= 0 ); + assert( GateID < GateSize ); + + pCtrl->setEtherType( PTP_ETH_TYPE ); + pCtrl->setTxPort( GateID ); + + switch( MsgType ) + { + case PTP_TYPE_PDELAY_REQ: + case PTP_TYPE_PDELAY_RESP: + case PTP_TYPE_PDELAY_RESP_FU: + { + pCtrl->setDest( PtpMcPDelayMAC ); + break; + } + default: + { + pCtrl->setDest( PtpMcMAC ); + break; + } + } + + msg->setControlInfo( pCtrl ); + send( msg, LowerLayerOutGateID ); + } +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +PTP_EthernetMapping::PTP_EthernetMapping() + : PtpMcMAC(PTP_ETH_MC_DEFAULT_MAC), PtpMcPDelayMAC(PTP_ETH_MC_PDELAY_MAC) +{ + Enable = true; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +PTP_EthernetMapping::SetMACAddress( MACAddress MAC ) +{ + par( "MAC_Address" ).setStringValue( MAC.str() ); +} diff --git a/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.h b/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.h new file mode 100644 index 0000000..74c2148 --- /dev/null +++ b/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.h @@ -0,0 +1,79 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_ETHERNET_MAPPING_H_ +#define LIBPTP_PTP_ETHERNET_MAPPING_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include + +#include "ModuleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_EthernetMapping : public cModuleInitBase +{ + private: + + // Resources + int LowerLayerOutGateID; + int LowerLayerInGateID; + int UpperLayerOutGateID; + int UpperLayerInGateID; + int GateSize; + + const MACAddress PtpMcMAC; + const MACAddress PtpMcPDelayMAC; + + // Config + bool Enable; + + // Init API + void AllocateResources(); + void ParseParameters(); + + protected: + + // OMNeT API + virtual void handleMessage(cMessage *msg); + + public: + + // Constructor + PTP_EthernetMapping(); + + // Setters + void SetMACAddress( MACAddress MAC ); +}; + +#endif diff --git a/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.ned b/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.ned new file mode 100644 index 0000000..4282623 --- /dev/null +++ b/src/Software/PTP_EthernetMapping/PTP_EthernetMapping.ned @@ -0,0 +1,55 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Package description +// ====================================================== + +package libptp.Software.PTP_EthernetMapping; + +// ====================================================== +// Imports +// ====================================================== + +// ====================================================== +// Channel declarations +// ====================================================== + +// ====================================================== +// Network declarations +// ====================================================== + +simple PTP_EthernetMapping +{ + parameters: + + @display("i=PTP/Components/PTP_EthernetMapping/PTP_EthernetMapping"); + + bool Enable = default(true); + + gates: + input upperLayerIn[]; + output upperLayerOut[]; + + input lowerLayerIn @labels(Ieee802Ctrl/up); + output lowerLayerOut @labels(Ieee802Ctrl/down); +} diff --git a/src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.cc b/src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.cc new file mode 100644 index 0000000..d16f686 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.cc @@ -0,0 +1,287 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_Stack.h" +#include "AppAnnounce.h" +#include "PTP_ForeignClockDS.h" + +#include "PTPv2_m.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Private methods +// ------------------------------------------------------ +PTPv2_AnnounceFrame * +cAppAnnounce::CreateAnnounceFrame() +{ + // Announce message specific stuff, 13.5.2 + + PTPv2_AnnounceFrame *pAnn = new PTPv2_AnnounceFrame; + + pAnn->setOriginTimestamp( cTimeStamp(0,0) ); + pAnn->setCurrentUtcOffset( pApp->timePropertiesDS.GetCurrentUtcOffset() ); + pAnn->setGrandmasterPriority1( pApp->parentDS.GetGrandmasterPriority1() ); + + pAnn->setGrandmasterClockQuality( pApp->parentDS.GrandmasterClockQuality() ); + pAnn->setGrandmasterPriority2( pApp->parentDS.GetGrandmasterPriority2() ); + pAnn->setGrandmasterIdentity( pApp->parentDS.GetGrandmasterIdentity() ); + + pAnn->setGrandMasterModuleID( pApp->parentDS.GetGrandMasterModuleID() ); + + pAnn->setStepsRemoved( pApp->currentDS.GetStepsRemoved() ); + + pAnn->setTimeSource( pApp->timePropertiesDS.GetTimeSource() ); + + // Generic PTP header, 13.3.2 + pAnn->setTransportSpecific(0); + pAnn->setMessageType(PTP_TYPE_ANNOUNCE); + pAnn->setReserved_0(0); + pAnn->setVersionPTP(pPort->PortDS().GetVersionNumber()); + pAnn->setMessageLength(PTP_MSG_ANNOUNCE_LEN); + pAnn->setDomainNumber(pApp->defaultDS.GetDomainNumber()); + pAnn->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pAnn->getFlagField(); + + FlagField.alternateMasterFlag = false; // Alternate master currently not supported + FlagField.twoStepFlag = false; + FlagField.unicastFlag = false; // Unicast is currently not supported + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.reserved = false; + FlagField.leap59 = pApp->timePropertiesDS.GetLeap59(); + FlagField.leap61 = pApp->timePropertiesDS.GetLeap61(); + FlagField.currentUtcOffsetValid = pApp->timePropertiesDS.GetCurrentUtcOffsetValid(); + FlagField.ptpTimescale = pApp->timePropertiesDS.GetPtpTimescale(); + FlagField.timeTraceable = pApp->timePropertiesDS.GetTimeTraceable(); + FlagField.frequencyTraceable = pApp->timePropertiesDS.GetFrequencyTraceable(); + + pAnn->setCorrectionField( cTimeInterval() ); // Table 21 + + for( unsigned int i = 0; i < pAnn->getReserved_2ArraySize(); i++ ) + { + pAnn->setReserved_2( i, 0); + } + + pAnn->setSourcePortIdentity( pPort->PortDS().PortIdentity() ); + pAnn->setSequenceId( DrawSequId() ); + pAnn->setControlField( PTP_MSG_CTRL_OTHERS ); + pAnn->setLogMessageInterval( pPort->PortDS().GetLogAnnounceInterval() ); + + // OMNeT specific stuff + pAnn->setByteLength( PTP_MSG_ANNOUNCE_LEN ); + + return pAnn; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +cAppAnnounce::RegisterSignals() +{ + cPortService::RegisterSignals(); + + AnnounceRcvd_SigId = pPort->RegisterDynamicSignal( "AnnounceRcvd" ); + ErbestModuleID_SigId = pPort->RegisterDynamicSignal( "ErbestModuleID" ); +} + +void +cAppAnnounce::InitSignals() +{ + cPortService::InitSignals(); +} + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cAppAnnounce::cAppAnnounce() + : cPortService() +{ +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cAppAnnounce::StartListening() +{ + StartTimeout(); +} + +void +cAppAnnounce::StopListening() +{ + StopTimeout(); +} + +void +cAppAnnounce::HandleIntervalEvent() +{ + PTPv2_AnnounceFrame *pAnn = CreateAnnounceFrame(); + + pPort->IssueFrame(pAnn); +} + +void +cAppAnnounce::HandleTimeoutEvent() +{ + StopTimeout(); + + pPort->HandleEvent( PORT_EVENT_ANNOUNCE_RCV_TIMEOUT ); +} + +void +cAppAnnounce::HandleMsg( PTPv2_Frame *pPtpFrame ) +{ + portState_t PortState; + PTPv2_AnnounceFrame *pAnn = check_and_cast(pPtpFrame); + + PortState = pPort->PortDS().GetPortState(); + + // Re-check message before sending + assert( pAnn->getGrandMasterModuleID() >= 0 ); + + pParentModule->emit( AnnounceRcvd_SigId, pAnn->getGrandMasterModuleID() ); + + if + ( + ( PortState == PORT_STATE_INITIALIZING ) || + ( PortState == PORT_STATE_DISABLED ) + ) + { + return; + } + + if( PortState == PORT_STATE_FAULTY ) + { + // Optional implementation specific handling of announce message would be possible here + return; + } + + // Restart timeout if in a listening state + switch( PortState ) + { + case PORT_STATE_LISTENING: + case PORT_STATE_PASSIVE: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + { + UpdateTimeout(); + break; + } + default: + break; + } + + // if in SLAVE-state && sender is current master + if + ( + ( PortState == PORT_STATE_SLAVE ) && + ( pApp->parentDS.GetGrandmasterIdentity() == pAnn->getSourcePortIdentity().ClockIdentity() ) + ) + { + // Update data sets as specified in Table 16 + pApp->currentDS.SetStepsRemoved ( pAnn->getStepsRemoved() + 1 ); + + pApp->parentDS.ParentPortIdentity() = pAnn->getSourcePortIdentity(); + pApp->parentDS.SetGrandmasterIdentity ( pAnn->getGrandmasterIdentity() ); + pApp->parentDS.GrandmasterClockQuality() = pAnn->getGrandmasterClockQuality(); + pApp->parentDS.SetGrandmasterPriority1 ( pAnn->getGrandmasterPriority1() ); + pApp->parentDS.SetGrandmasterPriority2 ( pAnn->getGrandmasterPriority2() ); + + pApp->timePropertiesDS.SetCurrentUtcOffset ( pAnn->getCurrentUtcOffset() ); + pApp->timePropertiesDS.SetCurrentUtcOffsetValid ( pAnn->getFlagField().currentUtcOffsetValid ); + pApp->timePropertiesDS.SetLeap59 ( pAnn->getFlagField().leap59 ); + pApp->timePropertiesDS.SetLeap61 ( pAnn->getFlagField().leap61 ); + pApp->timePropertiesDS.SetTimeTraceable ( pAnn->getFlagField().timeTraceable ); + pApp->timePropertiesDS.SetFrequencyTraceable ( pAnn->getFlagField().frequencyTraceable ); + pApp->timePropertiesDS.SetPtpTimescale ( pAnn->getFlagField().ptpTimescale ); + pApp->timePropertiesDS.SetTimeSource ( (timeSource_t) pAnn->getTimeSource() ); + + // Remark: portDS.portState is calculated at next state decision interval + + // TODO: This is a workaround to avoid the foreignMaster dataset to dry out + cForeignClockDS ForeignClockDS( pAnn ); + ForeignClockDS.ReceiverPortIdentity() = pPort->PortDS().PortIdentity(); + + pPort->ForeignMasterDS().PushForeignClock( ForeignClockDS, pAnn->getIngressTimeStamp().GetTime() ); + + pParentModule->emit( ErbestModuleID_SigId, pPort->ForeignMasterDS().GetErbest().GetModuleID() ); + + } + else + { + // if known sender: increment foreignMasterDS + // else (new sender): try to add to foreignMasterDS + + cForeignClockDS ForeignClockDS( pAnn ); + ForeignClockDS.ReceiverPortIdentity() = pPort->PortDS().PortIdentity(); + + pPort->ForeignMasterDS().PushForeignClock( ForeignClockDS, pAnn->getIngressTimeStamp().GetTime() ); + + pParentModule->emit( ErbestModuleID_SigId, pPort->ForeignMasterDS().GetErbest().GetModuleID() ); + } +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ diff --git a/src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.h b/src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.h new file mode 100644 index 0000000..654908e --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Announce/AppAnnounce.h @@ -0,0 +1,79 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_APP_ANNOUNCE_H_ +#define LIBPTP_APP_ANNOUNCE_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP.h" +#include "PortService.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cAppAnnounce : public cPortService +{ + private: + + // Signals for statistics + simsignal_t AnnounceRcvd_SigId; + simsignal_t ErbestModuleID_SigId; + + PTPv2_AnnounceFrame *CreateAnnounceFrame(); + + // Init API + void RegisterSignals(); + void InitSignals(); + + protected: + + public: + + // Constructors + cAppAnnounce(); + + // Destructor + + // Setters + + // Getters + + // API functions + void StartListening(); + void StopListening(); + void HandleMsg( PTPv2_Frame *pPtpFrame ); + + void HandleIntervalEvent(); + void HandleTimeoutEvent(); +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/AppService.cc b/src/Software/PTP_Stack/AppServices/AppService.cc new file mode 100644 index 0000000..e285716 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/AppService.cc @@ -0,0 +1,76 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "AppService.h" + +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cAppService::cAppService() + : cBasicService() +{ +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cAppService::~cAppService() +{ +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ diff --git a/src/Software/PTP_Stack/AppServices/AppService.h b/src/Software/PTP_Stack/AppServices/AppService.h new file mode 100644 index 0000000..bad7778 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/AppService.h @@ -0,0 +1,63 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_APP_SERVICE_H_ +#define LIBPTP_APP_SERVICE_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "BasicService.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cAppService : public cBasicService +{ + private: + + protected: + + public: + + // Constructors/Destructor + cAppService(); + virtual ~cAppService(); + + // Setters + + // Getters + + // API functions + + // Clock API +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/BasicService.cc b/src/Software/PTP_Stack/AppServices/BasicService.cc new file mode 100644 index 0000000..4f929cd --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/BasicService.cc @@ -0,0 +1,197 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "BasicService.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +cBasicService::VerifyCopyable() +{ + if( IntervalState != TimerState::STOPPED ) + { + throw cRuntimeError( "It is not allowed to copy services with non-stopped timers." ); + } +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cBasicService::cBasicService() + : cSubmoduleInitBase() +{ + this->pApp = nullptr; + this->pClock = nullptr; + + this->Interval = SIMTIME_ZERO; + this->IntervalState = TimerState::STOPPED; + this->IntervalEvent = cClockEvent( static_cast(BasicServiceEvent::INTERVAL), 0, 0, 0, nullptr ); + this->ScheduledInterval = cScheduledClockEvent(); +} + +cBasicService::cBasicService( const cBasicService& other ) + : cSubmoduleInitBase( other ) +{ + VerifyCopyable(); + + this->pApp = other.pApp; + this->pClock = other.pClock; + + this->Interval = other.Interval; + this->IntervalState = other.IntervalState; + this->IntervalEvent = other.IntervalEvent; + this->ScheduledInterval = other.ScheduledInterval; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cBasicService::~cBasicService() +{ +} + +// ------------------------------------------------------ +// Initialization +// ------------------------------------------------------ +void +cBasicService::SetHierarchy( PTP_Stack *pApp, cScheduleClock *pClock ) +{ + this->pApp = pApp; + this->pClock = pClock; +} + +// ------------------------------------------------------ +// Clock API +// ------------------------------------------------------ +void +cBasicService::HandleClockEvent( cClockEvent& ClockEvent ) +{ + BasicServiceEvent Event = static_cast(ClockEvent.GetID1()); + + switch( Event ) + { + case BasicServiceEvent::INTERVAL: + { + HandleIntervalEvent(); + + ScheduledInterval = pClock->ScheduleRelativeEvent( Interval, this, IntervalEvent ); + break; + } + + default: + { + throw cRuntimeError( "Received unexpected clock event." ); + break; + } + } +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cBasicService::SetInterval( simtime_t Interval ) +{ + if( Interval == SIMTIME_ZERO ) + { + throw cRuntimeError( "PortService: Can't set service interval to 0" ); + } + + this->Interval = Interval; +} + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cBasicService::StartInterval() +{ + if( IntervalState == TimerState::STOPPED ) + { + if( Interval == SIMTIME_ZERO ) + { + throw cRuntimeError( "PortService: Can't start interval service with a value of 0" ); + } + + ScheduledInterval = pClock->ScheduleRelativeEvent( SIMTIME_ZERO, this, IntervalEvent ); + } + + IntervalState = TimerState::ACTIVE; +} + +void +cBasicService::StopInterval() +{ + if( IntervalState == TimerState::ACTIVE ) + { + pClock->CancelEvent( ScheduledInterval ); + } + + IntervalState = TimerState::STOPPED; +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +cBasicService& +cBasicService::operator=( const cBasicService& other ) +{ + VerifyCopyable(); + + cSubmoduleInitBase::operator=( other ); + + this->pApp = other.pApp; + this->pClock = other.pClock; + + this->Interval = other.Interval; + this->IntervalState = other.IntervalState; + this->IntervalEvent = other.IntervalEvent; + this->ScheduledInterval = other.ScheduledInterval; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/AppServices/BasicService.h b/src/Software/PTP_Stack/AppServices/BasicService.h new file mode 100644 index 0000000..0ab8551 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/BasicService.h @@ -0,0 +1,108 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_BASIC_SERVICE_H_ +#define LIBPTP_BASIC_SERVICE_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "SubmoduleInitBase.h" +#include "ScheduleClock.h" +#include "IClockEventSink.h" +#include "CallableSubmodule.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_Stack; + +// ====================================================== +// Declarations +// ====================================================== + +class cBasicService : public cSubmoduleInitBase, public IClockEventSink, public cCallableSubmodule +{ + private: + + protected: + + // Types + enum class BasicServiceEvent + { + INTERVAL = 0, + NEXT_FREE_ENTRY, + }; + + enum class TimerState + { + STOPPED, + ACTIVE + }; + + // Resources + PTP_Stack *pApp; + cScheduleClock *pClock; + cClockEvent IntervalEvent; + cScheduledClockEvent ScheduledInterval; + + // Configuration + simtime_t Interval; + + // Internal housekeeping + TimerState IntervalState; + + // Internal functions + virtual void VerifyCopyable(); + + public: + + // Constructors/Destructor + cBasicService(); + cBasicService( const cBasicService& other ); + virtual ~cBasicService(); + + // Setters + void SetInterval( simtime_t Interval ); + + // Getters + + // Initialization + void SetHierarchy( PTP_Stack *pApp, cScheduleClock *pClock ); + + // API functions + void StartInterval(); + void StopInterval(); + + // Clock API + void HandleClockEvent( cClockEvent& ClockEvent ); + + // Operators + cBasicService& operator=( const cBasicService& other ); + + // Interval handling + virtual void HandleIntervalEvent() = 0; +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/Delay/AppDelay.cc b/src/Software/PTP_Stack/AppServices/Delay/AppDelay.cc new file mode 100644 index 0000000..de6d0d5 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Delay/AppDelay.cc @@ -0,0 +1,441 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include +#include + +#include "PTP_Stack.h" +#include "AppDelay.h" +#include "PTP_Port.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Private methods +// ------------------------------------------------------ +PTPv2_Delay_ReqFrame * +cAppDelay::CreateDelayReqFrame() +{ + // Delay Req message specific stuff, 13.6.2 + + PTPv2_Delay_ReqFrame *pReq = new PTPv2_Delay_ReqFrame; + + // Precise timestamp will be filled in by MAC, thus it is set to 0 here + pReq->setOriginTimestamp( cTimeStamp(0,0) ); + + // Generic PTP header, 13.3.2 + pReq->setTransportSpecific(0); + pReq->setMessageType(PTP_TYPE_DELAY_REQ); + pReq->setReserved_0(0); + pReq->setVersionPTP(pPort->PortDS().GetVersionNumber()); + pReq->setMessageLength(PTP_MSG_DEL_REQ_LEN); + pReq->setDomainNumber(pApp->defaultDS.GetDomainNumber()); + pReq->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pReq->getFlagField(); + + FlagField.alternateMasterFlag = false; // Alternate master currently not supported + FlagField.twoStepFlag = false; + FlagField.unicastFlag = false; // Unicast is currently not supported + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.reserved = false; + FlagField.leap59 = pApp->timePropertiesDS.GetLeap59(); + FlagField.leap61 = pApp->timePropertiesDS.GetLeap61(); + FlagField.currentUtcOffsetValid = pApp->timePropertiesDS.GetCurrentUtcOffsetValid(); + FlagField.ptpTimescale = pApp->timePropertiesDS.GetPtpTimescale(); + FlagField.timeTraceable = pApp->timePropertiesDS.GetTimeTraceable(); + FlagField.frequencyTraceable = pApp->timePropertiesDS.GetFrequencyTraceable(); + + pReq->setCorrectionField( cTimeInterval() ); // Table 21 + + for( unsigned int i = 0; i < pReq->getReserved_2ArraySize(); i++ ) + { + pReq->setReserved_2( i, 0); + } + + pReq->setSourcePortIdentity( pPort->PortDS().PortIdentity() ); + pReq->setSequenceId( DrawSequId() ); + pReq->setControlField( PTP_MSG_CTRL_DEL_REQ ); + pReq->setLogMessageInterval( 0x7F ); // Table 24 + + // OMNeT specific stuff + pReq->setByteLength( PTP_MSG_DEL_REQ_LEN ); + + return pReq; +} + +PTPv2_Delay_RespFrame * +cAppDelay::CreateDelayRespFrame( PTPv2_Delay_ReqFrame *pReq ) +{ + // Delay Resp message specific stuff, 13.8.2 + + PTPv2_Delay_RespFrame *pResp = new PTPv2_Delay_RespFrame; + + pResp->setReceiveTimestamp( pReq->getIngressTimeStamp().GetTime() ); + pResp->setRequestingPortIdentity( pReq->getSourcePortIdentity() ); + pResp->setReqEgressTime( SIMTIME_ZERO ); // Will be set by receiver's MAC on ingress + + // Generic PTP header, 13.3.2 + pResp->setTransportSpecific(0); + pResp->setMessageType(PTP_TYPE_DELAY_RESP); + pResp->setReserved_0(0); + pResp->setVersionPTP(pPort->PortDS().GetVersionNumber()); + pResp->setMessageLength(PTP_MSG_DEL_RESP_LEN); + pResp->setDomainNumber(pApp->defaultDS.GetDomainNumber()); + pResp->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pResp->getFlagField(); + + FlagField.alternateMasterFlag = false; // Alternate master currently not supported + FlagField.twoStepFlag = false; + FlagField.unicastFlag = false; // Unicast is currently not supported + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.reserved = false; + FlagField.leap59 = pApp->timePropertiesDS.GetLeap59(); + FlagField.leap61 = pApp->timePropertiesDS.GetLeap61(); + FlagField.currentUtcOffsetValid = pApp->timePropertiesDS.GetCurrentUtcOffsetValid(); + FlagField.ptpTimescale = pApp->timePropertiesDS.GetPtpTimescale(); + FlagField.timeTraceable = pApp->timePropertiesDS.GetTimeTraceable(); + FlagField.frequencyTraceable = pApp->timePropertiesDS.GetFrequencyTraceable(); + + pResp->setCorrectionField( pReq->getCorrectionField() ); // 11.3.2 + + for( unsigned int i = 0; i < pResp->getReserved_2ArraySize(); i++ ) + { + pResp->setReserved_2( i, 0); + } + + pResp->setSourcePortIdentity( pPort->PortDS().PortIdentity() ); + pResp->setSequenceId( pReq->getSequenceId() ); + pResp->setControlField( PTP_MSG_CTRL_DEL_RESP ); + pResp->setLogMessageInterval( pPort->PortDS().GetLogMinDelayReqInterval() ); // Table 24 + + // OMNeT specific stuff + pResp->setByteLength( PTP_MSG_DEL_RESP_LEN ); + + return pResp; +} + +void +cAppDelay::HandleSync( PTPv2_SyncFrame *pSync ) +{ + TwoStepFlag = pSync->getFlagField().twoStepFlag; + + if( !TwoStepFlag ) + { + TimestampMatcher.PushFirst( pSync->getOriginTimestamp().GetSimTime(), SIMTIME_ZERO, pSync->getSequenceId() ); + } + + TimestampMatcher.PushSecond( pSync->getIngressTimeStamp().GetTime(), + pSync->getCorrectionField().GetSimTime(), + pSync->getSequenceId() ); + + if( TriggerStart == true ) + { + TriggerStart = false; + + cFilteredPortService::StartInterval(); + } +} + +void +cAppDelay::HandleFollowUp( PTPv2_Follow_UpFrame *pFollowUp ) +{ + if( TwoStepFlag ) + { + TimestampMatcher.PushFirst( pFollowUp->getPreciseOriginTimestamp().GetSimTime(), pFollowUp->getCorrectionField().GetSimTime(), pFollowUp->getSequenceId() ); + } +} + +void +cAppDelay::HandleDelayReq ( PTPv2_Delay_ReqFrame *pReq ) +{ + portState_t PortState; + + // This code implements Figure 33 + PortState = pPort->PortDS().GetPortState(); + + if + ( + ( PortState == PORT_STATE_INITIALIZING ) || + ( PortState == PORT_STATE_DISABLED ) + ) + { + return; + } + + if( PortState == PORT_STATE_FAULTY ) + { + // Optional implementation specific handling of announce message would be possible here + return; + } + + if( PortState != PORT_STATE_MASTER ) + { + return; + } + + pParentModule->emit( DelReqRcvd_SigId, pReq->getSequenceId() ); + + PTPv2_Delay_RespFrame *pResp = CreateDelayRespFrame( pReq ); + + pPort->IssueFrame(pResp); +} + +void +cAppDelay::HandleDelayResp ( PTPv2_Delay_RespFrame *pResp ) +{ + portState_t PortState; + + // This code implements Figure 33 + PortState = pPort->PortDS().GetPortState(); + + if + ( + ( PortState == PORT_STATE_INITIALIZING ) || + ( PortState == PORT_STATE_DISABLED ) + ) + { + return; + } + + if( PortState == PORT_STATE_FAULTY ) + { + // Optional implementation specific handling of announce message would be possible here + return; + } + + if + ( + ( PortState != PORT_STATE_SLAVE ) && + ( PortState != PORT_STATE_UNCALIBRATED ) + ) + { + return; + } + + // Ignore not associated responses + if( pResp->getSequenceId() != RequSequId ) + { + return; + } + + // Message from current master? + if( pApp->parentDS.ParentPortIdentity() != pResp->getSourcePortIdentity() ) + { + return; + } + + // Response for our request? + if( pPort->PortDS().PortIdentity() != pResp->getRequestingPortIdentity() ) + { + return; + } + + pParentModule->emit( DelRespRcvd_SigId, pResp->getSequenceId() ); + + // Execute DelayReq-Response mechanism (11.3.2) + simtime_t t1 = SyncTimes.Time1; + simtime_t t2 = SyncTimes.Time2; + simtime_t t3 = pResp->getReqEgressTime(); + simtime_t t4 = pResp->getReceiveTimestamp().GetSimTime(); + + simtime_t c1 = SyncTimes.Corr1; + simtime_t c2 = SyncTimes.Corr2; + simtime_t c3 = pResp->getCorrectionField().GetSimTime(); + + simtime_t meanPathDelay = ( (t2-t3) + (t4-t1) - (c1+c2+c3) ) / 2.0; + + pApp->EmitSignal_meanPathDelay_raw( meanPathDelay ); + + pFilter->push( meanPathDelay ); + meanPathDelay = pFilter->pop(); + + pApp->SetMeanPathDelay( meanPathDelay ); + + // Update logMinDelayReqInterval + pPort->PortDS().SetLogMinDelayReqInterval( pResp->getLogMessageInterval() ); + Interval = pow( 2.0, pResp->getLogMessageInterval() ); +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +cAppDelay::ParseResourceParameters() +{ + cFilteredPortService::ParseResourceParameters(); + + FilterType = cSimTimeFilter_ParameterParser::ParseSimTimeFilterType( pParentModule->par( "meanPathDelayFilter_Type" ) ); + FilterLen = pParentModule->par( "meanPathDelayFilter_Len" ).longValue(); + FilterDiscardMinMax = pParentModule->par( "meanPathDelayFilter_DiscardMinMax" ).boolValue(); +} + +void +cAppDelay::ParseParameters() +{ + EnableDebugOutput = pParentModule->par( "Port_Delay_EnableDebugOutput" ).boolValue(); +} + +void +cAppDelay::RegisterSignals() +{ + cFilteredPortService::RegisterSignals(); + + DelReqRcvd_SigId = pPort->RegisterDynamicSignal( "DelReqRcvd" ); + DelRespRcvd_SigId = pPort->RegisterDynamicSignal( "DelRespRcvd" ); +} + +void +cAppDelay::InitSignals() +{ + cFilteredPortService::InitSignals(); +} + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cAppDelay::cAppDelay() + : cFilteredPortService() +{ + TriggerStart = false; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ + +void +cAppDelay::StartInterval() +{ + TriggerStart = true; +} + +void +cAppDelay::StopInterval() +{ + TriggerStart = false; + cFilteredPortService::StopInterval(); +} + +void +cAppDelay::HandleIntervalEvent() +{ + PTPv2_Delay_ReqFrame *pReq = CreateDelayReqFrame(); + + // Remember SeqID + RequSequId = pReq->getSequenceId(); + + // Remember Time of most recent Sync frame + SyncTimes = TimestampMatcher.GetMostRecent(); + + pPort->IssueFrame(pReq); +} + +void +cAppDelay::HandleTimeoutEvent() +{ + StopTimeout(); + + // Nothing special to do here, timeouts are not used in AppDelay +} + +void +cAppDelay::HandleTimeJump() +{ + cFilteredPortService::HandleTimeJump(); + + // Set request sequence Id to some bogus value + // This will cause received responses for ongoing requests + // to be ignored + RequSequId = DrawSequId(); +} + +void +cAppDelay::HandleMsg( PTPv2_Frame *pPtpFrame ) +{ + + if( pApp->DelayMechanism != DELAY_MECH_E2E ) + { + return; + } + + switch( pPtpFrame->getMessageType() ) + { + case PTP_TYPE_SYNC: HandleSync( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_FOLLOW_UP: HandleFollowUp( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_DELAY_REQ: HandleDelayReq( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_DELAY_RESP: HandleDelayResp ( check_and_cast(pPtpFrame) ); + break; + + default: // Ignore other frames + break; + } +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ diff --git a/src/Software/PTP_Stack/AppServices/Delay/AppDelay.h b/src/Software/PTP_Stack/AppServices/Delay/AppDelay.h new file mode 100644 index 0000000..21f19ec --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Delay/AppDelay.h @@ -0,0 +1,108 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_APP_DELAY_H_ +#define LIBPTP_APP_DELAY_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP.h" +#include "FilteredPortService.h" +#include "TimestampMatcher.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cAppDelay : public cFilteredPortService +{ + private: + + // Resources + cTimestampMatcher TimestampMatcher; + + // Configuration + bool TwoStepFlag; + + // Internal housekeeping + UInteger16 RequSequId; + bool TriggerStart; + MatchedTime_t SyncTimes; + + // Signals for statistics + simsignal_t DelReqRcvd_SigId; + simsignal_t DelRespRcvd_SigId; + + // Debug config + bool EnableDebugOutput; + + // Frame creation + PTPv2_Delay_ReqFrame *CreateDelayReqFrame(); + PTPv2_Delay_RespFrame *CreateDelayRespFrame( PTPv2_Delay_ReqFrame *pReq ); + + // Frame handling + void HandleSync ( PTPv2_SyncFrame *pSync ); + void HandleFollowUp ( PTPv2_Follow_UpFrame *pFollowUp ); + void HandleDelayReq ( PTPv2_Delay_ReqFrame *pReq ); + void HandleDelayResp ( PTPv2_Delay_RespFrame *pResp ); + + // Init API + void ParseResourceParameters(); + void ParseParameters(); + void RegisterSignals(); + void InitSignals(); + + protected: + + public: + + // Constructors + cAppDelay(); + + // Destructor + + // Setters + + // Getters + + // API functions + void StartInterval(); + void StopInterval(); + + void HandleMsg( PTPv2_Frame *pPtpFrame ); + + void HandleIntervalEvent(); + void HandleTimeoutEvent(); + void HandleTimeJump(); +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.cc b/src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.cc new file mode 100644 index 0000000..bb8a075 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.cc @@ -0,0 +1,123 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "TimestampMatcher.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Private methods +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ + +void +cTimestampMatcher::PushFirst( simtime_t Time, simtime_t Corr, uint16_t SequId ) +{ + this->Sequ1 = SequId; + this->Time1 = Time; + this->Corr1 = Corr; + + if( this->Sequ2 == SequId ) + { + // New match + MostRecentMatch.SequId = SequId; + MostRecentMatch.Time1 = this->Time1; + MostRecentMatch.Time2 = this->Time2; + MostRecentMatch.Corr1 = this->Corr1; + MostRecentMatch.Corr2 = this->Corr2; + } +} + +void +cTimestampMatcher::PushSecond( simtime_t Time, simtime_t Corr, uint16_t SequId ) +{ + this->Sequ2 = SequId; + this->Time2 = Time; + this->Corr2 = Corr; + + if( this->Sequ1 == SequId ) + { + // New match + MostRecentMatch.SequId = SequId; + MostRecentMatch.Time1 = this->Time1; + MostRecentMatch.Time2 = this->Time2; + MostRecentMatch.Corr1 = this->Corr1; + MostRecentMatch.Corr2 = this->Corr2; + } +} + +MatchedTime_t +cTimestampMatcher::GetMostRecent() +{ + return this->MostRecentMatch; +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ diff --git a/src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.h b/src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.h new file mode 100644 index 0000000..05931f8 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Delay/TimestampMatcher.h @@ -0,0 +1,72 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_TIMESTAMP_MATCHER_H_ +#define LIBPTP_TIMESTAMP_MATCHER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +struct MatchedTime_t +{ + simtime_t Time1; + simtime_t Time2; + simtime_t Corr1; + simtime_t Corr2; + uint16_t SequId; +}; + +class cTimestampMatcher +{ + private: + + uint16_t Sequ1; + uint16_t Sequ2; + simtime_t Time1; + simtime_t Time2; + simtime_t Corr1; + simtime_t Corr2; + + MatchedTime_t MostRecentMatch; + + protected: + + public: + + void PushFirst( simtime_t Time, simtime_t Corr, uint16_t SequId ); + void PushSecond( simtime_t Time, simtime_t Corr, uint16_t SequId ); + + MatchedTime_t GetMostRecent(); +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/FilteredPortService.cc b/src/Software/PTP_Stack/AppServices/FilteredPortService.cc new file mode 100644 index 0000000..49db665 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/FilteredPortService.cc @@ -0,0 +1,171 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "FilteredPortService.h" + +#include "MovingAvgSimTimeFilter.h" +#include "IdentitySimTimeFilter.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ + +cFilteredPortService::cFilteredPortService() + : cPortService() +{ + pFilter = nullptr; +} + +cFilteredPortService::cFilteredPortService( const cFilteredPortService& other ) + : cPortService( other ) +{ + // Configuration + FilterType = other.FilterType; + FilterLen = other.FilterLen; + FilterDiscardMinMax = other.FilterDiscardMinMax; + + // Resources + pFilter = other.pFilter->Clone(); +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cFilteredPortService::~cFilteredPortService() +{ + delete pFilter; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +void +cFilteredPortService::AllocateResources() +{ + cPortService::AllocateResources(); + + switch( FilterType ) + { + default: + { + throw cRuntimeError( "Invalid filter configured" ); + break; + } + + case MOVING_AVG_FILTER: + { + if( FilterLen == 0 ) + { + throw cRuntimeError( "Invalid filter length configured" ); + } + + pFilter = new MovingAvgSimTimeFilter( FilterLen, FilterDiscardMinMax ); + break; + } + + case NO_FILTER: + { + pFilter = new IdentitySimTimeFilter(); + break; + } + } +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ + +void +cFilteredPortService::StartInterval() +{ + if( IntervalState == TimerState::STOPPED ) + { + if( pFilter != nullptr ) + { + pFilter->reset(); + } + } + + cPortService::StartInterval(); +} + +void +cFilteredPortService::HandleTimeJump() +{ + if( pFilter != nullptr ) + { + pFilter->reset(); + } +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +cFilteredPortService& +cFilteredPortService::operator=( const cFilteredPortService& other ) +{ + cPortService::operator=( other ); + + // Configuration + FilterType = other.FilterType; + FilterLen = other.FilterLen; + FilterDiscardMinMax = other.FilterDiscardMinMax; + + // Resources + pFilter = other.pFilter->Clone(); + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/AppServices/FilteredPortService.h b/src/Software/PTP_Stack/AppServices/FilteredPortService.h new file mode 100644 index 0000000..ffceff1 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/FilteredPortService.h @@ -0,0 +1,79 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_FILTERED_PORT_SERVICE_H_ +#define LIBPTP_FILTERED_PORT_SERVICE_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PortService.h" +#include "ISimTimeFilter.h" +#include "SimTimeFilterTypes.h" +#include "SimTimeFilter_ParameterParser.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cFilteredPortService : public cPortService +{ + private: + + protected: + + // Configuration + SimTimeFilter_t FilterType; + size_t FilterLen; + bool FilterDiscardMinMax; + + // Resources + ISimTimeFilter *pFilter; + + public: + + // Constructors + cFilteredPortService(); + cFilteredPortService( const cFilteredPortService& other ); + + // Destructor + ~cFilteredPortService(); + + // Init API + void AllocateResources(); + + // API functions + void StartInterval(); + void HandleTimeJump(); + + // Operators + cFilteredPortService& operator=( const cFilteredPortService& other ); +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.cc b/src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.cc new file mode 100644 index 0000000..631fe7f --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.cc @@ -0,0 +1,598 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_Stack.h" +#include "AppPDelay.h" +#include "PTP_Port.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Private methods +// ------------------------------------------------------ + +PTPv2_PDelay_ReqFrame * +cAppPDelay::CreatePDelayReqFrame() +{ + // Delay Req message specific stuff, 13.9.2 + PTPv2_PDelay_ReqFrame *pReq = new PTPv2_PDelay_ReqFrame; + + // Precise timestamp will be filled in by MAC, thus it is set to 0 here + pReq->setOriginTimestamp( cTimeStamp(0,0) ); + + for( unsigned int i = 0; i < pReq->getReservedDelayReqArraySize(); i++ ) + { + pReq->setReservedDelayReq( i, 0); + } + + // Generic PTP header, 13.3.2 + pReq->setTransportSpecific(0); + pReq->setMessageType(PTP_TYPE_PDELAY_REQ); + pReq->setReserved_0(0); + pReq->setVersionPTP(pPort->PortDS().GetVersionNumber()); + pReq->setMessageLength(PTP_MSG_PDEL_REQ_LEN); + pReq->setDomainNumber(pApp->defaultDS.GetDomainNumber()); + pReq->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pReq->getFlagField(); + + FlagField.alternateMasterFlag = false; // Alternate master currently not supported + FlagField.twoStepFlag = false; + FlagField.unicastFlag = false; // Unicast is currently not supported + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.reserved = false; + FlagField.leap59 = pApp->timePropertiesDS.GetLeap59(); + FlagField.leap61 = pApp->timePropertiesDS.GetLeap61(); + FlagField.currentUtcOffsetValid = pApp->timePropertiesDS.GetCurrentUtcOffsetValid(); + FlagField.ptpTimescale = pApp->timePropertiesDS.GetPtpTimescale(); + FlagField.timeTraceable = pApp->timePropertiesDS.GetTimeTraceable(); + FlagField.frequencyTraceable = pApp->timePropertiesDS.GetFrequencyTraceable(); + + pReq->setCorrectionField( cTimeInterval() ); // Table 21 + + for( unsigned int i = 0; i < pReq->getReserved_2ArraySize(); i++ ) + { + pReq->setReserved_2( i, 0); + } + + pReq->setSourcePortIdentity( pPort->PortDS().PortIdentity() ); + pReq->setSequenceId( DrawSequId() ); + pReq->setControlField( PTP_MSG_CTRL_OTHERS ); + pReq->setLogMessageInterval( 0x7F ); // Table 24 + + // OMNeT specific stuff + pReq->setByteLength( PTP_MSG_PDEL_REQ_LEN ); + + return pReq; +} + +PTPv2_PDelay_RespFrame * +cAppPDelay::CreatePDelayRespFrame( PTPv2_PDelay_ReqFrame *pReq ) +{ + // Delay Resp message specific stuff, 13.10.2 + PTPv2_PDelay_RespFrame *pResp = new PTPv2_PDelay_RespFrame; + + pResp->setRequestReceiptTimestamp( cTimeStamp(0,0) ); // This is also set to 0 in case of twoStepFlag == true (first implementation approach, see 11.4.3) + pResp->setRequestingPortIdentity( pReq->getSourcePortIdentity() ); + + // Remember timestamps of pReq for future calculations + pResp->setReqEgressTime( SIMTIME_ZERO ); // Will be set by receiver's MAC on ingress + + // Generic PTP header, 13.3.2 + pResp->setTransportSpecific(0); + pResp->setMessageType(PTP_TYPE_PDELAY_RESP); + pResp->setReserved_0(0); + pResp->setVersionPTP(pPort->PortDS().GetVersionNumber()); + pResp->setMessageLength(PTP_MSG_PDEL_RESP_LEN); + pResp->setDomainNumber( pReq->getDomainNumber() ); + pResp->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pResp->getFlagField(); + + FlagField.alternateMasterFlag = false; // Alternate master currently not supported + FlagField.twoStepFlag = pApp->defaultDS.GetTwoStepFlag(); + FlagField.unicastFlag = false; // Unicast is currently not supported + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.reserved = false; + FlagField.leap59 = pApp->timePropertiesDS.GetLeap59(); + FlagField.leap61 = pApp->timePropertiesDS.GetLeap61(); + FlagField.currentUtcOffsetValid = pApp->timePropertiesDS.GetCurrentUtcOffsetValid(); + FlagField.ptpTimescale = pApp->timePropertiesDS.GetPtpTimescale(); + FlagField.timeTraceable = pApp->timePropertiesDS.GetTimeTraceable(); + FlagField.frequencyTraceable = pApp->timePropertiesDS.GetFrequencyTraceable(); + + if( pApp->defaultDS.GetTwoStepFlag() ) + { + pResp->setCorrectionField( cTimeInterval() ); + } + else + { + pResp->setCorrectionField( pReq->getCorrectionField() ); // 11.3.2 + } + + for( unsigned int i = 0; i < pResp->getReserved_2ArraySize(); i++ ) + { + pResp->setReserved_2( i, 0); + } + + pResp->setSourcePortIdentity( pPort->PortDS().PortIdentity() ); + pResp->setSequenceId( pReq->getSequenceId() ); + pResp->setControlField( PTP_MSG_CTRL_OTHERS ); + pResp->setLogMessageInterval( 0x7F ); // Table 24 + + // OMNeT specific stuff + pResp->setByteLength( PTP_MSG_PDEL_RESP_LEN ); + + return pResp; +} + +PTPv2_PDelay_Resp_FU_Frame * +cAppPDelay::CreatePDelayRespFUFrame( PTPv2_PDelay_ReqFrame *pReq ) +{ + // Delay Resp message specific stuff, 13.11.2 + PTPv2_PDelay_Resp_FU_Frame *pRespFU = new PTPv2_PDelay_Resp_FU_Frame; + + pRespFU->setResponseOriginTimestamp( cTimeStamp(0,0) ); // This is always set to 0 (first implementation approach, see 11.4.3) + pRespFU->setRequestingPortIdentity( pReq->getSourcePortIdentity() ); + + // Generic PTP header, 13.3.2 + pRespFU->setTransportSpecific(0); + pRespFU->setMessageType(PTP_TYPE_PDELAY_RESP_FU); + pRespFU->setReserved_0(0); + pRespFU->setVersionPTP(pPort->PortDS().GetVersionNumber()); + pRespFU->setMessageLength(PTP_MSG_PDEL_RESP_FU_LEN); + pRespFU->setDomainNumber( pReq->getDomainNumber() ); + pRespFU->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pRespFU->getFlagField(); + + FlagField.alternateMasterFlag = false; // Alternate master currently not supported + FlagField.twoStepFlag = true; + FlagField.unicastFlag = false; // Unicast is currently not supported + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.reserved = false; + FlagField.leap59 = pApp->timePropertiesDS.GetLeap59(); + FlagField.leap61 = pApp->timePropertiesDS.GetLeap61(); + FlagField.currentUtcOffsetValid = pApp->timePropertiesDS.GetCurrentUtcOffsetValid(); + FlagField.ptpTimescale = pApp->timePropertiesDS.GetPtpTimescale(); + FlagField.timeTraceable = pApp->timePropertiesDS.GetTimeTraceable(); + FlagField.frequencyTraceable = pApp->timePropertiesDS.GetFrequencyTraceable(); + + pRespFU->setCorrectionField( pReq->getCorrectionField() ); // 11.3.2 + + for( unsigned int i = 0; i < pRespFU->getReserved_2ArraySize(); i++ ) + { + pRespFU->setReserved_2( i, 0); + } + + pRespFU->setSourcePortIdentity( pPort->PortDS().PortIdentity() ); + pRespFU->setSequenceId( pReq->getSequenceId() ); + pRespFU->setControlField( PTP_MSG_CTRL_OTHERS ); + pRespFU->setLogMessageInterval( 0x7F ); // Table 24 + + // OMNeT specific stuff + pRespFU->setByteLength( PTP_MSG_PDEL_RESP_FU_LEN ); + + return pRespFU; +} + +PTPv2_PDelay_Resp_FU_Frame * +cAppPDelay::CreatePDelayRespFUFrame( PTPv2_PDelay_RespFrame *pResp ) +{ + // Delay Resp FU creation, according to 11.5.4.3 + PTPv2_PDelay_Resp_FU_Frame *pRespFU = new PTPv2_PDelay_Resp_FU_Frame; + + pRespFU->setResponseOriginTimestamp( cTimeStamp(0,0) ); // This is always set to 0 (first implementation approach, see 11.4.3) + pRespFU->setRequestingPortIdentity( pResp->getSourcePortIdentity() ); + + // Generic PTP header, 13.3.2 + pRespFU->setTransportSpecific(0); + pRespFU->setMessageType(PTP_TYPE_PDELAY_RESP_FU); + pRespFU->setReserved_0(0); + pRespFU->setVersionPTP( pResp->getVersionPTP() ); + pRespFU->setMessageLength(PTP_MSG_PDEL_RESP_FU_LEN); + pRespFU->setDomainNumber( pResp->getDomainNumber() ); + pRespFU->setReserved_1(0); + pRespFU->setFlagField( pResp->getFlagField() ); + + pRespFU->setCorrectionField( cTimeInterval(0) ); + + for( unsigned int i = 0; i < pRespFU->getReserved_2ArraySize(); i++ ) + { + pRespFU->setReserved_2( i, 0); + } + + pRespFU->setSourcePortIdentity( pResp->getSourcePortIdentity() ); + pRespFU->setSequenceId( pResp->getSequenceId() ); + pRespFU->setControlField( PTP_MSG_CTRL_OTHERS ); + pRespFU->setLogMessageInterval( 0x7F ); // Table 24 + + // OMNeT specific stuff + pRespFU->setByteLength( PTP_MSG_PDEL_RESP_FU_LEN ); + + return pRespFU; +} + +// Frame handling +void +cAppPDelay::HandlePDelayReq( PTPv2_PDelay_ReqFrame *pReq ) +{ + // Verify we are in a state that is allowed to answer + switch( pPort->PortDS().GetPortState() ) + { + case PORT_STATE_LISTENING: + case PORT_STATE_PRE_MASTER: + case PORT_STATE_MASTER: + case PORT_STATE_PASSIVE: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + + break; + + default: + return; + } + + pParentModule->emit( PDelReqRcvd_SigId, pReq->getSequenceId() ); + + PTPv2_PDelay_RespFrame *pResp = CreatePDelayRespFrame( pReq ); + + pPort->IssueFrame(pResp); + + // Create + send follow up if necessary + if( pApp->defaultDS.GetTwoStepFlag() ) + { + PTPv2_PDelay_Resp_FU_Frame *pRespFU; + + pRespFU = CreatePDelayRespFUFrame( pReq ); + + // Issue frame + pPort->IssueFrame(pRespFU); + } +} + +void +cAppPDelay::HandlePDelayResp( PTPv2_PDelay_RespFrame *pResp ) +{ + // Check if we are waiting for this response + if( pResp->getSequenceId() != RequSequId ) + { + return; + } + + // Response for our request? + if( pPort->PortDS().PortIdentity() != pResp->getRequestingPortIdentity() ) + { + return; + } + + // Verify we are in suitable state + switch( pPort->PortDS().GetPortState() ) + { + case PORT_STATE_LISTENING: + case PORT_STATE_PRE_MASTER: + case PORT_STATE_MASTER: + case PORT_STATE_PASSIVE: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + + break; + + default: + return; + } + + pParentModule->emit( PDelRespRcvd_SigId, pResp->getSequenceId() ); + + if( pResp->getFlagField().twoStepFlag ) + { + ReqEgress = pResp->getReqEgressTime(); + RequestReceipt = pResp->getRequestReceiptTimestamp().GetSimTime(); + RespIngress = pResp->getIngressTimeStamp().GetTime(); + RespCorr = pResp->getCorrectionField().GetSimTime(); + RespSequID = pResp->getSequenceId(); + } + else + { + CalcMeanPathDelay( pResp->getReqEgressTime(), + pResp->getIngressTimeStamp().GetTime(), + pResp->getRequestReceiptTimestamp().GetSimTime(), + SIMTIME_ZERO, + pResp->getCorrectionField().GetSimTime(), + SIMTIME_ZERO ); + } +} + +void +cAppPDelay::HandlePDelayRespFU( PTPv2_PDelay_Resp_FU_Frame *pRespFU ) +{ + // Check if we are waiting for this response + if( pRespFU->getSequenceId() != RequSequId ) + { + return; + } + + // Response for our request? + if( pPort->PortDS().PortIdentity() != pRespFU->getRequestingPortIdentity() ) + { + return; + } + + // Verify we are in suitable state + switch( pPort->PortDS().GetPortState() ) + { + case PORT_STATE_LISTENING: + case PORT_STATE_PRE_MASTER: + case PORT_STATE_MASTER: + case PORT_STATE_PASSIVE: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + + break; + + default: + return; + } + + pParentModule->emit( PDelRespFuRcvd_SigId, pRespFU->getSequenceId() ); + + if( pRespFU->getSequenceId() == RespSequID ) + { + CalcMeanPathDelay( ReqEgress, + RespIngress, + RequestReceipt, + pRespFU->getResponseOriginTimestamp().GetSimTime(), + RespCorr, + pRespFU->getCorrectionField().GetSimTime() ); + } +} + +void +cAppPDelay::CalcMeanPathDelay +( + simtime_t t1, + simtime_t t4, + simtime_t requestReceiptTimestamp, + simtime_t responseOriginTimestamp, + simtime_t CorrPDelResp, + simtime_t CorrPDelRespFU +) +{ + // 11.4.3 + // = [(t4 − t1) − + // (responseOriginTimestamp − requestReceiptTimestamp) − + // correctionField of Pdelay_Resp − correctionField of Pdelay_Resp_Follow_Up]/2 + + simtime_t meanPathDelay; + + meanPathDelay = (t4-t1); + meanPathDelay -= (responseOriginTimestamp-requestReceiptTimestamp); + meanPathDelay -= CorrPDelResp; + meanPathDelay -= CorrPDelRespFU; + meanPathDelay /= 2.0; + + pParentModule->emit( peerMeanPathDelay_raw_SigId, meanPathDelay ); + + pFilter->push( meanPathDelay ); + pPort->PortDS().SetPeerMeanPathDelay( pFilter->pop() ); +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +void +cAppPDelay::ParseResourceParameters() +{ + cFilteredPortService::ParseResourceParameters(); + + FilterType = cSimTimeFilter_ParameterParser::ParseSimTimeFilterType( pParentModule->par( "meanPeerDelayFilter_Type" ) ); + FilterLen = pParentModule->par( "meanPeerDelayFilter_Len" ).longValue(); + FilterDiscardMinMax = pParentModule->par( "meanPeerDelayFilter_DiscardMinMax" ).boolValue(); +} + +void +cAppPDelay::ParseParameters() +{ + EnableDebugOutput = pParentModule->par( "Port_PDelay_EnableDebugOutput" ).boolValue(); +} + +void +cAppPDelay::RegisterSignals() +{ + cFilteredPortService::RegisterSignals(); + + PDelReqRcvd_SigId = pPort->RegisterDynamicSignal( "PDelReqRcvd" ); + PDelRespRcvd_SigId = pPort->RegisterDynamicSignal( "PDelRespRcvd" ); + PDelRespFuRcvd_SigId = pPort->RegisterDynamicSignal( "PDelRespFuRcvd" ); + + peerMeanPathDelay_raw_SigId = pPort->RegisterDynamicSignal( "peerMeanPathDelay_raw" ); +} + +void +cAppPDelay::InitSignals() +{ + cFilteredPortService::InitSignals(); +} + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cAppPDelay::cAppPDelay() + : cFilteredPortService() +{ +} + +cAppPDelay::cAppPDelay( const cAppPDelay& other ) + : cFilteredPortService( other ) +{ + // Internal housekeeping + RequSequId = other.RequSequId; + RespSequID = other.RespSequID; + + // TwoStepFlag stuff + ReqEgress = other.ReqEgress; + RespIngress = other.RespIngress; + RespCorr = other.RespCorr; + RequestReceipt = other.RequestReceipt; + + // Signals for statistics + PDelReqRcvd_SigId = other.PDelReqRcvd_SigId; + PDelRespRcvd_SigId = other.PDelRespRcvd_SigId; + PDelRespFuRcvd_SigId = other.PDelRespFuRcvd_SigId; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ + +void +cAppPDelay::HandleIntervalEvent() +{ + PTPv2_PDelay_ReqFrame *pReq = CreatePDelayReqFrame(); + + pPort->IssueFrame(pReq); + + // Remember SeqID + RequSequId = pReq->getSequenceId(); +} + +void +cAppPDelay::HandleTimeoutEvent() +{ + StopTimeout(); + + // Nothing special to do here, timeouts are not used in AppPDelay +} + +void +cAppPDelay::HandleTimeJump() +{ + cFilteredPortService::HandleTimeJump(); + + // Set request sequence Id to some bogus value + // This will cause received responses for ongoing requests + // to be ignored + RequSequId = DrawSequId(); +} + +void +cAppPDelay::HandleMsg( PTPv2_Frame *pPtpFrame ) +{ + if( pApp->DelayMechanism != DELAY_MECH_P2P ) + { + return; + } + + switch( pPtpFrame->getMessageType() ) + { + case PTP_TYPE_PDELAY_REQ: HandlePDelayReq( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_PDELAY_RESP: HandlePDelayResp( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_PDELAY_RESP_FU: HandlePDelayRespFU( check_and_cast(pPtpFrame) ); + break; + + default: // Ignore other frames + break; + } +} + +void +cAppPDelay::TriggerFollowUp( PTPv2_PDelay_RespFrame *pResp ) +{ + PTPv2_PDelay_Resp_FU_Frame *pFollowUp; + + pFollowUp = CreatePDelayRespFUFrame( pResp ); + + pPort->IssueFrame( pFollowUp ); +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +cAppPDelay& +cAppPDelay::operator=( const cAppPDelay& other ) +{ + cFilteredPortService::operator=( other ); + + // Internal housekeeping + RequSequId = other.RequSequId; + RespSequID = other.RespSequID; + + // TwoStepFlag stuff + ReqEgress = other.ReqEgress; + RespIngress = other.RespIngress; + RespCorr = other.RespCorr; + RequestReceipt = other.RequestReceipt; + + // Signals for statistics + PDelReqRcvd_SigId = other.PDelReqRcvd_SigId; + PDelRespRcvd_SigId = other.PDelRespRcvd_SigId; + PDelRespFuRcvd_SigId = other.PDelRespFuRcvd_SigId; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.h b/src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.h new file mode 100644 index 0000000..0efb85e --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/PDelay/AppPDelay.h @@ -0,0 +1,123 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_APP_PDELAY_H_ +#define LIBPTP_APP_PDELAY_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP.h" +#include "FilteredPortService.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cAppPDelay : public cFilteredPortService +{ + private: + + // Configuration + + // Resources + + // Internal housekeeping + UInteger16 RequSequId; + UInteger16 RespSequID; + + // TwoStepFlag stuff + simtime_t ReqEgress; + simtime_t RespIngress; + simtime_t RespCorr; + simtime_t RequestReceipt; + + // Signals for statistics + simsignal_t PDelReqRcvd_SigId; + simsignal_t PDelRespRcvd_SigId; + simsignal_t PDelRespFuRcvd_SigId; + simsignal_t peerMeanPathDelay_raw_SigId; + + // Debug config + bool EnableDebugOutput; + + // Frame creation + PTPv2_PDelay_ReqFrame *CreatePDelayReqFrame(); + PTPv2_PDelay_RespFrame *CreatePDelayRespFrame( PTPv2_PDelay_ReqFrame *pReq ); + PTPv2_PDelay_Resp_FU_Frame *CreatePDelayRespFUFrame( PTPv2_PDelay_ReqFrame *pReq ); + PTPv2_PDelay_Resp_FU_Frame *CreatePDelayRespFUFrame( PTPv2_PDelay_RespFrame *pResp ); + + // Frame handling + void HandlePDelayReq ( PTPv2_PDelay_ReqFrame *pReq ); + void HandlePDelayResp ( PTPv2_PDelay_RespFrame *pResp ); + void HandlePDelayRespFU ( PTPv2_PDelay_Resp_FU_Frame *pRespFU ); + + void CalcMeanPathDelay ( simtime_t t1, + simtime_t t4, + simtime_t requestReceiptTimestamp, + simtime_t responseOriginTimestamp, + simtime_t CorrPDelResp, + simtime_t CorrPDelRespFU ); + + // Init API + void ParseResourceParameters(); + void ParseParameters(); + void RegisterSignals(); + void InitSignals(); + + protected: + + public: + + // Constructors/Destructor + cAppPDelay(); + cAppPDelay( const cAppPDelay& other ); + + // Destructor + + // Setters + + // Getters + + // API functions + void HandleMsg( PTPv2_Frame *pPtpFrame ); + + void HandleIntervalEvent(); + void HandleTimeoutEvent(); + void HandleTimeJump(); + + void TriggerFollowUp( PTPv2_PDelay_RespFrame *pResp ); + + // Operators + cAppPDelay& operator=( const cAppPDelay& other ); +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/PortService.cc b/src/Software/PTP_Stack/AppServices/PortService.cc new file mode 100644 index 0000000..5d1bc6f --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/PortService.cc @@ -0,0 +1,211 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PortService.h" +#include "PTP_Port.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +cPortService::VerifyCopyable() +{ + cBasicService::VerifyCopyable(); + + if( TimeoutState != TimerState::STOPPED ) + { + throw cRuntimeError( "It is not allowed to copy services with non-stopped timers." ); + } +} + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cPortService::cPortService() + : cBasicService() +{ + this->pPort = nullptr; + this->SequId = 0; + + this->Timeout = SIMTIME_ZERO; + this->TimeoutState = TimerState::STOPPED; + this->TimeoutEvent = cClockEvent( static_cast(PortServiceEvent::TIMEOUT), 0, 0, 0, nullptr ); + this->ScheduledTimeout = cScheduledClockEvent(); +} + +cPortService::cPortService( const cPortService& other ) + : cBasicService( other ) +{ + VerifyCopyable(); + + this->pPort = other.pPort; + this->SequId = other.SequId; + + this->Timeout = other.Timeout; + this->TimeoutState = other.TimeoutState; + this->TimeoutEvent = other.TimeoutEvent; + this->ScheduledTimeout = other.ScheduledTimeout; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cPortService::~cPortService() +{ +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cPortService::SetTimeout( simtime_t Timeout ) +{ + this->Timeout = Timeout; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cPortService::SetHierarchy( PTP_Stack *pApp, cPTP_Port *pPort, cScheduleClock *pClock ) +{ + cBasicService::SetHierarchy( pApp, pClock ); + + this->pPort = pPort; +} + +UInteger16 +cPortService::DrawSequId() +{ + return this->SequId++; +} + +void +cPortService::StartTimeout() +{ + if( TimeoutState == TimerState::STOPPED ) + { + ScheduledTimeout = pClock->ScheduleRelativeEvent( Timeout, this, TimeoutEvent ); + } + + TimeoutState = TimerState::ACTIVE; +} + +void +cPortService::UpdateTimeout() +{ + if( TimeoutState == TimerState::ACTIVE ) + { + if( Timeout == SIMTIME_ZERO ) + { + throw cRuntimeError( "PortService: Can't update timeout service with a value of 0" ); + } + + pClock->CancelEvent( ScheduledTimeout ); + ScheduledTimeout = pClock->ScheduleRelativeEvent( Timeout, this, TimeoutEvent ); + } +} + +void +cPortService::StopTimeout() +{ + if( TimeoutState == TimerState::ACTIVE ) + { + pClock->CancelEvent( ScheduledTimeout ); + } + + TimeoutState = TimerState::STOPPED; +} + +// ------------------------------------------------------ +// Clock API +// ------------------------------------------------------ +void +cPortService::HandleClockEvent( cClockEvent& ClockEvent ) +{ + PortServiceEvent Event = static_cast(ClockEvent.GetID1()); + + switch( Event ) + { + case PortServiceEvent::TIMEOUT: + { + HandleTimeoutEvent(); + break; + } + + default: + { + cBasicService::HandleClockEvent( ClockEvent ); + break; + } + } +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +cPortService& +cPortService::operator=( const cPortService& other ) +{ + VerifyCopyable(); + + cBasicService::operator=( other ); + + this->pPort = other.pPort; + this->SequId = other.SequId; + + this->Timeout = other.Timeout; + this->TimeoutState = other.TimeoutState; + this->TimeoutEvent = other.TimeoutEvent; + this->ScheduledTimeout = other.ScheduledTimeout; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/AppServices/PortService.h b/src/Software/PTP_Stack/AppServices/PortService.h new file mode 100644 index 0000000..23ea0f6 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/PortService.h @@ -0,0 +1,106 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PORT_SERVICE_H_ +#define LIBPTP_PORT_SERVICE_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "BasicService.h" + +#include "PTP_PrimitiveDataTypes.h" + +// ====================================================== +// Types +// ====================================================== + +class cPTP_Port; + +// ====================================================== +// Declarations +// ====================================================== + +class cPortService : public cBasicService +{ + private: + + protected: + + // Types + enum class PortServiceEvent + { + TIMEOUT = BasicServiceEvent::NEXT_FREE_ENTRY, + NEXT_FREE_ENTRY, + }; + + // Internal housekeeping + UInteger16 SequId; + TimerState TimeoutState; + + // Configuration + simtime_t Timeout; + + // Resources + cPTP_Port *pPort; + cClockEvent TimeoutEvent; + cScheduledClockEvent ScheduledTimeout; + + // Internal functions + virtual void VerifyCopyable(); + + public: + + // Constructors/Destructor + cPortService(); + cPortService( const cPortService& other ); + virtual ~cPortService(); + + // Setters + void SetTimeout( simtime_t Timeout ); + + // Getters + + // Initialization + void SetHierarchy( PTP_Stack *pApp, cPTP_Port *pPort, cScheduleClock *pClock ); + + // API functions + UInteger16 DrawSequId(); + + void StartTimeout(); + void UpdateTimeout(); + void StopTimeout(); + + // Clock API + void HandleClockEvent( cClockEvent& ClockEvent ); + + // Operators + cPortService& operator=( const cPortService& other ); + + // Timeout handling + virtual void HandleTimeoutEvent() = 0; +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.cc b/src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.cc new file mode 100644 index 0000000..3d0523c --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.cc @@ -0,0 +1,254 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_Stack.h" + +#include "AppStateDecision.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cAppStateDecision::cAppStateDecision() + : cAppService() +{ +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cAppStateDecision::~cAppStateDecision() +{ +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API Functions +// ------------------------------------------------------ + +// This functions implements the procedure described in 9.3.2.2 +// Steps a) and b) are handled inside the foreignMasterDS of each port +// Step c) Calculate E_best +void +cAppStateDecision::HandleIntervalEvent() +{ + UInteger16 EbestPortID = 0; + + if( EnableBriefDebugOutput ) + { + EV << "========================================================================" << endl; + EV << " State Decision " << endl; + EV << "========================================================================" << endl; + EV << endl; + EV << "Step 1: Calculating current Ebest" << endl; + } + + pApp->Ebest.Clear(); + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cForeignClockDS Erbest = pApp->pPorts[PortIdx].GetErbest(); + + if( EnableBriefDebugOutput ) + { + EV << "Checking Erbest of Port " << PortIdx + 1 << endl; + } + if( !Erbest.IsEmpty() ) + { + ClockCompReturn_t CompReturn = cForeignClockDS::CompareClockDS( pApp->Ebest, Erbest, EnableDetailedDebugOutput ); + + if( EnableBriefDebugOutput ) + { + EV << " CompReturn.Result: " << CompReturn.Result << endl; + EV << " CompReturn.Reason: " << CompReturn.Reason << endl; + } + + switch( CompReturn.Result ) + { + case ClockCompResult::B_BETTER_A: + case ClockCompResult::B_BETTER_A_BY_TOPO: + + pApp->Ebest = Erbest; + EbestPortID = PortIdx + 1; + break; + + default: + break; + } + } + else + { + if( EnableBriefDebugOutput ) + { + EV << " Erbest of Port " << PortIdx + 1 << " is empty" << endl; + } + } + } + + pParentModule->emit( EbestModuleID_SigId, pApp->Ebest.GetModuleID() ); + + if( EnableBriefDebugOutput ) + { + EV << "Current Ebest: "; + if( pApp->Ebest.IsEmpty() ) + { + EV << "empty" << endl; + } + else + { + EV << pApp->Ebest.ClockIdentity().GetString() << endl; + } + EV << endl; + EV << "Step 2: Calculating StateDecision for all ports" << endl; + EV << endl; + } + + bool NewMasterFlag = pApp->Ebest.SenderPortIdentity() != pApp->parentDS.ParentPortIdentity(); + + // Step d) Apply state decision algorithm for each port + std::vector StateDecisions; + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPTP_Port *pPort = &pApp->pPorts[PortIdx]; + portStateDecision_t StateDecision = pPort->GetStateDecision( pApp->Ebest, EbestPortID, EnableDetailedDebugOutput ); + + StateDecisions.push_back( StateDecision ); + + if( EnableBriefDebugOutput ) + { + EV << "State Decision for Port " << PortIdx + 1 << ": " << StateDecision << endl; + } + } + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + pApp->HandlePortStateDec( StateDecisions[PortIdx] ); + } + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + pApp->pPorts[PortIdx].HandleStateDecision( StateDecisions[PortIdx], NewMasterFlag ); + + if( EnableBriefDebugOutput ) + { + EV << endl; + } + } + if( EnableBriefDebugOutput ) + { + EV << "========================================================================" << endl; + } + + // Update tool tip + if( EnableTooltip ) + { + std::stringstream ss; + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPTP_Port *pPort = &pApp->pPorts[PortIdx]; + + ss << "Port " << PortIdx + 1 << ": " << pPort->PortDS().GetPortState(); + + if( PortIdx < pApp->PtpGateSize-1 ) + { + ss << endl; + } + } + + Tooltip = ss.str(); + + pTooltipModule->getDisplayString().setTagArg("tt", 0, Tooltip.c_str()); + } +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +void +cAppStateDecision::ParseResourceParameters() +{ + cAppService::ParseResourceParameters(); + + TooltipPath = pParentModule->par( "StateDec_TooltipPath" ).stringValue(); + EnableTooltip = TooltipPath.empty() ? false : true; +} + +void +cAppStateDecision::AllocateResources() +{ + cAppService::AllocateResources(); + + pTooltipModule = EnableTooltip ? pParentModule->getModuleByPath( TooltipPath.c_str() ) : nullptr; +} + +void +cAppStateDecision::ParseParameters() +{ + cAppService::ParseParameters(); + + EnableBriefDebugOutput = pParentModule->par( "StateDecision_EnableBriefDebugOutput" ).boolValue(); + EnableDetailedDebugOutput = pParentModule->par( "StateDecision_EnableDetailedDebugOutput" ).boolValue(); +} + +void +cAppStateDecision::RegisterSignals() +{ + cAppService::RegisterSignals(); + + EbestModuleID_SigId = pParentModule->registerSignal("EbestModuleID"); +} +void +cAppStateDecision::InitSignals() +{ + cAppService::InitSignals(); +} diff --git a/src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.h b/src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.h new file mode 100644 index 0000000..2e94e5d --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/StateDecision/AppStateDecision.h @@ -0,0 +1,86 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_APP_STATE_DECISION_H_ +#define LIBPTP_APP_STATE_DECISION_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "AppService.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cAppStateDecision : public cAppService +{ + private: + + // Signals + simsignal_t EbestModuleID_SigId; + + // Internal functions + void HandleIntervalEvent(); + + protected: + + // Resources + std::string Tooltip; + cModule *pTooltipModule; + + // Configuration + std::string TooltipPath; + bool EnableTooltip; + + // Debug configuration + bool EnableBriefDebugOutput; + bool EnableDetailedDebugOutput; + + public: + + // Constructors + cAppStateDecision(); + + // Destructor + ~cAppStateDecision(); + + // Setters + + // Getters + + // Initialization + void ParseResourceParameters(); + void AllocateResources(); + void ParseParameters(); + void RegisterSignals(); + void InitSignals(); +}; + +#endif diff --git a/src/Software/PTP_Stack/AppServices/Sync/AppSync.cc b/src/Software/PTP_Stack/AppServices/Sync/AppSync.cc new file mode 100644 index 0000000..77b2177 --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Sync/AppSync.cc @@ -0,0 +1,475 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + + +#include +#include + +#include "PTP_Stack.h" +#include "AppSync.h" +#include "PTP_Port.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Private methods +// ------------------------------------------------------ +PTPv2_SyncFrame * +cAppSync::CreateSyncFrame() +{ + // Sync message specific stuff, 13.6.2 + + PTPv2_SyncFrame *pSync = new PTPv2_SyncFrame; + + // Precise timestamp will be filled in by MAC, thus it is set to 0 here + pSync->setOriginTimestamp( cTimeStamp(0,0) ); + + // Generic PTP header, 13.3.2 + pSync->setTransportSpecific(0); + pSync->setMessageType(PTP_TYPE_SYNC); + pSync->setReserved_0(0); + pSync->setVersionPTP(pPort->PortDS().GetVersionNumber()); + pSync->setMessageLength(PTP_MSG_SYNC_LEN); + pSync->setDomainNumber(pApp->defaultDS.GetDomainNumber()); + pSync->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pSync->getFlagField(); + + FlagField.alternateMasterFlag = false; // Alternate master currently not supported + FlagField.twoStepFlag = pApp->defaultDS.GetTwoStepFlag(); + FlagField.unicastFlag = false; // Unicast is currently not supported + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.reserved = false; + FlagField.leap59 = pApp->timePropertiesDS.GetLeap59(); + FlagField.leap61 = pApp->timePropertiesDS.GetLeap61(); + FlagField.currentUtcOffsetValid = pApp->timePropertiesDS.GetCurrentUtcOffsetValid(); + FlagField.ptpTimescale = pApp->timePropertiesDS.GetPtpTimescale(); + FlagField.timeTraceable = pApp->timePropertiesDS.GetTimeTraceable(); + FlagField.frequencyTraceable = pApp->timePropertiesDS.GetFrequencyTraceable(); + + pSync->setCorrectionField( cTimeInterval() ); // Table 21 + + for( unsigned int i = 0; i < pSync->getReserved_2ArraySize(); i++ ) + { + pSync->setReserved_2( i, 0); + } + + pSync->setSourcePortIdentity( pPort->PortDS().PortIdentity() ); + pSync->setSequenceId( DrawSequId() ); + pSync->setControlField( PTP_MSG_CTRL_SYNC ); + pSync->setLogMessageInterval( pPort->PortDS().GetLogSyncInterval() ); + + // OMNeT specific stuff + pSync->setByteLength( PTP_MSG_SYNC_LEN ); + + return pSync; +} + +PTPv2_Follow_UpFrame * +cAppSync::CreateFollowUpFrame( PTPv2_SyncFrame *pSync ) +{ + // Follow up message specific stuff, 13.7.2 + + PTPv2_Follow_UpFrame *pFollowUp = new PTPv2_Follow_UpFrame; + + pFollowUp->setPreciseOriginTimestamp( pSync->getOriginTimestamp() ); + + // Generic PTP header, 13.3.2 + pFollowUp->setTransportSpecific(0); + pFollowUp->setMessageType(PTP_TYPE_FOLLOW_UP); + pFollowUp->setReserved_0(0); + pFollowUp->setVersionPTP( pSync->getVersionPTP() ); + pFollowUp->setMessageLength(PTP_MSG_FOLLOW_UP_LEN); + pFollowUp->setDomainNumber( pSync->getDomainNumber()); + pFollowUp->setReserved_1(0); + + pFollowUp->setFlagField( pSync->getFlagField() ); + pFollowUp->getFlagField().twoStepFlag = false; + + pFollowUp->setCorrectionField( cTimeInterval() ); + + for( unsigned int i = 0; i < pFollowUp->getReserved_2ArraySize(); i++ ) + { + pFollowUp->setReserved_2( i, 0); + } + + pFollowUp->setSourcePortIdentity( pSync->getSourcePortIdentity() ); + pFollowUp->setSequenceId( pSync->getSequenceId() ); + pFollowUp->setControlField( PTP_MSG_CTRL_FOLLOW_UP ); + pFollowUp->setLogMessageInterval( pSync->getLogMessageInterval() ); // Table 24 + + // OMNeT specific stuff + pFollowUp->setByteLength( PTP_MSG_FOLLOW_UP_LEN ); + + return pFollowUp; +} + +// ------------------------------------------------------ +// Synchronization functions +// ------------------------------------------------------ +void +cAppSync::SynchronizeClock +( + simtime_t SyncIngress, + simtime_t originTimestamp, + simtime_t SyncCorr, + simtime_t FollowUpCorr +) +{ + // Secion 11.2 + // = - - - correctionField of Sync message + // = - - - correctionField of Sync message - correctionField of Follow_Up message. + + simtime_t offsetFromMaster_raw; + simtime_t offsetFromMaster; + simtime_t meanPathDelay; + + if( pPort->PortDS().GetDelayMechanism() == DELAY_MECH_E2E ) + { + meanPathDelay = pApp->currentDS.GetMeanPathDelay().GetSimTime(); + } + else if( pPort->PortDS().GetDelayMechanism() == DELAY_MECH_P2P ) + { + meanPathDelay = pPort->PortDS().GetPeerMeanPathDelay().GetSimTime(); + } + else + { + meanPathDelay = SIMTIME_ZERO; + } + + offsetFromMaster_raw = SyncIngress - originTimestamp - meanPathDelay - SyncCorr - FollowUpCorr; + + pApp->EmitSignal_offsetFromMaster_raw( offsetFromMaster_raw ); + + pFilter->push( offsetFromMaster_raw ); + offsetFromMaster = pFilter->pop(); + + pApp->SetOffsetFromMaster( offsetFromMaster ); + + if( EnableDebugOutput ) + { + EV << "Synchronizing clock" << endl; + EV << " SyncIngress: " << SyncIngress << endl; + EV << " originTimestamp: " << originTimestamp << endl; + EV << " SyncCorr: " << SyncCorr << endl; + EV << " FollowUpCorr: " << FollowUpCorr << endl; + EV << " meanPathDelay: " << meanPathDelay << endl; + EV << " offsetFromMaster (raw): " << offsetFromMaster_raw << endl; + EV << " offsetFromMaster (filtered): " << offsetFromMaster << endl; + } + + SampleDecision_t SampleDec; + + SampleDec = pClockServo->Sample( offsetFromMaster, SyncIngress ); + + if( SampleDec.EnableJump ) + { + pClock->IncScaledTime( SampleDec.Delta ); + pApp->HandleTimejump( SampleDec.Delta ); + } + if( SampleDec.EnableScale ) + { + pClock->SetScaleFactor_ppb( SampleDec.ScaleFactor_ppb ); + } + + // Check if offset is inside allowed window + if( offsetFromMaster < SIMTIME_ZERO ) + { + offsetFromMaster = -offsetFromMaster; + } + + if( offsetFromMaster <= pApp->MaxOffsetFromMaster ) + { + pPort->HandleEvent( PORT_EVENT_MASTER_CLOCK_SELECTED ); + } + else + { + pPort->HandleEvent( PORT_EVENT_SYNCHRONIZATION_FAULT ); + } +} + +void +cAppSync::HandleSync( PTPv2_SyncFrame *pSync ) +{ + pParentModule->emit( SyncRcvd_SigId, pSync->getSequenceId() ); + + // State machine in Figure 30 + + portState_t PortState = pPort->PortDS().GetPortState(); + + if + ( + ( PortState == PORT_STATE_INITIALIZING ) || + ( PortState == PORT_STATE_DISABLED ) + ) + { + return; + } + + if( PortState == PORT_STATE_FAULTY ) + { + // Optional implementation specific handling of sync message would be possible here + return; + } + + // Message from current master? + if( pApp->parentDS.ParentPortIdentity() != pSync->getSourcePortIdentity() ) + { + return; + } + + if + ( + ( PortState == PORT_STATE_SLAVE ) || + ( PortState == PORT_STATE_UNCALIBRATED ) + ) + { + // Accept frame --> use frame info + pPort->PortDS().SetLogSyncInterval( pSync->getLogMessageInterval() ); + pClockServo->SetSyncInterval( pow(2.0, pSync->getLogMessageInterval()) ); + + if( pSync->getFlagField().twoStepFlag ) + { + // Save timestamps + SequId + SyncIngress = pSync->getIngressTimeStamp().GetTime(); + SyncCorr = pSync->getCorrectionField().GetSimTime(); + SyncSequID = pSync->getSequenceId(); + } + else + { + SynchronizeClock( pSync->getIngressTimeStamp().GetTime(), + pSync->getOriginTimestamp().GetSimTime(), + pSync->getCorrectionField().GetSimTime(), + SIMTIME_ZERO ); + } + } +} + +void +cAppSync::HandleFollowUp( PTPv2_Follow_UpFrame *pFollowUp ) +{ + pParentModule->emit( SyncFuRcvd_SigId, pFollowUp->getSequenceId() ); + + // State machine in Figure 31 + + portState_t PortState = pPort->PortDS().GetPortState(); + + if + ( + ( PortState == PORT_STATE_INITIALIZING ) || + ( PortState == PORT_STATE_DISABLED ) + ) + { + return; + } + + if( PortState == PORT_STATE_FAULTY ) + { + // Optional implementation specific handling of announce message would be possible here + return; + } + + // Message from current master? + if( pApp->parentDS.ParentPortIdentity() != pFollowUp->getSourcePortIdentity() ) + { + return; + } + + // Only accept associated follow ups + if( pFollowUp->getSequenceId() != SyncSequID ) + { + return; + } + + if + ( + ( PortState == PORT_STATE_SLAVE ) || + ( PortState == PORT_STATE_UNCALIBRATED ) + ) + { + SynchronizeClock( SyncIngress, + pFollowUp->getPreciseOriginTimestamp().GetSimTime(), + SyncCorr, + pFollowUp->getCorrectionField().GetSimTime() ); + } +} + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cAppSync::cAppSync() + : cFilteredPortService() +{ +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +void +cAppSync::ParseResourceParameters() +{ + cPortService::ParseResourceParameters(); + + FilterType = cSimTimeFilter_ParameterParser::ParseSimTimeFilterType( pParentModule->par( "offsetFromMasterFilter_Type" ) ); + FilterLen = pParentModule->par( "offsetFromMasterFilter_Len" ).longValue(); + FilterDiscardMinMax = pParentModule->par( "offsetFromMasterFilter_DiscardMinMax" ).boolValue(); +} + +void +cAppSync::ParseParameters() +{ + cPortService::ParseParameters(); + + EnableDebugOutput = pParentModule->par( "Port_Sync_EnableDebugOutput" ).boolValue(); +} + +void +cAppSync::RegisterSignals() +{ + cPortService::RegisterSignals(); + + SyncRcvd_SigId = pPort->RegisterDynamicSignal( "SyncRcvd" ); + SyncFuRcvd_SigId = pPort->RegisterDynamicSignal( "SyncFuRcvd" ); +} + +void +cAppSync::InitInternalState() +{ + cPortService::InitInternalState(); +} + +void +cAppSync::InitSignals() +{ + cPortService::InitSignals(); +} + +void +cAppSync::SetHierarchy( PTP_Stack *pApp, cPTP_Port *pPort, cScheduleClock *pClock, IClockServo *pClockServo ) +{ + cPortService::SetHierarchy( pApp, pPort, pClock ); + + this->pClock = pClock; + this->pClockServo = pClockServo; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cAppSync::StartListening() +{ +} +void +cAppSync::StopListening() +{ +} + +void +cAppSync::HandleIntervalEvent() +{ + PTPv2_SyncFrame *pSync = CreateSyncFrame(); + + pPort->IssueFrame(pSync); + + if( pApp->defaultDS.GetTwoStepFlag() ) + { + pPort->IssueFrame( CreateFollowUpFrame( pSync ) ); + } +} + +void +cAppSync::HandleTimeoutEvent() +{ + StopTimeout(); + + pPort->HandleEvent( PORT_EVENT_QUALIFICATION_TIMEOUT ); +} + +void +cAppSync::HandleMsg( PTPv2_Frame *pPtpFrame ) +{ + switch( pPtpFrame->getMessageType() ) + { + case PTP_TYPE_SYNC: HandleSync( check_and_cast(pPtpFrame) ); + break; + + case PTP_TYPE_FOLLOW_UP: HandleFollowUp( check_and_cast(pPtpFrame) ); + break; + + default: + break; + } +} + +void +cAppSync::TriggerFollowUp( PTPv2_SyncFrame *pSync ) +{ + PTPv2_Follow_UpFrame *pFollowUp; + + pFollowUp = CreateFollowUpFrame( pSync ); + + pPort->IssueFrame( pFollowUp ); +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ + diff --git a/src/Software/PTP_Stack/AppServices/Sync/AppSync.h b/src/Software/PTP_Stack/AppServices/Sync/AppSync.h new file mode 100644 index 0000000..8e8e85c --- /dev/null +++ b/src/Software/PTP_Stack/AppServices/Sync/AppSync.h @@ -0,0 +1,121 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_APP_SYNC_H_ +#define LIBPTP_APP_SYNC_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP.h" +#include "FilteredPortService.h" + +#include "IClockServo.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cAppSync : public cFilteredPortService +{ + private: + + // Configuration + std::string ClockServoPath; + std::string ClockPath; + + // Resources + IClockServo *pClockServo; + cScheduleClock *pClock; + + // TwoStepFlag stuff + simtime_t SyncIngress; + simtime_t SyncCorr; + uint16_t SyncSequID; + + // Syntonization support + simtime_t OldSyncIngress; + simtime_t OldMasterTime; + + // Signals for statistics + simsignal_t SyncRcvd_SigId; + simsignal_t SyncFuRcvd_SigId; + + // Debug config + bool EnableDebugOutput; + + // Frame creation + PTPv2_SyncFrame *CreateSyncFrame(); + PTPv2_Follow_UpFrame *CreateFollowUpFrame( PTPv2_SyncFrame *pSync ); + + void HandleSync ( PTPv2_SyncFrame *pSync ); + void HandleFollowUp( PTPv2_Follow_UpFrame *pFollowUp ); + + // Synchronization functions + void SynchronizeClock( simtime_t SyncIngress, + simtime_t originTimestamp, + simtime_t SyncCorr, + simtime_t FollowUpCorr ); + + // Init API + void ParseResourceParameters(); + void ParseParameters(); + void RegisterSignals(); + void InitInternalState(); + void InitSignals(); + + protected: + + public: + + // Constructors + cAppSync(); + + // Destructor + + // Setters + + // Getters + + // API functions + void StartListening(); + void StopListening(); + void HandleMsg( PTPv2_Frame *pPtpFrame ); + void TriggerFollowUp( PTPv2_SyncFrame *pSync ); + + void HandleIntervalEvent(); + void HandleTimeoutEvent(); + + // Init API + void SetHierarchy( PTP_Stack *pApp, cPTP_Port *pPort, cScheduleClock *pClock, IClockServo *pClockServo ); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.cc new file mode 100644 index 0000000..bcd4b6f --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.cc @@ -0,0 +1,175 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_CurrentDS.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cCurrentDS::cCurrentDS() + : cSubmoduleInitBase() +{ + // Data set specific variables + this->stepsRemoved = 0; + this->meanPathDelay = 0; + this->offsetFromMaster = 0; +} + +// ------------------------------------------------------ +// Initialization +// ------------------------------------------------------ +void +cCurrentDS::RegisterSignals() +{ + offsetFromMaster_SigId = pParentModule->registerSignal("offsetFromMaster"); + stepsRemoved_SigId = pParentModule->registerSignal("stepsRemoved"); + meanPathDelay_SigId = pParentModule->registerSignal("meanPathDelay"); +} + +void +cCurrentDS::InitSignals() +{ + pParentModule->emit( stepsRemoved_SigId, this->stepsRemoved ); + pParentModule->emit( offsetFromMaster_SigId, this->offsetFromMaster.GetSimTime() ); + pParentModule->emit( meanPathDelay_SigId, this->meanPathDelay.GetSimTime() ); +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cCurrentDS::SetStepsRemoved( size_t stepsRemoved ) +{ + this->stepsRemoved = stepsRemoved; + + pParentModule->emit( stepsRemoved_SigId, this->stepsRemoved ); +} + +void +cCurrentDS::SetOffsetFromMaster( cTimeInterval offsetFromMaster ) +{ + this->offsetFromMaster = offsetFromMaster; + + pParentModule->emit( offsetFromMaster_SigId, this->offsetFromMaster.GetSimTime() ); +} + +void +cCurrentDS::SetMeanPathDelay( cTimeInterval meanPathDelay ) +{ + if( this->meanPathDelay != meanPathDelay ) + { + pParentModule->emit( meanPathDelay_SigId, meanPathDelay.GetSimTime() ); + } + + this->meanPathDelay = meanPathDelay; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +size_t +cCurrentDS::GetStepsRemoved() +{ + return this->stepsRemoved; +} + +cTimeInterval +cCurrentDS::GetOffsetFromMaster() +{ + return this->offsetFromMaster; +} + +cTimeInterval cCurrentDS::GetMeanPathDelay() +{ + return this->meanPathDelay; +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cCurrentDS::operator== (const cCurrentDS& other) +{ + if + ( + ( this->stepsRemoved == other.stepsRemoved ) && + ( this->offsetFromMaster == other.offsetFromMaster ) && + ( this->meanPathDelay == other.meanPathDelay ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cCurrentDS& +cCurrentDS::operator= (const cCurrentDS& other) +{ + this->stepsRemoved = other.stepsRemoved; + this->offsetFromMaster = other.offsetFromMaster; + this->meanPathDelay = other.meanPathDelay; + + // By convention, always return *this + return *this; +} + +void +cCurrentDS::Print() +{ + EV << "===============================================================" << endl; + EV << " Current Data Set" << endl; + EV << "===============================================================" << endl << endl; + + EV << "StepsRemoved:\t" << this->stepsRemoved << endl; + EV << "OffsetFromMaster:\t" << this->offsetFromMaster.GetString() << endl; + EV << "MeanPathDelay:\t" << this->meanPathDelay.GetString() << endl << endl; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.h new file mode 100644 index 0000000..04b7b54 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_CurrentDS.h @@ -0,0 +1,100 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_CURRENTDS_H_ +#define LIBPTP_PTP_CURRENTDS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_TimeInterval.h" +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Current data set, see clause 8.2.2 in IEEE 1588-2008 +class cCurrentDS: public cSubmoduleInitBase +{ + private: + + // Resources + + // Static members + // -- + + // Dynamic members + size_t stepsRemoved; // + cTimeInterval offsetFromMaster; // + cTimeInterval meanPathDelay; // + + // Configurable members + // -- + + // ------------------------------------------------------------ + // Signals for statistics + // ------------------------------------------------------------ + + // Current DS + simsignal_t stepsRemoved_SigId; + simsignal_t offsetFromMaster_SigId; + simsignal_t meanPathDelay_SigId; + + protected: + + public: + // Constructors/Destructor + cCurrentDS(); + + // Init API + void RegisterSignals(); + void InitSignals(); + + // Instance methods + + // Setters + void SetStepsRemoved ( size_t stepsRemoved ); + void SetOffsetFromMaster ( cTimeInterval offsetFromMaster ); + void SetMeanPathDelay ( cTimeInterval meanPathDelay ); + + // Getters + size_t GetStepsRemoved(); + cTimeInterval GetOffsetFromMaster(); + cTimeInterval GetMeanPathDelay(); + + // Operators + bool operator== (const cCurrentDS& other); + cCurrentDS& operator= (const cCurrentDS& other); + + // Debug functions + void Print(); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.cc new file mode 100644 index 0000000..d707e65 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.cc @@ -0,0 +1,210 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_DefaultDS.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cDefaultDS::cDefaultDS() +{ + this->twoStepFlag = true; + this->clockIdentity = ""; + + this->numberPorts = 0; + this->clockQuality.SetClockClass(CLOCK_CLASS_DEFAULT); + this->clockQuality.SetClockAccuracy(CLOCK_ACCURACY_UNKNOWN); + this->clockQuality.SetOffsetScaledLogVar(0); + + this->priority1 = 0; + this->priority2 = 0; + this->domainNumber = DOMAIN_DEFAULT; + this->slaveOnly = false; + + this->pApp = nullptr; +} + +// ------------------------------------------------------ +// Initialization +// ------------------------------------------------------ +void +cDefaultDS::SetHierarchy( PTP_Stack *pApp ) +{ + this->pApp = pApp; +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cClockIdentity& +cDefaultDS::ClockIdentity() +{ + return this->clockIdentity; +} + +cClockQuality& +cDefaultDS::ClockQuality() +{ + return this->clockQuality; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cDefaultDS::SetTwoStepFlag( bool twoStepFlag ) +{ + this->twoStepFlag = twoStepFlag; +} + +void +cDefaultDS::SetNumberPorts( size_t numberPorts ) +{ + this->numberPorts = numberPorts; +} + +void +cDefaultDS::SetPriority1( uint8_t priority1 ) +{ + this->priority1 = priority1; +} + +void +cDefaultDS::SetPriority2( uint8_t priority2) +{ + this->priority2 = priority2; +} + +void +cDefaultDS::SetDomainNumber( domainNumber_t domainNumber) +{ + this->domainNumber = domainNumber; +} + +void +cDefaultDS::SetSlaveOnly( bool slaveOnly) +{ + this->slaveOnly = slaveOnly; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +bool +cDefaultDS::GetTwoStepFlag() const +{ + return this->twoStepFlag; +} + +size_t +cDefaultDS::GetNumberPorts() const +{ + return this->numberPorts; +} + +uint8_t +cDefaultDS::GetPriority1() const +{ + return this->priority1; +} + +uint8_t +cDefaultDS::GetPriority2() const +{ + return this->priority2; +} + +domainNumber_t +cDefaultDS::GetDomainNumber() const +{ + return this->domainNumber; +} + +bool +cDefaultDS::GetSlaveOnly() const +{ + return this->slaveOnly; +} + +cClockIdentity +cDefaultDS::GetClockIdentity() const +{ + return this->clockIdentity; +} + +cClockQuality +cDefaultDS::GetClockQuality() const +{ + return this->clockQuality; +} + +int +cDefaultDS::GetModuleID() const +{ + return pApp->GetModuleID(); +} + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cDefaultDS::Print() +{ + EV << "===============================================================" << endl; + EV << " Default Data Set" << endl; + EV << "===============================================================" << endl << endl; + + EV << "TwoStepFlag:\t" << this->twoStepFlag << endl; + EV << "ClockIdentity:\t" << this->clockIdentity.GetString() << endl; + EV << "Number of Ports:\t" << this->numberPorts << endl; + EV << "ClockQuality:\t" << this->clockQuality.GetString() << endl; + + EV << "Priority1:\t" << (int) this->priority1 << endl; + EV << "Priority2:\t" << (int) this->priority2 << endl; + EV << "DomainNumber:\t" << this->domainNumber << endl; + EV << "SlaveOnly:\t" << this->slaveOnly << endl << endl; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.h new file mode 100644 index 0000000..aace54d --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_DefaultDS.h @@ -0,0 +1,104 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_DEFAULTDS_H_ +#define LIBPTP_PTP_DEFAULTDS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_ClockIdentity.h" +#include "PTP_ClockQuality.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_Stack; + +// ====================================================== +// Declarations +// ====================================================== + +// Default data set, see 8.2.1 +class cDefaultDS +{ + private: + + // Resources + PTP_Stack *pApp; + + // Static members + bool twoStepFlag; // On-the-fly mode or FollowUp frames + cClockIdentity clockIdentity; // + size_t numberPorts; // + + // Dynamic members + cClockQuality clockQuality; // + + // Configurable members + uint8_t priority1; // + uint8_t priority2; // + domainNumber_t domainNumber; // + bool slaveOnly; // + + protected: + + public: + // Constructors/Destructor + cDefaultDS(); + + // Init + void SetHierarchy( PTP_Stack *pApp ); + + // Instance methods + cClockIdentity &ClockIdentity(); + cClockQuality &ClockQuality(); + + // Setters + void SetTwoStepFlag ( bool twoStepFlag ); + void SetNumberPorts ( size_t numberPorts); + void SetPriority1 ( uint8_t priority1); + void SetPriority2 ( uint8_t priority2); + void SetDomainNumber ( domainNumber_t domainNumber); + void SetSlaveOnly ( bool slaveOnly); + + // Getters + bool GetTwoStepFlag() const; + size_t GetNumberPorts() const; + uint8_t GetPriority1() const; + uint8_t GetPriority2() const; + domainNumber_t GetDomainNumber() const; + bool GetSlaveOnly() const; + cClockIdentity GetClockIdentity() const; + cClockQuality GetClockQuality() const; + int GetModuleID() const; + + // Debug functions + void Print(); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.cc new file mode 100644 index 0000000..35aee16 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.cc @@ -0,0 +1,657 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ForeignClockDS.h" + +#include +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cForeignClockDS::cForeignClockDS() +{ + this->Empty = true; + + this->ModuleID = -1; + + this->Priority1 = 0; + this->Priority2 = 0; + this->StepsRemoved = 0; + + this->flagField = cPtpHeaderFlags(); + this->currentUtcOffset = 0; + this->timeSource = TIME_SRC_OTHER; +} + +cForeignClockDS::cForeignClockDS(const cDefaultDS& defaultDS) +{ + this->operator=( defaultDS ); +} + +cForeignClockDS::cForeignClockDS(const PTPv2_AnnounceFrame *pAnn) +{ + this->operator=( pAnn ); +} + +cForeignClockDS::cForeignClockDS(const cForeignClockDS& other) +{ + this->Empty = other.Empty; + + this->Priority1 = other.Priority1; + this->ClockId = other.ClockId; + this->ClockQual = other.ClockQual; + this->Priority2 = other.Priority2; + this->StepsRemoved = other.StepsRemoved; + this->SenderPortId = other.SenderPortId; + this->ReceiverPortId = other.ReceiverPortId; + + this->ModuleID = other.ModuleID; + + this->flagField = other.flagField; + this->currentUtcOffset = other.currentUtcOffset; + this->timeSource = other.timeSource; +} + +cForeignClockDS::~cForeignClockDS() +{ +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cClockIdentity& +cForeignClockDS::ClockIdentity() +{ + return this->ClockId; +} + +cClockQuality& +cForeignClockDS::ClockQuality() +{ + return this->ClockQual; +} + +cPortIdentity& +cForeignClockDS::SenderPortIdentity() +{ + return this->SenderPortId; +} + +cPortIdentity& +cForeignClockDS::ReceiverPortIdentity() +{ + return this->ReceiverPortId; +} + +cPtpHeaderFlags& +cForeignClockDS::FlagsField() +{ + return this->flagField; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cForeignClockDS::SetPriority1( UInteger8 Priority1 ) +{ + this->Priority1 = Priority1; +} + +void +cForeignClockDS::SetPriority2( UInteger8 Priority2 ) +{ + this->Priority2 = Priority2; +} + +void +cForeignClockDS::SetStepsRemoved( UInteger16 StepsRemoved ) +{ + this->StepsRemoved = StepsRemoved; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +UInteger8 +cForeignClockDS::GetPriority1() const +{ + if( Empty ) + { + return std::numeric_limits::max(); + } + + return this->Priority1; +} + +UInteger8 +cForeignClockDS::GetPriority2() const +{ + if( Empty ) + { + return std::numeric_limits::max(); + } + + return this->Priority2; +} + +UInteger16 +cForeignClockDS::GetStepsRemoved() const +{ + if( Empty ) + { + return std::numeric_limits::max(); + } + + return this->StepsRemoved; +} + +timeSource_t +cForeignClockDS::GetTimeSource() const +{ + if( Empty ) + { + return TIME_SRC_OTHER; + } + + return this->timeSource; +} + +UInteger16 +cForeignClockDS::GetCurrentUtcOffset() const +{ + if( Empty ) + { + return std::numeric_limits::max(); + } + + return this->currentUtcOffset; +} + +int +cForeignClockDS::GetModuleID() const +{ + if( Empty ) + { + return -1; + } + + return this->ModuleID; +} + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cForeignClockDS::Clear() +{ + this->Empty = true; +} + +void +cForeignClockDS::Activate() +{ + this->Empty = false; +} + +bool +cForeignClockDS::IsEmpty() const +{ + return this->Empty; +} + +ClockCompReturn_t +cForeignClockDS::CompareClockDS( const cForeignClockDS& A, const cForeignClockDS& B, bool EnableDebugOutput ) +{ + ClockCompReturn_t ret; + + if( EnableDebugOutput ) + { + EV << "----------------------------------------------------------------" << endl; + EV << "Clock DataSet comparison" << endl; + EV << "----------------------------------------------------------------" << endl; + EV << "A :" << endl; + A.Print(); + EV << "----------------------------------------------------------------" << endl; + EV << "B :" << endl; + B.Print(); + EV << "----------------------------------------------------------------" << endl; + } + + // Check for empty and equal sets + if( B.IsEmpty() ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::OTHER_EMPTY; + return ret; + } + + if( A.IsEmpty() ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::OTHER_EMPTY; + return ret; + } + + // This function implements Figure 27 and Figure 28 of IEEE 1588-2008 + + // GM Identity of A == GM Identity of B + if( A.ClockId == B.ClockId ) + { + // Figure 28 + + // Compare StepsRemoved of A and B + if( A.StepsRemoved > B.StepsRemoved + 1 ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::STEPS_REMOVED; + return ret; + } + + if( A.StepsRemoved + 1 < B.StepsRemoved ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::STEPS_REMOVED; + return ret; + } + + if( A.StepsRemoved > B.StepsRemoved ) + { + // Compare Identities of Receiver of A and Sender of A + if( A.ReceiverPortId < A.SenderPortId ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::RCV_PORT_ID_SMALLER; + return ret; + } + else if( A.ReceiverPortId > A.SenderPortId ) + { + ret.Result = ClockCompResult::B_BETTER_A_BY_TOPO; + ret.Reason = ClockCompReason::RCV_PORT_ID_GREATER; + return ret; + } + + ret.Result = ClockCompResult::ERROR_1; + ret.Reason = ClockCompReason::NONE; + return ret; + } + + if( A.StepsRemoved < B.StepsRemoved ) + { + // Compare Identities of Receiver of B and Sender of B + if( B.ReceiverPortId < B.SenderPortId ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::RCV_PORT_ID_SMALLER; + return ret; + } + else if( B.ReceiverPortId > B.SenderPortId ) + { + ret.Result = ClockCompResult::A_BETTER_B_BY_TOPO; + ret.Reason = ClockCompReason::RCV_PORT_ID_GREATER; + return ret; + } + + ret.Result = ClockCompResult::ERROR_1; + ret.Reason = ClockCompReason::NONE; + return ret; + } + + // Compare identities of senders of A and B + if( A.SenderPortId > B.SenderPortId ) + { + ret.Result = ClockCompResult::B_BETTER_A_BY_TOPO; + ret.Reason = ClockCompReason::SND_PORT_ID; + return ret; + } + else if( A.SenderPortId < B.SenderPortId ) + { + ret.Result = ClockCompResult::A_BETTER_B_BY_TOPO; + ret.Reason = ClockCompReason::SND_PORT_ID; + return ret; + } + + // Compare Port Numbers of receivers of A and B + if( A.ReceiverPortId.GetPortNumber() > B.ReceiverPortId.GetPortNumber() ) + { + ret.Result = ClockCompResult::B_BETTER_A_BY_TOPO; + ret.Reason = ClockCompReason::RCV_PORT_NUMBER; + return ret; + } + else if( A.ReceiverPortId.GetPortNumber() < B.ReceiverPortId.GetPortNumber() ) + { + ret.Result = ClockCompResult::A_BETTER_B_BY_TOPO; + ret.Reason = ClockCompReason::RCV_PORT_NUMBER; + return ret; + } + + ret.Result = ClockCompResult::ERROR_2; + ret.Reason = ClockCompReason::NONE; + return ret; + } + else + { + // Figure 27 + + // Compare GM priority1 values of A and B + if( A.Priority1 > B.Priority1 ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::PRIORITY1; + return ret; + } + else if( A.Priority1 < B.Priority1 ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::PRIORITY1; + return ret; + } + + // Compare GM class values of A and B + if( A.ClockQual.GetClockClass() > B.ClockQual.GetClockClass() ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::CLOCK_CLASS; + return ret; + } + else if( A.ClockQual.GetClockClass() < B.ClockQual.GetClockClass() ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::CLOCK_CLASS; + return ret; + } + + // Compare GM accuracy values of A and B + if( A.ClockQual.GetClockAccuracy() > B.ClockQual.GetClockAccuracy() ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::CLOCK_ACCURACY; + return ret; + } + else if( A.ClockQual.GetClockAccuracy() < B.ClockQual.GetClockAccuracy() ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::CLOCK_ACCURACY; + return ret; + } + + // Compare GM offsetScaledLogVariance of A and B + if( A.ClockQual.GetOffsetScaledLogVar() > B.ClockQual.GetOffsetScaledLogVar() ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::OFFSET_LOG_VAR; + return ret; + } + else if( A.ClockQual.GetOffsetScaledLogVar() < B.ClockQual.GetOffsetScaledLogVar() ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::OFFSET_LOG_VAR; + return ret; + } + + // Compare GM priority2 values of A and B + if( A.Priority2 > B.Priority2 ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::PRIORITY2; + return ret; + } + else if( A.Priority2 < B.Priority2 ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::PRIORITY2; + return ret; + } + + // Compare GM identity valeus of A and B + if( A.ClockId > B.ClockId ) + { + ret.Result = ClockCompResult::B_BETTER_A; + ret.Reason = ClockCompReason::CLOCK_ID; + return ret; + } + else if( A.ClockId < B.ClockId ) + { + ret.Result = ClockCompResult::A_BETTER_B; + ret.Reason = ClockCompReason::CLOCK_ID; + return ret; + } + } + + throw cRuntimeError("Data Set Comparison algorithm: Invalid comparison" ); + + ret.Result = ClockCompResult::ERROR_INVALID; + ret.Reason = ClockCompReason::NONE; + return ret; +} + +// ------------------------------------------------------ +// Compare Operators +// ------------------------------------------------------ +bool +cForeignClockDS::operator== (const cForeignClockDS& other) const +{ + if( this->Empty || other.Empty ) + { + return false; + } + + if + ( + ( this->Priority1 == other.Priority1 ) && + ( this->ClockId == other.ClockId ) && + ( this->ClockQual == other.ClockQual ) && + ( this->Priority2 == other.Priority2 ) && + ( this->StepsRemoved == other.StepsRemoved ) && + ( this->SenderPortId == other.SenderPortId ) && + ( this->ReceiverPortId == other.ReceiverPortId ) && + ( this->ModuleID == other.ModuleID ) && + ( this->flagField == other.flagField ) && + ( this->currentUtcOffset == other.currentUtcOffset ) && + ( this->timeSource == other.timeSource ) + ) + { + return true; + } + else + { + return false; + } +} + +bool +cForeignClockDS::operator!= (const cForeignClockDS& other) const +{ + return !( this->operator ==(other) ); +} + +// ------------------------------------------------------ +// Assignment Operators +// ------------------------------------------------------ +cForeignClockDS& +cForeignClockDS::operator= (const cForeignClockDS& other) +{ + this->Empty = other.Empty; + + this->Priority1 = other.Priority1; + this->ClockId = other.ClockId; + this->ClockQual = other.ClockQual; + this->Priority2 = other.Priority2; + this->StepsRemoved = other.StepsRemoved; + this->SenderPortId = other.SenderPortId; + this->ReceiverPortId = other.ReceiverPortId; + + this->ModuleID = other.ModuleID; + + this->flagField = other.flagField; + this->currentUtcOffset = other.currentUtcOffset; + this->timeSource = other.timeSource; + + // By convention, always return *this + return *this; +} + +cForeignClockDS& +cForeignClockDS::operator= (const PTPv2_AnnounceFrame *pAnn) +{ + // Information source is used as specified in Table 12 of IEEE 1588-2008 + + this->Priority1 = pAnn->getGrandmasterPriority1(); + this->ClockId = pAnn->getGrandmasterIdentity(); + this->ClockQual = pAnn->getGrandmasterClockQuality(); + this->Priority2 = pAnn->getGrandmasterPriority2(); + this->StepsRemoved = pAnn->getStepsRemoved(); + this->SenderPortId = pAnn->getSourcePortIdentity(); + this->ReceiverPortId = cPortIdentity(); + + this->ModuleID = pAnn->getGrandMasterModuleID(); + + // Save Announce message specific stuff + this->flagField = pAnn->getFlagField(); + this->currentUtcOffset = pAnn->getCurrentUtcOffset(); + this->timeSource = (timeSource_t) pAnn->getTimeSource(); + + this->Empty = false; + + // By convention, always return *this + return *this; +} + +cForeignClockDS& +cForeignClockDS::operator= (const cDefaultDS& defaultDS) +{ + // Information source is used as specified in Table 12 of IEEE 1588-2008 + + this->Priority1 = defaultDS.GetPriority1(); + this->ClockId = defaultDS.GetClockIdentity(); + this->ClockQual = defaultDS.GetClockQuality(); + this->Priority2 = defaultDS.GetPriority2(); + this->StepsRemoved = 0; + this->SenderPortId = defaultDS.GetClockIdentity(); + this->ReceiverPortId = defaultDS.GetClockIdentity(); + + this->ModuleID = defaultDS.GetModuleID(); + + this->flagField = cPtpHeaderFlags(); + this->currentUtcOffset = 0; + this->timeSource = TIME_SRC_OTHER; + + this->Empty = false; + + // By convention, always return *this + return *this; +} + + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cForeignClockDS::Print() const +{ + if( Empty ) + { + EV << " Empty" << endl; + return; + } + + EV << "Priority1: " << static_cast(Priority1) << endl; + EV << "ClockId: " << ClockId << endl; + EV << "ClockQual: " << ClockQual << endl; + EV << "Priority2: " << static_cast(Priority2) << endl; + EV << "StepsRemoved: " << StepsRemoved << endl; + EV << "SenderPortId: " << SenderPortId << endl; + EV << "ReceiverPortId: " << ReceiverPortId << endl; + EV << "ModuleID: " << ModuleID << endl; + EV << "flagField: " << flagField << endl; + EV << "currentUtcOffset: " << currentUtcOffset << endl; + EV << "timeSource: " << timeSource << endl; + +} + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const ClockCompResult& o) +{ + switch(o) + { + case ClockCompResult::A_BETTER_B: os << "A better B"; break; + case ClockCompResult::A_BETTER_B_BY_TOPO: os << "A better B by topology"; break; + case ClockCompResult::B_BETTER_A: os << "B better A"; break; + case ClockCompResult::B_BETTER_A_BY_TOPO: os << "B better A by topology"; break; + case ClockCompResult::ERROR_1: os << "Error 1"; break; + case ClockCompResult::ERROR_2: os << "Error 2"; break; + case ClockCompResult::ERROR_INVALID: os << "Error (invalid)"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const ClockCompReason& o) +{ + switch(o) + { + case ClockCompReason::NONE: os << "No reason"; break; + case ClockCompReason::OTHER_EMPTY: os << "The other one is empty"; break; + case ClockCompReason::STEPS_REMOVED: os << "Steps removed"; break; + case ClockCompReason::RCV_PORT_ID_SMALLER: os << "Receiver PortID 1"; break; + case ClockCompReason::RCV_PORT_ID_GREATER: os << "Receiver PortID 2"; break; + case ClockCompReason::SND_PORT_ID: os << "Sender Port ID"; break; + case ClockCompReason::RCV_PORT_NUMBER: os << "Receiver Port number"; break; + case ClockCompReason::PRIORITY1: os << "Priority 1"; break; + case ClockCompReason::CLOCK_CLASS: os << "Clock class"; break; + case ClockCompReason::CLOCK_ACCURACY: os << "Clock accuracy"; break; + case ClockCompReason::OFFSET_LOG_VAR: os << "Offset log variance"; break; + case ClockCompReason::PRIORITY2: os << "Priority 2"; break; + case ClockCompReason::CLOCK_ID: os << "ClockID"; break; + } + + return os; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.h new file mode 100644 index 0000000..4f38741 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockDS.h @@ -0,0 +1,151 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_FOREIGN_CLOCK_DS_H_ +#define LIBPTP_PTP_FOREIGN_CLOCK_DS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_ClockIdentity.h" +#include "PTP_ClockQuality.h" +#include "PTP_PortIdentity.h" +#include "PTP_DefaultDS.h" + +#include "PTPv2_m.h" + +// ====================================================== +// Types +// ====================================================== + +enum class ClockCompResult +{ + A_BETTER_B, + A_BETTER_B_BY_TOPO, + B_BETTER_A, + B_BETTER_A_BY_TOPO, + ERROR_1, + ERROR_2, + ERROR_INVALID, +}; + +enum class ClockCompReason +{ + NONE, + OTHER_EMPTY, + STEPS_REMOVED, + RCV_PORT_ID_SMALLER, + RCV_PORT_ID_GREATER, + SND_PORT_ID, + RCV_PORT_NUMBER, + PRIORITY1, + CLOCK_CLASS, + CLOCK_ACCURACY, + OFFSET_LOG_VAR, + PRIORITY2, + CLOCK_ID, +}; + +struct ClockCompReturn_t +{ + ClockCompResult Result; + ClockCompReason Reason; +}; + +// ====================================================== +// Declarations +// ====================================================== + +std::ostream& operator<<(std::ostream& os, const ClockCompResult& o); +std::ostream& operator<<(std::ostream& os, const ClockCompReason& o); + +class cForeignClockDS +{ + private: + bool Empty; + + UInteger8 Priority1; + cClockIdentity ClockId; + cClockQuality ClockQual; + UInteger8 Priority2; + UInteger16 StepsRemoved; + cPortIdentity SenderPortId; + cPortIdentity ReceiverPortId; + + int ModuleID; + + cPtpHeaderFlags flagField; + uint16_t currentUtcOffset; + timeSource_t timeSource; + + protected: + + public: + + // Constructors/Destructor + cForeignClockDS(); + cForeignClockDS(const cDefaultDS& defaultDS); + cForeignClockDS(const PTPv2_AnnounceFrame *pAnn); + cForeignClockDS(const cForeignClockDS& other); + ~cForeignClockDS(); + + // Instance methods + cClockIdentity& ClockIdentity(); + cClockQuality& ClockQuality(); + cPortIdentity& SenderPortIdentity(); + cPortIdentity& ReceiverPortIdentity(); + cPtpHeaderFlags& FlagsField(); + + // Setters + void SetPriority1( UInteger8 Priority1 ); + void SetPriority2( UInteger8 Priority2 ); + void SetStepsRemoved( UInteger16 StepsRemoved ); + + // Getters + UInteger8 GetPriority1() const; + UInteger8 GetPriority2() const; + UInteger16 GetStepsRemoved() const; + timeSource_t GetTimeSource() const; + UInteger16 GetCurrentUtcOffset() const; + int GetModuleID() const; + + // API functions + void Clear(); + void Activate(); + bool IsEmpty() const; + static ClockCompReturn_t CompareClockDS(const cForeignClockDS& A, const cForeignClockDS& B, bool EnableDebugOutput); + + // Operators + bool operator== (const cForeignClockDS& other) const; + bool operator!= (const cForeignClockDS& other) const; + cForeignClockDS& operator= (const cForeignClockDS& other); + cForeignClockDS& operator= (const PTPv2_AnnounceFrame *pAnn); + cForeignClockDS& operator= (const cDefaultDS& defaultDS); + + // Debug functions + void Print() const; +}; + +#endif + diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.cc new file mode 100644 index 0000000..d5ad487 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.cc @@ -0,0 +1,130 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ForeignClockMsg.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ + +cForeignClockMsg::cForeignClockMsg() +{ + foreignClockDS = cForeignClockDS(); + RxTime = SIMTIME_ZERO; +} + +cForeignClockMsg::cForeignClockMsg( cForeignClockDS ForeignClockDS, simtime_t RxTime ) + : foreignClockDS( ForeignClockDS ), RxTime( RxTime ) +{ +} + +cForeignClockMsg::cForeignClockMsg(const cForeignClockMsg& other) +{ + this->foreignClockDS = other.foreignClockDS; + this->RxTime = other.RxTime; +} + +cForeignClockMsg::~cForeignClockMsg() +{ +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cForeignClockDS& +cForeignClockMsg::ForeignClockDS() +{ + return this->foreignClockDS; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cForeignClockMsg::SetRxTime( simtime_t RxTime ) +{ + this->RxTime = RxTime; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +simtime_t +cForeignClockMsg::GetRxTime() +{ + return this->RxTime; +} + +cForeignClockDS +cForeignClockMsg::GetForeignClockDS() +{ + return this->foreignClockDS; +} + +// ------------------------------------------------------ +// Compare Operators +// ------------------------------------------------------ +bool +cForeignClockMsg::operator<(const cForeignClockMsg& rhs) const +{ + return this->RxTime < rhs.RxTime; +} + +bool +cForeignClockMsg::operator>(const cForeignClockMsg& rhs) const +{ + return this->RxTime > rhs.RxTime; +} + +cForeignClockMsg& +cForeignClockMsg::operator= (const cForeignClockMsg& other) +{ + this->foreignClockDS = other.foreignClockDS; + this->RxTime = other.RxTime; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.h new file mode 100644 index 0000000..ab0c08b --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignClockMsg.h @@ -0,0 +1,73 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_FOREIGN_CLOCK_MSG_H_ +#define LIBPTP_PTP_FOREIGN_CLOCK_MSG_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ForeignClockDS.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cForeignClockMsg +{ + private: + + cForeignClockDS foreignClockDS; + simtime_t RxTime; + + public: + + // Constructors/Destructor + cForeignClockMsg(); + cForeignClockMsg( cForeignClockDS ForeignClockDS, simtime_t RxTime ); + cForeignClockMsg(const cForeignClockMsg& other); + ~cForeignClockMsg(); + + // Instance methods + cForeignClockDS &ForeignClockDS(); + + // Setters + void SetRxTime( simtime_t RxTime ); + + // Getters + simtime_t GetRxTime(); + cForeignClockDS GetForeignClockDS(); + + // Operators + bool operator<(const cForeignClockMsg& rhs) const; + bool operator>(const cForeignClockMsg& rhs) const; + + cForeignClockMsg& operator= (const cForeignClockMsg& other); +}; + +#endif + diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.cc new file mode 100644 index 0000000..9a0b354 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.cc @@ -0,0 +1,304 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ForeignMasterDS.h" +#include "PTP_ForeignClockDS.h" + +#include "PTP_Port.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ +cForeignMasterDS::cForeignMasterDS() +{ + this->pPort = nullptr; + this->pClock = nullptr; + this->ForeignMasterTimeWindow = SIMTIME_ZERO; + + this->Erbest = cForeignClockMsg(); +} + +cForeignMasterDS::cForeignMasterDS( const cForeignMasterDS& other ) +{ + this->pPort = other.pPort; + this->pClock = other.pClock; + this->ForeignMasterTimeWindow = other.ForeignMasterTimeWindow; + + this->ForeignMasters = other.ForeignMasters; + this->Erbest = other.Erbest; +} + +cForeignMasterDS::~cForeignMasterDS() +{ +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Init functions +// ------------------------------------------------------ +void +cForeignMasterDS::SetHierarchy( cPTP_Port *pPort, cScheduleClock *pClock ) +{ + this->pPort = pPort; + this->pClock = pClock; +} + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ +void +cForeignMasterDS::RegisterSignals() +{ + ForeinMasterDS_Cnt_SigId = pPort->RegisterDynamicSignal( "ForeignMasterDS_Cnt" ); +} + +void +cForeignMasterDS::InitSignals() +{ + pParentModule->emit( ForeinMasterDS_Cnt_SigId, 0 ); +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +void +cForeignMasterDS::SetForeignMasterTimeWindow( simtime_t ForeignMasterTimeWindow ) +{ + this->ForeignMasterTimeWindow = ForeignMasterTimeWindow; + + for(std::vector::iterator it = ForeignMasters.begin(); it != ForeignMasters.end(); ++it) + { + it->SetForeignMasterTimeWindow( ForeignMasterTimeWindow ); + } +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ + +void +cForeignMasterDS::CalculateErbest() +{ + Erbest.ForeignClockDS().Clear(); + + // Loop over all remaining entries and try to find a new Erbest + for(std::vector::iterator it = ForeignMasters.begin(); it != ForeignMasters.end(); ++it) + { + if( it->GetNumEntries() >= PTP_FOREIGN_MASTER_THRESHOLD ) + { + cForeignClockMsg Candidate = it->GetMostRecentEntry(); + ClockCompReturn_t CompReturn = cForeignClockDS::CompareClockDS( Erbest.GetForeignClockDS(), Candidate.GetForeignClockDS(), false ); + + switch( CompReturn.Result ) + { + case ClockCompResult::B_BETTER_A: + case ClockCompResult::B_BETTER_A_BY_TOPO: + + Erbest = Candidate; + break; + + default: + break; + } + } + } +} + +bool +cForeignMasterDS::IsErbestValid() +{ + simtime_t Now = pClock->GetScaledTime(); + + if( Now <= this->ForeignMasterTimeWindow ) + return true; + + if( Erbest.GetRxTime() >= (pClock->GetScaledTime() - this->ForeignMasterTimeWindow) ) + { + return true; + } + else + { + return false; + } +} + +void +cForeignMasterDS::PushForeignClock( cForeignClockDS ForeignClockDS, simtime_t RxTime ) +{ + // Get rid of old values + std::vector::iterator it = ForeignMasters.begin(); + while( it != ForeignMasters.end() ) + { + if( it->IsEmpty() ) + { + // erase returns the new iterator + it = ForeignMasters.erase( it ); + } + else + { + ++it; + } + } + pParentModule->emit( ForeinMasterDS_Cnt_SigId, ForeignMasters.size() ); + + // Try to find existing entry with that ID + bool NewDataFlag; + + for(std::vector::iterator it = ForeignMasters.begin(); it != ForeignMasters.end(); ++it) + { + if( ForeignClockDS.SenderPortIdentity() == it->PortIdentity() ) + { + it->PushForeignClock( ForeignClockDS, RxTime, NewDataFlag ); + + // TODO: Check if Erbest is still valid + + if( Erbest.ForeignClockDS().SenderPortIdentity() == ForeignClockDS.SenderPortIdentity() ) + { + if( NewDataFlag ) + { + CalculateErbest(); + } + else + { + Erbest.SetRxTime( RxTime ); + } + } + else if( NewDataFlag ) + { + ClockCompReturn_t CompReturn = cForeignClockDS::CompareClockDS( Erbest.ForeignClockDS(), ForeignClockDS, false ); + + switch( CompReturn.Result ) + { + case ClockCompResult::B_BETTER_A: + case ClockCompResult::B_BETTER_A_BY_TOPO: + + Erbest = cForeignClockMsg( ForeignClockDS, RxTime ); + break; + + default: + break; + } + } + + return; + } + } + + // Add a new entry + cForeignMasterDS_Entry NewEntry; + + // Initialize new entry + NewEntry.SetClock( pClock ); + NewEntry.SetForeignMasterTimeWindow( ForeignMasterTimeWindow ); + NewEntry.PortIdentity() = ForeignClockDS.SenderPortIdentity(); + + NewEntry.PushForeignClock( ForeignClockDS, RxTime, NewDataFlag ); + + ForeignMasters.push_back( NewEntry ); + + pParentModule->emit( ForeinMasterDS_Cnt_SigId, ForeignMasters.size() ); +} + +size_t +cForeignMasterDS::GetNumEntries( cPortIdentity foreignMasterPortIdentity ) +{ + // Try to find existing entry + for(std::vector::iterator it = ForeignMasters.begin(); it != ForeignMasters.end(); ++it) + { + if( foreignMasterPortIdentity == it->PortIdentity() ) + { + return it->GetNumEntries(); + } + } + + // If entry is not found, return 0 + return 0; +} + +cForeignClockDS +cForeignMasterDS::GetErbest() +{ + if( !IsErbestValid() ) + { + CalculateErbest(); + } + + return Erbest.ForeignClockDS(); +} + +void +cForeignMasterDS::AdjustTimestamps( simtime_t Delta ) +{ + for(std::vector::iterator it = ForeignMasters.begin(); it != ForeignMasters.end(); ++it) + { + it->AdjustTimestamps( Delta ); + } +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +cForeignMasterDS& +cForeignMasterDS::operator=( const cForeignMasterDS& other ) +{ + this->pPort = other.pPort; + this->pClock = other.pClock; + this->ForeignMasterTimeWindow = other.ForeignMasterTimeWindow; + + this->ForeignMasters = other.ForeignMasters; + this->Erbest = other.Erbest; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.h new file mode 100644 index 0000000..8a49eaf --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS.h @@ -0,0 +1,106 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_FOREIGN_MASTER_DS_H_ +#define LIBPTP_PTP_FOREIGN_MASTER_DS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "ScheduleClock.h" + +#include "PTP_PortIdentity.h" +#include "PTP_ForeignMasterDS_Entry.h" +#include "PTP_ForeignClockDS.h" +#include "PTP_ForeignClockMsg.h" + +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +class cPTP_Port; + +// ====================================================== +// Declarations +// ====================================================== + +// Foreign Master data set, see clause 9.3.2.4 +class cForeignMasterDS : public cSubmoduleInitBase +{ + private: + + // Resources + cPTP_Port *pPort; + cScheduleClock *pClock; + std::vector ForeignMasters; + + // Configuration + simtime_t ForeignMasterTimeWindow; + + cForeignClockMsg Erbest; + + // Signals for statistics + simsignal_t ForeinMasterDS_Cnt_SigId; + + // Internal functions + void CalculateErbest(); + bool IsErbestValid(); + + protected: + + public: + + // Constructors/Destructor + cForeignMasterDS(); + cForeignMasterDS( const cForeignMasterDS& other ); + ~cForeignMasterDS(); + + // Instance methods + + // Initialize + void SetHierarchy( cPTP_Port *pPort, cScheduleClock *pClock ); + + // Init API + void RegisterSignals(); + void InitSignals(); + + // Setters + void SetForeignMasterTimeWindow( simtime_t ForeignMasterTimeWindow ); + + // Getters + + // API functions + void PushForeignClock( cForeignClockDS ForeignClockDS, simtime_t RxTime ); + size_t GetNumEntries( cPortIdentity foreignMasterPortIdentity ); + cForeignClockDS GetErbest(); + void AdjustTimestamps( simtime_t Delta ); + + // Operators + cForeignMasterDS& operator=( const cForeignMasterDS& other ); + + // Debug functions +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.cc new file mode 100644 index 0000000..9ace16f --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.cc @@ -0,0 +1,204 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ForeignMasterDS_Entry.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cForeignMasterDS_Entry::cForeignMasterDS_Entry() +{ + this->pClock = nullptr; + this->ForeignMasterTimeWindow = SIMTIME_ZERO; +} + +cForeignMasterDS_Entry::cForeignMasterDS_Entry( const cForeignMasterDS_Entry& other ) +{ + this->pClock = other.pClock; + this->ForeignMasterTimeWindow = other.ForeignMasterTimeWindow; + + this->ForeignClockMsgs = other.ForeignClockMsgs; + this->foreignMasterPortIdentity = other.foreignMasterPortIdentity; +} + +cForeignMasterDS_Entry::~cForeignMasterDS_Entry() +{ +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cPortIdentity& +cForeignMasterDS_Entry::PortIdentity() +{ + return this->foreignMasterPortIdentity; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cForeignMasterDS_Entry::SetClock( cScheduleClock *pClock ) +{ + this->pClock = pClock; +} + +void +cForeignMasterDS_Entry::SetForeignMasterTimeWindow( simtime_t ForeignMasterTimeWindow ) +{ + this->ForeignMasterTimeWindow = ForeignMasterTimeWindow; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +simtime_t +cForeignMasterDS_Entry::GetForeignMasterTimeWindow() +{ + return this->ForeignMasterTimeWindow; +} + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cForeignMasterDS_Entry::PushForeignClock( cForeignClockDS ForeignClockDS, simtime_t RxTime, bool &NewDataFlag ) +{ + // Check if the newly added entry makes the already available information valid + if( ForeignClockMsgs.size() + 1 == PTP_FOREIGN_MASTER_THRESHOLD ) + { + NewDataFlag = true; + } + else if + ( + ( ForeignClockMsgs.size() >= PTP_FOREIGN_MASTER_THRESHOLD ) && + ( ForeignClockMsgs.back().ForeignClockDS() != ForeignClockDS ) + ) + { + NewDataFlag = true; + } + else + { + NewDataFlag = false; + } + + // Add entry to queue + ForeignClockMsgs.push_back( cForeignClockMsg( ForeignClockDS, RxTime ) ); +} + +cForeignClockMsg +cForeignMasterDS_Entry::GetMostRecentEntry() +{ + if( IsEmpty() ) + { + return cForeignClockMsg(); + } + else + { + return ForeignClockMsgs.back(); + } +} + +size_t +cForeignMasterDS_Entry::GetNumEntries() +{ + CleanUp(); + + return ForeignClockMsgs.size(); +} + +bool +cForeignMasterDS_Entry::IsEmpty() +{ + CleanUp(); + + return ForeignClockMsgs.empty(); +} + +void +cForeignMasterDS_Entry::CleanUp() +{ + // Get minimal scaled time that is inside the window + simtime_t MinScaledTime = pClock->GetScaledTime() - this->ForeignMasterTimeWindow; + + // Drop all timestamps that are below the minimal time + while( !ForeignClockMsgs.empty() ) + { + if( ForeignClockMsgs.front().GetRxTime() < MinScaledTime ) + { + ForeignClockMsgs.pop_front(); + } + else + { + break; + } + } +} + +void +cForeignMasterDS_Entry::AdjustTimestamps( simtime_t Delta ) +{ + for( std::list::iterator it = ForeignClockMsgs.begin(); it != ForeignClockMsgs.end(); it ++ ) + { + it->SetRxTime( it->GetRxTime() + Delta ); + } +} + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +cForeignMasterDS_Entry& +cForeignMasterDS_Entry::operator=( const cForeignMasterDS_Entry& other ) +{ + this->pClock = other.pClock; + this->ForeignMasterTimeWindow = other.ForeignMasterTimeWindow; + + this->ForeignClockMsgs = other.ForeignClockMsgs; + this->foreignMasterPortIdentity = other.foreignMasterPortIdentity; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.h new file mode 100644 index 0000000..b1697e7 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ForeignMasterDS_Entry.h @@ -0,0 +1,86 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_FOREIGNMASTERDS_ENTRY_H_ +#define LIBPTP_PTP_FOREIGNMASTERDS_ENTRY_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "ScheduleClock.h" + +#include "PTP_PortIdentity.h" +#include "PTP_ForeignClockDS.h" +#include "PTP_ForeignClockMsg.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cForeignMasterDS_Entry +{ + private: + + cPortIdentity foreignMasterPortIdentity; + std::list ForeignClockMsgs; + + // Internal resources + cScheduleClock *pClock; + simtime_t ForeignMasterTimeWindow; + + protected: + + public: + + // Constructors/Destructor + cForeignMasterDS_Entry(); + cForeignMasterDS_Entry( const cForeignMasterDS_Entry& other ); + ~cForeignMasterDS_Entry(); + + // Instance methods + cPortIdentity& PortIdentity(); + + // Setters + void SetClock( cScheduleClock *pClock ); + void SetForeignMasterTimeWindow( simtime_t ForeignMasterTimeWindow ); + + // Getters + simtime_t GetForeignMasterTimeWindow(); + + // API functions + void PushForeignClock( cForeignClockDS ForeignClockDS, simtime_t RxTime, bool &NewDataFlag ); + cForeignClockMsg GetMostRecentEntry(); + size_t GetNumEntries(); + bool IsEmpty(); + void CleanUp(); + void AdjustTimestamps( simtime_t Delta ); + + // Operators + cForeignMasterDS_Entry& operator=( const cForeignMasterDS_Entry& other ); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.cc new file mode 100644 index 0000000..55594ac --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.cc @@ -0,0 +1,235 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ParentDS.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cParentDS::cParentDS() + : cSubmoduleInitBase() +{ + // Data set specific variables + this->parentPortIdentity = cPortIdentity(); + this->parentStats = false; + this->observedParentOffsetScaledLogVariance = 0; + this->observedParentClockPhaseChangeRate = 0; + this->grandmasterIdentity = cClockIdentity(); + this->grandmasterClockQuality = cClockQuality(); + this->grandmasterPriority1 = 0; + this->grandmasterPriority2 = 0; +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cPortIdentity& +cParentDS::ParentPortIdentity() +{ + return this->parentPortIdentity; +} + +cClockQuality& +cParentDS::GrandmasterClockQuality() +{ + return this->grandmasterClockQuality; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cParentDS::SetParentStats( bool parentStats ) +{ + this->parentStats = parentStats; +} + +void +cParentDS::SetObservedParentOffsetScaledLogVariance( UInteger16 observedParentOffsetScaledLogVariance ) +{ + this->observedParentOffsetScaledLogVariance = observedParentOffsetScaledLogVariance; +} + +void +cParentDS::SetObservedParentClockPhaseChangeRate( Integer32 observedParentClockPhaseChangeRate ) +{ + this->observedParentClockPhaseChangeRate = observedParentClockPhaseChangeRate; +} + +void +cParentDS::SetGrandmasterIdentity( cClockIdentity grandmasterIdentity ) +{ + this->grandmasterIdentity = grandmasterIdentity; +} + +void +cParentDS::SetGrandmasterPriority1( uint8_t grandmasterPriority1 ) +{ + this->grandmasterPriority1 = grandmasterPriority1; +} + +void +cParentDS::SetGrandmasterPriority2( uint8_t grandmasterPriority2 ) +{ + this->grandmasterPriority2 = grandmasterPriority2; +} + +void +cParentDS::SetGrandMasterModuleID( int GrandMasterModuleID ) +{ + this->GrandMasterModuleID = GrandMasterModuleID; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +bool +cParentDS::GetParentStats() +{ + return this->parentStats; +} + +UInteger16 +cParentDS::GetObservedParentOffsetScaledLogVariance() +{ + return this->observedParentOffsetScaledLogVariance; +} + +Integer32 +cParentDS::GetObservedParentClockPhaseChangeRate() +{ + return this->observedParentClockPhaseChangeRate; +} + +cClockIdentity +cParentDS::GetGrandmasterIdentity() +{ + return this->grandmasterIdentity; +} + +uint8_t +cParentDS::GetGrandmasterPriority1() +{ + return this->grandmasterPriority1; +} + +uint8_t +cParentDS::GetGrandmasterPriority2() +{ + return this->grandmasterPriority2; +} + +int +cParentDS::GetGrandMasterModuleID() +{ + return this->GrandMasterModuleID; +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cParentDS::operator== (const cParentDS& other) +{ + if + ( + ( this->parentPortIdentity == other.parentPortIdentity ) && + ( this->parentStats == other.parentStats ) && + ( this->observedParentOffsetScaledLogVariance == other.observedParentOffsetScaledLogVariance ) && + ( this->observedParentClockPhaseChangeRate == other.observedParentClockPhaseChangeRate ) && + ( this->grandmasterIdentity == other.grandmasterIdentity ) && + ( this->grandmasterClockQuality == other.grandmasterClockQuality ) && + ( this->grandmasterPriority1 == other.grandmasterPriority1 ) && + ( this->grandmasterPriority2 == other.grandmasterPriority2 ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cParentDS& +cParentDS::operator= (const cParentDS& other) +{ + this->parentPortIdentity = other.parentPortIdentity; + this->parentStats = other.parentStats; + this->observedParentOffsetScaledLogVariance = other.observedParentOffsetScaledLogVariance; + this->observedParentClockPhaseChangeRate = other.observedParentClockPhaseChangeRate; + this->grandmasterIdentity = other.grandmasterIdentity; + this->grandmasterClockQuality = other.grandmasterClockQuality; + this->grandmasterPriority1 = other.grandmasterPriority1; + this->grandmasterPriority2 = other.grandmasterPriority2; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cParentDS::Print() +{ + EV << "===============================================================" << endl; + EV << " Parent Data Set" << endl; + EV << "===============================================================" << endl << endl; + + EV << "Parent PortIdentity:\t" << this->parentPortIdentity.GetString() << endl; + EV << "Parent Stats:\t" << this->parentStats << endl; + EV << "Observed ParentOffsetScaledLogVariance :\t" << this->observedParentOffsetScaledLogVariance << endl; + EV << "Observed ParentClockPhaseChangeRate:\t" << this->observedParentClockPhaseChangeRate << endl; + EV << "Grandmaster Identity:\t" << this->grandmasterIdentity.GetString() << endl; + EV << "Grandmaster ClockQuality:\t" << this->grandmasterClockQuality.GetString() << endl; + EV << "Grandmaster Priority1:\t" << (int) this->grandmasterPriority1 << endl; + EV << "Grandmaster Priority2:\t" << (int) this->grandmasterPriority2 << endl << endl; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.h new file mode 100644 index 0000000..c290432 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_ParentDS.h @@ -0,0 +1,111 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PARENTDS_H_ +#define LIBPTP_PTP_PARENTDS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_PortIdentity.h" +#include "PTP_ClockIdentity.h" +#include "PTP_ClockQuality.h" +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_Stack; + +// ====================================================== +// Declarations +// ====================================================== + +// Parent data set, see clause 8.2.3 in IEEE 1588-2008 +class cParentDS : public cSubmoduleInitBase +{ + private: + + // Resources + + // Static members + // -- + + // Dynamic members + cPortIdentity parentPortIdentity; // + bool parentStats; // + UInteger16 observedParentOffsetScaledLogVariance; // + Integer32 observedParentClockPhaseChangeRate; // + cClockIdentity grandmasterIdentity; // + cClockQuality grandmasterClockQuality; // + uint8_t grandmasterPriority1; // + uint8_t grandmasterPriority2; // + + int GrandMasterModuleID; + + // Configurable members + // -- + + protected: + + public: + + // Constructors/Destructor + cParentDS(); + + // Instance methods + cPortIdentity& ParentPortIdentity(); + cClockQuality& GrandmasterClockQuality(); + + // Setters + void SetParentStats ( bool parentStats ); + void SetObservedParentOffsetScaledLogVariance ( UInteger16 observedParentOffsetScaledLogVariance); + void SetObservedParentClockPhaseChangeRate ( Integer32 observedParentClockPhaseChangeRate ); + void SetGrandmasterIdentity ( cClockIdentity grandmasterIdentity ); + void SetGrandmasterPriority1 ( uint8_t grandmasterPriority1 ); + void SetGrandmasterPriority2 ( uint8_t grandmasterPriority2 ); + void SetGrandMasterModuleID ( int GrandMasterModuleID ); + + // Getters + bool GetParentStats(); + UInteger16 GetObservedParentOffsetScaledLogVariance(); + Integer32 GetObservedParentClockPhaseChangeRate(); + cClockIdentity GetGrandmasterIdentity(); + uint8_t GetGrandmasterPriority1(); + uint8_t GetGrandmasterPriority2(); + int GetGrandMasterModuleID(); + + // Operators + bool operator== (const cParentDS& other); + cParentDS& operator= (const cParentDS& other); + + // Debugn functions + void Print(); + + // Friends + friend PTP_Stack; +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.cc new file mode 100644 index 0000000..b8cc2ec --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.cc @@ -0,0 +1,348 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PortDS.h" + +#include "PTP_Port.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cPortDS::cPortDS() + : cSubmoduleInitBase() +{ + this->portIdentity = cPortIdentity(); + this->portState = PORT_STATE_INITIALIZING; + this->logMinDelayReqInterval = 0; + this->peerMeanPathDelay = 0; + this->logAnnounceInterval = 0; + this->announceReceiptTimeout = 0; + this->logSyncInterval = 0; + this->delayMechanism = DELAY_MECH_DISABLED; + this->logMinPdelayReqInterval = 0; + this->versionNumber = PTP_VERSION_IEEE_1588_2008; + + // Hierarchy specific variables, will be set later + this->pPort = nullptr; +} + +cPortDS::cPortDS( const cPortDS& other ) + : cSubmoduleInitBase( other ) +{ + // Resources + this->pPort = other.pPort; + + // Data set + this->portIdentity = other.portIdentity; + this->portState = other.portState; + this->logMinDelayReqInterval = other.logMinDelayReqInterval; + this->peerMeanPathDelay = other.peerMeanPathDelay; + this->logAnnounceInterval = other.logAnnounceInterval; + this->announceReceiptTimeout = other.announceReceiptTimeout; + this->logSyncInterval = other.logSyncInterval; + this->delayMechanism = other.delayMechanism; + this->logMinPdelayReqInterval = other.logMinPdelayReqInterval; + this->versionNumber = other.versionNumber; + this->Asymmetry = other.Asymmetry; + + // Signals + this->peerMeanPathDelay_SigId = other.peerMeanPathDelay_SigId; + this->portState_SigId = other.portState_SigId; +} + +cPortDS::~cPortDS() +{ +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +cPortDS::RegisterSignals() +{ + peerMeanPathDelay_SigId = pPort->RegisterDynamicSignal( "peerMeanPathDelay" ); + portState_SigId = pPort->RegisterDynamicSignal( "portState" ); +} + +void +cPortDS::InitSignals() +{ + pParentModule->emit( peerMeanPathDelay_SigId, peerMeanPathDelay.GetSimTime() ); + pParentModule->emit( portState_SigId, portState ); +} + +void +cPortDS::SetHierarchy( cPTP_Port *pPort ) +{ + this->pPort = pPort; +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cPortIdentity& +cPortDS::PortIdentity() +{ + return this->portIdentity; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cPortDS::SetPeerMeanPathDelay( cTimeInterval meanPathDelay ) +{ + this->peerMeanPathDelay = meanPathDelay; + + pParentModule->emit( peerMeanPathDelay_SigId, peerMeanPathDelay.GetSimTime() ); + pPort->ConfigPortPathDelay( peerMeanPathDelay.GetSimTime() ); +} + +void +cPortDS::SetPortState( portState_t portState ) +{ + this->portState = portState; + + pParentModule->emit( portState_SigId, portState ); +} + +void +cPortDS::SetLogMinDelayReqInterval( int8_t logMinDelayReqInterval ) +{ + this->logMinDelayReqInterval = logMinDelayReqInterval; +} + +void +cPortDS::SetLogAnnounceInterval( int8_t logAnnounceInterval ) +{ + this->logAnnounceInterval = logAnnounceInterval; +} + +void +cPortDS::SetAnnounceReceiptTimeout( uint8_t announceReceiptTimeout ) +{ + this->announceReceiptTimeout = announceReceiptTimeout; +} + +void +cPortDS::SetLogSyncInterval( int8_t logSyncInterval ) +{ + this->logSyncInterval = logSyncInterval; +} + +void +cPortDS::SetDelayMechanism( delayMechanism_t delayMechanism ) +{ + this->delayMechanism = delayMechanism; +} + +void +cPortDS::SetLogMinPdelayReqInterval( int8_t logMinPdelayReqInterval ) +{ + this->logMinPdelayReqInterval = logMinPdelayReqInterval; +} + +void +cPortDS::SetVersionNumber( UInteger4 versionNumber ) +{ + this->versionNumber = versionNumber; +} + +void +cPortDS::SetAsymmetry( cTimeInterval Asymmetry ) +{ + this->Asymmetry = Asymmetry; + + pPort->ConfigPortAsymmetry( Asymmetry.GetSimTime() ); +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +cTimeInterval +cPortDS::GetPeerMeanPathDelay() +{ + return this->peerMeanPathDelay; +} + +portState_t +cPortDS::GetPortState() +{ + return this->portState; +} + +int8_t +cPortDS::GetLogMinDelayReqInterval() +{ + return this->logMinDelayReqInterval; +} + +int8_t +cPortDS::GetLogAnnounceInterval() +{ + return this->logAnnounceInterval; +} + +uint8_t +cPortDS::GetAnnounceReceiptTimeout() +{ + return this->announceReceiptTimeout; +} + +int8_t +cPortDS::GetLogSyncInterval() +{ + return this->logSyncInterval; +} + +delayMechanism_t +cPortDS::GetDelayMechanism() +{ + return this->delayMechanism; +} + +int8_t +cPortDS::GetLogMinPdelayReqInterval() +{ + return this->logMinPdelayReqInterval; +} + +UInteger4 +cPortDS::GetVersionNumber() +{ + return this->versionNumber; +} + +cTimeInterval +cPortDS::GetAsymmetry() +{ + return this->Asymmetry; +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cPortDS::operator== (const cPortDS& other) +{ + if + ( + ( this->portIdentity == other.portIdentity ) && + ( this->portState == other.portState ) && + ( this->logMinDelayReqInterval == other.logMinDelayReqInterval ) && + ( this->peerMeanPathDelay == other.peerMeanPathDelay ) && + ( this->logAnnounceInterval == other.logAnnounceInterval ) && + ( this->announceReceiptTimeout == other.announceReceiptTimeout ) && + ( this->logSyncInterval == other.logSyncInterval ) && + ( this->delayMechanism == other.delayMechanism ) && + ( this->logMinPdelayReqInterval == other.logMinPdelayReqInterval) && + ( this->versionNumber == other.versionNumber ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cPortDS& +cPortDS::operator= (const cPortDS& other) +{ + cSubmoduleInitBase::operator=( other ); + + // Resources + this->pPort = other.pPort; + + // Data set + this->portIdentity = other.portIdentity; + this->portState = other.portState; + this->logMinDelayReqInterval = other.logMinDelayReqInterval; + this->peerMeanPathDelay = other.peerMeanPathDelay; + this->logAnnounceInterval = other.logAnnounceInterval; + this->announceReceiptTimeout = other.announceReceiptTimeout; + this->logSyncInterval = other.logSyncInterval; + this->delayMechanism = other.delayMechanism; + this->logMinPdelayReqInterval = other.logMinPdelayReqInterval; + this->versionNumber = other.versionNumber; + this->Asymmetry = other.Asymmetry; + + // Signals + this->peerMeanPathDelay_SigId = other.peerMeanPathDelay_SigId; + this->portState_SigId = other.portState_SigId; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cPortDS::Print() +{ + EV << "===============================================================" << endl; + EV << " Port Data Set " << portIdentity.GetPortNumber() << endl; + EV << "===============================================================" << endl << endl; + + EV << "PortIdentity:\t" << this->portIdentity.GetString() << endl; + EV << "PortState:\t" << this->portState << endl; + EV << "LogMinDelayReqInterval:\t" << (int) this->logMinDelayReqInterval << endl; + EV << "PeerMeanPathDelay:\t" << this->peerMeanPathDelay.GetString() << endl; + EV << "LogAnnounceInterval:\t" << (int) this->logAnnounceInterval << endl; + EV << "AnnounceReceiptTimeout:\t" << (int) this->announceReceiptTimeout << endl; + EV << "LogSyncInterval" << (int) this->logSyncInterval << endl; + EV << "DelayMechanism:\t" << this->delayMechanism << endl; + EV << "LogMinPdelayReqInterval:\t" << (int) this->logMinPdelayReqInterval << endl; + EV << "VersionNumber:\t" << (int) this->versionNumber << endl; + EV << "Asymmetry:\t" << this->Asymmetry.GetSimTime() << endl; + EV << endl; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.h new file mode 100644 index 0000000..9300473 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_PortDS.h @@ -0,0 +1,138 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PORTDS_H_ +#define LIBPTP_PTP_PORTDS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_PortIdentity.h" +#include "PTP_TimeInterval.h" +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +class cPTP_Port; + +// ====================================================== +// Declarations +// ====================================================== + +// Port data set, see clause 8.2.5 in IEEE 1588-2008 +class cPortDS : public cSubmoduleInitBase +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + cPTP_Port *pPort; + + // Signals for statistics + simsignal_t peerMeanPathDelay_SigId; + simsignal_t portState_SigId; + + // ------------------------------------------------------------ + // Static members + // ------------------------------------------------------------ + cPortIdentity portIdentity; // ID of this port + + // ------------------------------------------------------------ + // Dynamic members + // ------------------------------------------------------------ + portState_t portState; // Current port state + int8_t logMinDelayReqInterval; // Exponent in range -128 ... 127, [s] + cTimeInterval peerMeanPathDelay; // Result of P2P measurement + + // ------------------------------------------------------------ + // Configurable members + // ------------------------------------------------------------ + int8_t logAnnounceInterval; // Exponent in range -128 ... 127, [s] + uint8_t announceReceiptTimeout; // Number of missed Announce-intervals before ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES is triggered + int8_t logSyncInterval; // Exponent in range -128 ... 127, [s] + delayMechanism_t delayMechanism; // P2P or E2E + int8_t logMinPdelayReqInterval; // Exponent in range -128 ... 127, [s] + UInteger4 versionNumber; // Has to be 2 for IEEE 1588-2008 + + cTimeInterval Asymmetry; // Not in the standard, but added to portDS as it fits here + + // ------------------------------------------------------------ + // Internal functions + // ------------------------------------------------------------ + void RegisterSignals(); + void InitSignals(); + + protected: + + public: + + // Constructors/Destructor + cPortDS(); + cPortDS( const cPortDS& other ); + ~cPortDS(); + + // Initialization + void SetHierarchy( cPTP_Port *pPort ); + + // Setters + void SetPeerMeanPathDelay ( cTimeInterval offsetFromMaster ); + void SetPortState ( portState_t portState ); + void SetLogMinDelayReqInterval ( int8_t logMinDelayReqInterval ); + void SetLogAnnounceInterval ( int8_t logAnnounceInterval ); + void SetAnnounceReceiptTimeout ( uint8_t announceReceiptTimeout ); + void SetLogSyncInterval ( int8_t logSyncInterval ); + void SetDelayMechanism ( delayMechanism_t delayMechanism ); + void SetLogMinPdelayReqInterval ( int8_t logMinPdelayReqInterval ); + void SetVersionNumber ( UInteger4 versionNumber ); + void SetAsymmetry ( cTimeInterval Asymmetry ); + + // Instance methods + cPortIdentity& PortIdentity(); + + // Getters + cTimeInterval GetPeerMeanPathDelay(); + portState_t GetPortState(); + int8_t GetLogMinDelayReqInterval(); + int8_t GetLogAnnounceInterval(); + uint8_t GetAnnounceReceiptTimeout(); + int8_t GetLogSyncInterval(); + delayMechanism_t GetDelayMechanism(); + int8_t GetLogMinPdelayReqInterval(); + UInteger4 GetVersionNumber(); + cTimeInterval GetAsymmetry(); + + // Operators + bool operator== (const cPortDS& other); + cPortDS& operator= (const cPortDS& other); + + // Debug functions + void Print(); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.cc new file mode 100644 index 0000000..601625c --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.cc @@ -0,0 +1,234 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_TimePropertiesDS.h" +#include "PTP_Constants.h" + +// ====================================================== +// Definitions +// ====================================================== + + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cTimePropertiesDS::cTimePropertiesDS() + : cSubmoduleInitBase() +{ + this->currentUtcOffset = PTP_UTC_OFFSET_2006; + this->currentUtcOffsetValid = false; + this->leap59 = false; + this->leap61 = false; + this->timeTraceable = false; + this->frequencyTraceable = false; + this->ptpTimescale = false; + this->timeSource = TIME_SRC_INTERNAL_OSCILLATOR; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cTimePropertiesDS::SetCurrentUtcOffset( Integer16 currentUtcOffset ) +{ + this->currentUtcOffset = currentUtcOffset; +} + +void +cTimePropertiesDS::SetCurrentUtcOffsetValid( bool currentUtcOffsetValid ) +{ + this->currentUtcOffsetValid = currentUtcOffsetValid; +} + +void +cTimePropertiesDS::SetLeap59( bool leap59 ) +{ + this->leap59 = leap59; +} + +void +cTimePropertiesDS::SetLeap61( bool leap61 ) +{ + this->leap61 = leap61; +} + +void +cTimePropertiesDS::SetTimeTraceable( bool timeTraceable ) +{ + this->timeTraceable = timeTraceable; +} + +void +cTimePropertiesDS::SetFrequencyTraceable( bool frequencyTraceable ) +{ + this->frequencyTraceable = frequencyTraceable; +} + +void +cTimePropertiesDS::SetPtpTimescale( bool ptpTimescale ) +{ + this->ptpTimescale = ptpTimescale; +} + +void +cTimePropertiesDS::SetTimeSource( timeSource_t timeSource ) +{ + this->timeSource = timeSource; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +Integer16 +cTimePropertiesDS::GetCurrentUtcOffset() +{ + return this->currentUtcOffset; +} + +bool +cTimePropertiesDS::GetCurrentUtcOffsetValid() +{ + return this->currentUtcOffsetValid; +} + +bool +cTimePropertiesDS::GetLeap59() +{ + return this->leap59; +} + +bool +cTimePropertiesDS::GetLeap61() +{ + return this->leap61; +} + +bool +cTimePropertiesDS::GetTimeTraceable() +{ + return this->timeTraceable; +} + +bool +cTimePropertiesDS::GetFrequencyTraceable() +{ + return this->frequencyTraceable; +} + +bool +cTimePropertiesDS::GetPtpTimescale() +{ + return this->ptpTimescale; +} + +timeSource_t +cTimePropertiesDS::GetTimeSource() +{ + return this->timeSource; +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cTimePropertiesDS::operator== (const cTimePropertiesDS& other) +{ + if + ( + ( other.currentUtcOffset == this->currentUtcOffset ) && + ( other.currentUtcOffsetValid == this->currentUtcOffsetValid ) && + ( other.leap59 == this->leap59 ) && + ( other.leap61 == this->leap61 ) && + ( other.timeTraceable == this->timeTraceable ) && + ( other.frequencyTraceable == this->frequencyTraceable ) && + ( other.ptpTimescale == this->ptpTimescale ) && + ( other.timeSource == this->timeSource ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cTimePropertiesDS& +cTimePropertiesDS::operator= (const cTimePropertiesDS& other) +{ + this->currentUtcOffset = other.currentUtcOffset; + this->currentUtcOffsetValid = other.currentUtcOffsetValid; + this->leap59 = other.leap59; + this->leap61 = other.leap61; + this->timeTraceable = other.timeTraceable; + this->frequencyTraceable = other.frequencyTraceable; + this->ptpTimescale = other.ptpTimescale; + this->timeSource = other.timeSource; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cTimePropertiesDS::Print() +{ + EV << "===============================================================" << endl; + EV << " Timeproperties Data Set" << endl; + EV << "===============================================================" << endl << endl; + + EV << "Current UtcOffset:\t" << this->currentUtcOffset << endl; + EV << "Current UtcOffset Valid:\t" << this->currentUtcOffsetValid << endl; + EV << "Leap59:\t" << this->leap59 << endl; + EV << "Leap59:\t" << this->leap61 << endl; + EV << "Time Traceable:\t" << this->timeTraceable << endl; + EV << "Frequency Traceable:\t" << this->frequencyTraceable << endl; + EV << "PtpTimescale:\t" << this->ptpTimescale << endl; + EV << "TimeSource:\t" << this->timeSource << endl << endl; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.h new file mode 100644 index 0000000..c948728 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TimePropertiesDS.h @@ -0,0 +1,101 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TIME_PROPERTIES_DS_H_ +#define LIBPTP_PTP_TIME_PROPERTIES_DS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_Stack; + +// ====================================================== +// Declarations +// ====================================================== + +// Time properties data set, see clause 8.2.4 in IEEE 1588-2008 +class cTimePropertiesDS : public cSubmoduleInitBase +{ + private: + + // Resources + + // Static members + // -- + + // Dynamic members + Integer16 currentUtcOffset; // [s] Offset UTC <-> TAI, currently 33s + bool currentUtcOffsetValid; // + bool leap59; // + bool leap61; // + bool timeTraceable; // + bool frequencyTraceable; // + bool ptpTimescale; // + timeSource_t timeSource; // + + // Configurable members + // -- + + protected: + + public: + // Constructors/Destructor + cTimePropertiesDS(); + + // Setters + void SetCurrentUtcOffset ( Integer16 currentUtcOffset ); + void SetCurrentUtcOffsetValid ( bool currentUtcOffsetValid ); + void SetLeap59 ( bool leap59 ); + void SetLeap61 ( bool leap61 ); + void SetTimeTraceable ( bool timeTraceable ); + void SetFrequencyTraceable ( bool frequencyTraceable ); + void SetPtpTimescale ( bool ptpTimescale ); + void SetTimeSource ( timeSource_t timeSource ); + + // Getters + Integer16 GetCurrentUtcOffset(); + bool GetCurrentUtcOffsetValid(); + bool GetLeap59(); + bool GetLeap61(); + bool GetTimeTraceable(); + bool GetFrequencyTraceable(); + bool GetPtpTimescale(); + timeSource_t GetTimeSource(); + + // Operators + bool operator== (const cTimePropertiesDS& other); + cTimePropertiesDS& operator= (const cTimePropertiesDS& other); + + // Debug functions + void Print(); +}; + + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.cc new file mode 100644 index 0000000..dbf59aa --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.cc @@ -0,0 +1,146 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_TransparentClockDefaultDS.h" + +// ====================================================== +// Definitions +// ====================================================== + + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cTransparentClockDefaultDS::cTransparentClockDefaultDS() +{ + this->clockIdentity = cClockIdentity(); + this->numberPorts = 0; + this->delayMechanism = DELAY_MECH_DISABLED; + this->primaryDomain = DOMAIN_DEFAULT; +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cClockIdentity& cTransparentClockDefaultDS::ClockIdentity() +{ + return this->clockIdentity; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cTransparentClockDefaultDS::SetNumberPorts( size_t numberPorts ) +{ + this->numberPorts = numberPorts; +} + +void +cTransparentClockDefaultDS::SetDelayMechanism( delayMechanism_t delayMechanism ) +{ + this->delayMechanism = delayMechanism; +} + +void +cTransparentClockDefaultDS::SetPrimaryDomain( domainNumber_t primaryDomain ) +{ + this->primaryDomain = primaryDomain; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +size_t +cTransparentClockDefaultDS::GetNumberPorts() +{ + return this->numberPorts; +} + +delayMechanism_t +cTransparentClockDefaultDS::GetDelayMechanism() +{ + return this->delayMechanism; +} + +domainNumber_t +cTransparentClockDefaultDS::GetPrimaryDomain() +{ + return this->primaryDomain; +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cTransparentClockDefaultDS::operator== (const cTransparentClockDefaultDS& other) +{ + if + ( + ( this->clockIdentity == other.clockIdentity ) && + ( this->numberPorts == other.numberPorts ) && + ( this->delayMechanism == other.delayMechanism ) && + ( this->primaryDomain == other.primaryDomain ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cTransparentClockDefaultDS& +cTransparentClockDefaultDS::operator= (const cTransparentClockDefaultDS& other) +{ + this->clockIdentity = other.clockIdentity; + this->numberPorts = other.numberPorts; + this->delayMechanism = other.delayMechanism; + this->primaryDomain = other.primaryDomain; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.h new file mode 100644 index 0000000..5c64816 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockDefaultDS.h @@ -0,0 +1,80 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TRANSPARENT_CLOCK_DEFAULT_DS_H_ +#define LIBPTP_PTP_TRANSPARENT_CLOCK_DEFAULT_DS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_ClockIdentity.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Transparent Clock default data set, see clause 8.3.2 in IEEE 1588-2008 +class cTransparentClockDefaultDS +{ + private: + // Static members + cClockIdentity clockIdentity; // ID of this clock + size_t numberPorts; // Number of ports + + // Configurable members + delayMechanism_t delayMechanism; // P2P or E2E + domainNumber_t primaryDomain; // Primary clock domain + + protected: + + public: + + // Constructors + cTransparentClockDefaultDS(); + + // Instance methods + cClockIdentity& ClockIdentity(); + + // Setters + void SetNumberPorts ( size_t numberPorts ); + void SetDelayMechanism ( delayMechanism_t delayMechanism); + void SetPrimaryDomain ( domainNumber_t primaryDomain ); + + // Getters + size_t GetNumberPorts(); + delayMechanism_t GetDelayMechanism(); + domainNumber_t GetPrimaryDomain(); + + // Operators + bool operator== (const cTransparentClockDefaultDS& other); + cTransparentClockDefaultDS& operator= (const cTransparentClockDefaultDS& other); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.cc b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.cc new file mode 100644 index 0000000..fd26daa --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.cc @@ -0,0 +1,140 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_TransparentClockPortDS.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cTransparentClockPortDS::cTransparentClockPortDS() +{ + this->portIdentity = cPortIdentity(); + this->logMinPdelayReqInterval = 0; + this->faultyFlag = false; + this->peerMeanPathDelay = cTimeInterval(); +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cPortIdentity& +cTransparentClockPortDS::PortIdentity() +{ + return this->portIdentity; +} + +cTimeInterval& +cTransparentClockPortDS::PeerMeanPathDelay() +{ + return this->peerMeanPathDelay; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cTransparentClockPortDS::SetLogMinPdelayReqInterval( int8_t logMinPdelayReqInterval ) +{ + this->logMinPdelayReqInterval = logMinPdelayReqInterval; +} + +void +cTransparentClockPortDS::SetFaultyFlag( bool faultyFlag ) +{ + this->faultyFlag = faultyFlag; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +int8_t +cTransparentClockPortDS::GetLogMinPdelayReqInterval() +{ + return this->logMinPdelayReqInterval; +} + +bool +cTransparentClockPortDS::GetFaultyFlag() +{ + return this->faultyFlag; +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cTransparentClockPortDS::operator== (const cTransparentClockPortDS& other) +{ + if + ( + ( this->portIdentity == other.portIdentity ) && + ( this->logMinPdelayReqInterval == other.logMinPdelayReqInterval) && + ( this->faultyFlag == other.faultyFlag ) && + ( this->peerMeanPathDelay == other.peerMeanPathDelay ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cTransparentClockPortDS& +cTransparentClockPortDS::operator= (const cTransparentClockPortDS& other) +{ + this->portIdentity = other.portIdentity; + this->logMinPdelayReqInterval = other.logMinPdelayReqInterval; + this->faultyFlag = other.faultyFlag; + this->peerMeanPathDelay = other.peerMeanPathDelay; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.h b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.h new file mode 100644 index 0000000..84651a8 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/DataSets/PTP_TransparentClockPortDS.h @@ -0,0 +1,78 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TRANSPARENT_CLOCK_PORT_DS_H_ +#define LIBPTP_PTP_TRANSPARENT_CLOCK_PORT_DS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_PortIdentity.h" +#include "PTP_TimeInterval.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Transparent Clock port data set, see clause 8.3.3 in IEEE 1588-2008 +class cTransparentClockPortDS +{ + private: + // Static members + cPortIdentity portIdentity; // Address of this port + + // Configurable members + int8_t logMinPdelayReqInterval; // Exponent in range -128 ... 127, [s] + bool faultyFlag; // Indicates errors on this port + cTimeInterval peerMeanPathDelay; // Result of P2P measurement + + protected: + + public: + + // Constructors + cTransparentClockPortDS(); + + // Instance methods + cPortIdentity& PortIdentity(); + cTimeInterval& PeerMeanPathDelay(); + + // Setters + void SetLogMinPdelayReqInterval( int8_t logMinPdelayReqInterval ); + void SetFaultyFlag( bool faultyFlag ); + + // Getters + int8_t GetLogMinPdelayReqInterval(); + bool GetFaultyFlag(); + + // Operators + bool operator== (const cTransparentClockPortDS& other); + cTransparentClockPortDS& operator= (const cTransparentClockPortDS& other); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.cc b/src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.cc new file mode 100644 index 0000000..0c94393 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.cc @@ -0,0 +1,275 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_ClockIdentity.h" +#include "PTP_Constants.h" + +#include +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ + +cClockIdentity::cClockIdentity() +{ + // Nullify clock ID on startup + memset( Bytes, 0x00, PTP_CLOCK_IDENTITY_SIZE ); +} + +cClockIdentity::cClockIdentity( const char *Str ) +{ + this->operator=(Str); +} + +cClockIdentity::cClockIdentity( const MACAddress MAC ) +{ + this->operator=(MAC); +} + +cClockIdentity::cClockIdentity( const BufPtpClockId_t *pBuf ) +{ + ReadFromBuffer( pBuf ); +} + +cClockIdentity::cClockIdentity( const cClockIdentity& other ) +{ + memcpy( this->Bytes, other.Bytes, PTP_CLOCK_IDENTITY_SIZE ); +} + +cClockIdentity::~cClockIdentity() +{ +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cClockIdentity::ReadFromBuffer( const BufPtpClockId_t *pBuf ) +{ + memcpy( Bytes, pBuf->data, PTP_CLOCK_IDENTITY_SIZE ); +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +Octet +cClockIdentity::GetByteAt( size_t idx ) +{ + return this->Bytes[ idx ]; +} + +void +cClockIdentity::SaveToBuffer( BufPtpClockId_t *pBuf ) +{ + memcpy( &pBuf->data, Bytes, PTP_CLOCK_IDENTITY_SIZE ); +} + +// ------------------------------------------------------ +// Compare operators +// ------------------------------------------------------ +bool +cClockIdentity::operator== (const cClockIdentity& other) const +{ + return (0 == memcmp( this->Bytes, other.Bytes, PTP_CLOCK_IDENTITY_SIZE )); +} + +bool +cClockIdentity::operator!= (const cClockIdentity& other) const +{ + return !(this->operator ==(other)); +} + +bool +operator<(cClockIdentity const& lhs, cClockIdentity const& rhs) +{ + if( memcmp( lhs.Bytes, rhs.Bytes, PTP_CLOCK_IDENTITY_SIZE ) < 0 ) + { + return true; + } + + return false; +} + +bool +operator>(cClockIdentity const& lhs, cClockIdentity const& rhs) +{ + if( memcmp( lhs.Bytes, rhs.Bytes, PTP_CLOCK_IDENTITY_SIZE ) > 0 ) + { + return true; + } + + return false; +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cClockIdentity & +cClockIdentity::operator= (const cClockIdentity& other) +{ + // Copy data + memcpy( this->Bytes, other.Bytes, PTP_CLOCK_IDENTITY_SIZE ); + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Set Clock Identity from hexadecimal string +// ------------------------------------------------------ +cClockIdentity & +cClockIdentity::operator= (const char *Str) +{ + cStringTokenizer Tokenizer(Str, " :-.,;"); + std::vector StringVector; + + StringVector = Tokenizer.asVector(); + + if + ( + ( StringVector.size() != 0 ) && + ( StringVector.size() != PTP_CLOCK_IDENTITY_SIZE ) + ) + { + // Wrong Clock ID size + throw cRuntimeError("Parsing exception: Clock ID '%s' has wrong size (valid sizes are 0 and %d)", Str, PTP_CLOCK_IDENTITY_SIZE ); + } + + if( StringVector.size() == 0 ) + { + memset( this->Bytes, 0x00, PTP_CLOCK_IDENTITY_SIZE ); + } + else + { + for( size_t i = 0; i < PTP_CLOCK_IDENTITY_SIZE; i ++ ) + { + unsigned int x; + std::stringstream ss; + + // Convert string to hex value + ss << std::hex << StringVector.at( i ); + ss >> x; + + // Check range + if + ( + ( x >= 0x00 ) && + ( x <= 0xFF ) + ) + { + this->Bytes[ i ] = x; + } + else + { + // Unexpected value + throw cRuntimeError("Parsing exception: Clock ID '%s' contains unexpected numerical value (bytes expected)", Str ); + } + } + } + + // By convention, always return *this + return *this; +} + +cClockIdentity & +cClockIdentity::operator= (const MACAddress MAC) +{ + // MAC to ClockIdentity assignment is specified in 7.5.2.2.2 + + this->Bytes[ 0 ] = MAC.getAddressByte( 0 ); + this->Bytes[ 1 ] = MAC.getAddressByte( 1 ); + this->Bytes[ 2 ] = MAC.getAddressByte( 2 ); + this->Bytes[ 3 ] = 0xFF; + this->Bytes[ 4 ] = 0xFE; + this->Bytes[ 5 ] = MAC.getAddressByte( 3 ); + this->Bytes[ 6 ] = MAC.getAddressByte( 4 ); + this->Bytes[ 7 ] = MAC.getAddressByte( 5 ); + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const cClockIdentity& o) +{ + os << o.GetString(); // no endl! + + return os; +} + +// ------------------------------------------------------ +// Print clock identity +// ------------------------------------------------------ +void +cClockIdentity::Print() const +{ + EV << "ClockIdentity: " << GetString() << endl; +} + +std::string +cClockIdentity::GetString() const +{ + std::stringstream ss; + + for( size_t i = 0; i < PTP_CLOCK_IDENTITY_SIZE; i ++ ) + { + ss << std::setfill('0') << std::setw(2) << std::hex << (int)Bytes[i]; + + if( i < PTP_CLOCK_IDENTITY_SIZE-1 ) + { + ss << "-"; + } + } + + return ss.str(); +} + diff --git a/src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.h b/src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.h new file mode 100644 index 0000000..ffe7700 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_ClockIdentity.h @@ -0,0 +1,84 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_CLOCK_IDENTITY_H_ +#define LIBPTP_PTP_CLOCK_IDENTITY_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "MACAddress.h" + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_Constants.h" +#include "PTP_ByteBuffers.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Clock identity, see clause 5.3.4 and 7.5.2.2 in IEEE 1588-2008 +class cClockIdentity +{ + private: + Octet Bytes[ PTP_CLOCK_IDENTITY_SIZE ]; + + protected: + + public: + // Constructors/Destructor + cClockIdentity(); + cClockIdentity( const char *Str ); + cClockIdentity( const MACAddress MAC ); + cClockIdentity( const BufPtpClockId_t *pBuf ); + cClockIdentity( const cClockIdentity& other ); + ~cClockIdentity(); + + // Setters + void ReadFromBuffer( const BufPtpClockId_t *pBuf ); + + // Getters + Octet GetByteAt( size_t idx ); + void SaveToBuffer( BufPtpClockId_t *pBuf ); + + // Operators + bool operator== (const cClockIdentity& other) const; + bool operator!= (const cClockIdentity& other) const; + friend bool operator<(cClockIdentity const& lhs, cClockIdentity const& rhs); + friend bool operator>(cClockIdentity const& lhs, cClockIdentity const& rhs); + cClockIdentity& operator= (const cClockIdentity& other); + cClockIdentity& operator= (const char *Str); + cClockIdentity& operator= (const MACAddress MAC); + + friend std::ostream& operator<<(std::ostream& os, const cClockIdentity& o); + + // Debug functions + void Print() const; + std::string GetString() const; +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.cc b/src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.cc new file mode 100644 index 0000000..6346111 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.cc @@ -0,0 +1,238 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ClockQuality.h" + +#include +#include + +#include "ByteOrder.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cClockQuality::cClockQuality() +{ + // Initialize with dummy values + this->clockAccuracy = CLOCK_ACCURACY_UNKNOWN; + this->clockClass = CLOCK_CLASS_DEFAULT; + this->offsetScaledLogVariance = 0; +} + +cClockQuality::cClockQuality( ClockClass_t clockClass, + clockAccuracy_t clockAccuracy, + UInteger16 offsetScaledLogVariance) +: clockClass(clockClass), + clockAccuracy(clockAccuracy), + offsetScaledLogVariance(offsetScaledLogVariance) +{ +} + +cClockQuality::cClockQuality( BufPtpClockQual_t *pBuf ) +{ + ReadFromBuffer( pBuf ); +} + +cClockQuality::cClockQuality( const cClockQuality& other ) +{ + this->clockAccuracy = other.clockAccuracy; + this->clockClass = other.clockClass; + this->offsetScaledLogVariance = other.offsetScaledLogVariance; +} + +cClockQuality::~cClockQuality() +{ +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cClockQuality::SetClockClass( ClockClass_t clockClass ) +{ + this->clockClass = clockClass; +} + +void +cClockQuality::SetClockAccuracy( clockAccuracy_t clockAccuracy ) +{ + this->clockAccuracy = clockAccuracy; +} + +void +cClockQuality::SetOffsetScaledLogVar( UInteger16 offsetScaledLogVariance ) +{ + this->offsetScaledLogVariance = offsetScaledLogVariance; +} + +void +cClockQuality::ReadFromBuffer( BufPtpClockQual_t *pBuf ) +{ + this->clockClass = (ClockClass_t) pBuf->clockClass; + this->clockAccuracy = (clockAccuracy_t) pBuf->clockAccuracy; + this->offsetScaledLogVariance = NetToHostUI16( pBuf->offsetScaledLogVariance ); +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +ClockClass_t +cClockQuality::GetClockClass() const +{ + return this->clockClass; +} + +clockAccuracy_t +cClockQuality::GetClockAccuracy() const +{ + return this->clockAccuracy; +} + +UInteger16 +cClockQuality::GetOffsetScaledLogVar() const +{ + return this->offsetScaledLogVariance; +} + +void +cClockQuality::SaveToBuffer( BufPtpClockQual_t *pBuf ) const +{ + pBuf->clockClass = clockClass; + pBuf->clockAccuracy = clockAccuracy; + pBuf->offsetScaledLogVariance = HostToNetUI16( offsetScaledLogVariance ); +} + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +uint64_t cClockQuality::GetUInt64() +{ + uint64_t cClockQuality; + + UInteger8 clockClass = this->clockClass; + UInteger8 clockAccuracy = this->clockAccuracy; + UInteger16 offsetScaledLogVariance = this->offsetScaledLogVariance; + + cClockQuality = 0; + cClockQuality += clockClass; + cClockQuality <<= 8; + cClockQuality += clockAccuracy; + cClockQuality <<= 16; + cClockQuality += offsetScaledLogVariance; + + return cClockQuality; +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cClockQuality::operator== (const cClockQuality& other) const +{ + if + ( + ( other.clockAccuracy == this->clockAccuracy ) && + ( other.clockClass == this->clockClass ) && + ( other.offsetScaledLogVariance == this->offsetScaledLogVariance ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cClockQuality & +cClockQuality::operator= (const cClockQuality& other) +{ + this->clockAccuracy = other.clockAccuracy; + this->clockClass = other.clockClass; + this->offsetScaledLogVariance = other.offsetScaledLogVariance; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const cClockQuality& o) +{ + os << "Accuracy: " << o.clockAccuracy << ", Class: " << o.clockClass << ", OffsetScaledLogVar: " << o.offsetScaledLogVariance; + + return os; +} + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cClockQuality::Print() +{ + EV << "ClockQuality: " << GetString() << endl; +} + +std::string +cClockQuality::GetString() +{ + std::stringstream ss; + + ss << "<"; + + ss << (int) this->clockAccuracy << ":"; + ss << (int) this->clockClass << ":"; + ss << (int) this->offsetScaledLogVariance; + + ss << ">"; + + return ss.str(); +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.h b/src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.h new file mode 100644 index 0000000..3ff658e --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_ClockQuality.h @@ -0,0 +1,93 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_CLOCKQUALITY_H_ +#define LIBPTP_PTP_CLOCKQUALITY_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_ByteBuffers.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Clock quality, see clause 5.3.7 and 8.2.1.3.1 in IEEE 1588-2008 +class cClockQuality +{ + private: + + ClockClass_t clockClass; // + clockAccuracy_t clockAccuracy; // + UInteger16 offsetScaledLogVariance; // + + protected: + + public: + + // Constructors + cClockQuality(); + cClockQuality( ClockClass_t clockClass, + clockAccuracy_t clockAccuracy, + UInteger16 offsetScaledLogVariance); + cClockQuality( BufPtpClockQual_t *pBuf ); + cClockQuality( const cClockQuality& other ); + ~cClockQuality(); + + // Setters + void SetClockClass ( ClockClass_t clockClass ); + void SetClockAccuracy ( clockAccuracy_t clockAccuracy ); + void SetOffsetScaledLogVar ( UInteger16 offsetScaledLogVariance ); + + void ReadFromBuffer( BufPtpClockQual_t *pBuf ); + + // Getters + ClockClass_t GetClockClass() const; + clockAccuracy_t GetClockAccuracy() const; + UInteger16 GetOffsetScaledLogVar() const; + + void SaveToBuffer( BufPtpClockQual_t *pBuf ) const; + + // API functions + uint64_t GetUInt64(); + + // Operators + bool operator== (const cClockQuality& other) const; + cClockQuality &operator= (const cClockQuality& other); + + friend std::ostream& operator<<(std::ostream& os, const cClockQuality& o); + + // Debug functions + void Print(); + std::string GetString(); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.cc b/src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.cc new file mode 100644 index 0000000..660dd81 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.cc @@ -0,0 +1,137 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_FaultRecord.h" +#include "PTP_PrimitiveDataTypes.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cFaultRecord::cFaultRecord() +{ + this->faultTime = 0; + this->severityCode = SERVIRITY_DEBUG; + this->faultName = ""; + this->faultValue = ""; + this->faultDescription = ""; + + this->faultRecordLength = 0; +} + +cFaultRecord::cFaultRecord +( + simtime_t faultTime, + SeverityCode_t severityCode, + const char* faultName, + const char* faultValue, + const char* faultDescription +) +{ + this->faultTime = faultTime; + this->severityCode = severityCode; + this->faultName = faultName; + this->faultValue = faultValue; + this->faultDescription = faultDescription; + + // 5.3.10 + // The faultRecordLength member shall indicate the number of octets in the FaultRecord not including the 2 + // octets of the faultRecordLength member. + this->faultRecordLength = 10 + // faultTime + 1 + // severityCode + this->faultName.GetLengthField() + + this->faultValue.GetLengthField() + + this->faultDescription.GetLengthField(); +} + +cFaultRecord::~cFaultRecord() +{ + +} + +// ------------------------------------------------------ +// Setters/Getters +// ------------------------------------------------------ +// TODO: Implement + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool cFaultRecord::operator== (const cFaultRecord& other) +{ + if + ( + ( this->faultTime == other.faultTime ) && + ( this->severityCode == other.severityCode ) && + ( this->faultName == other.faultName ) && + ( this->faultValue == other.faultValue ) && + ( this->faultDescription == other.faultDescription ) && + ( this->faultRecordLength == other.faultRecordLength ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cFaultRecord& cFaultRecord::operator= (const cFaultRecord& other) +{ + this->faultTime = other.faultTime; + this->severityCode = other.severityCode; + this->faultName = other.faultName; + this->faultValue = other.faultValue; + this->faultDescription = other.faultDescription; + + this->faultRecordLength = other.faultRecordLength; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.h b/src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.h new file mode 100644 index 0000000..48154f1 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_FaultRecord.h @@ -0,0 +1,76 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_FAULTRECORD_H_ +#define LIBPTP_PTP_FAULTRECORD_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_TimeStamp.h" +#include "PTP_PTPText.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Fault record, see clause 5.3.10 in IEEE 1588-2008 +class cFaultRecord +{ + private: + UInteger16 faultRecordLength; // + cTimeStamp faultTime; // + SeverityCode_t severityCode; // + cPTPText faultName; // + cPTPText faultValue; // + cPTPText faultDescription; // + + protected: + + public: + + // Constructors + cFaultRecord(); + cFaultRecord( simtime_t faultTime, + SeverityCode_t severityCode, + const char* faultName, + const char* faultValue, + const char* faultDescription + ); + ~cFaultRecord(); + + // Setters/Getters + + // Operators + bool operator== (const cFaultRecord& other); + cFaultRecord &operator= (const cFaultRecord& other); + + // Debug functions +}; + + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.cc b/src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.cc new file mode 100644 index 0000000..9eec91e --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.cc @@ -0,0 +1,264 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_HeaderFlags.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +const Octet PTP_FLAG_ALT_MASTER_MASK = (1 << 0); +const Octet PTP_FLAG_TWO_STEP_MASK_MASK = (1 << 1); +const Octet PTP_FLAG_UNICAST_MASK_MASK = (1 << 2); +const Octet PTP_FLAG_PROF_SPEC_1_MASK = (1 << 5); +const Octet PTP_FLAG_PROF_SPEC_2_MASK = (1 << 6); +const Octet PTP_FLAG_RESERVED_MASK = (1 << 7); + +const Octet PTP_FLAG_LEAP_61_MASK = (1 << 0); +const Octet PTP_FLAG_LEAP_59_MASK = (1 << 1); +const Octet PTP_FLAG_CURR_UTC_VALID_MASK = (1 << 2); +const Octet PTP_FLAG_PTP_TIMESCALE_MASK = (1 << 3); +const Octet PTP_FLAG_TIME_TRACABLE_MASK = (1 << 4); +const Octet PTP_FLAG_FREQ_TRACABLE_MASK = (1 << 5); + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cPtpHeaderFlags::cPtpHeaderFlags() +{ + alternateMasterFlag = false; + twoStepFlag = false; + unicastFlag = false; + ptpProfileSpecific_1 = false; + ptpProfileSpecific_2 = false; + reserved = false; + leap61 = false; + leap59 = false; + currentUtcOffsetValid = false; + ptpTimescale = false; + timeTraceable = false; + frequencyTraceable = false; +} + +cPtpHeaderFlags::cPtpHeaderFlags( const Octet flagField[] ) +{ + ReadFromBuffer( flagField ); +} + +cPtpHeaderFlags::cPtpHeaderFlags( const cPtpHeaderFlags& other ) +{ + this->alternateMasterFlag = other.alternateMasterFlag; + this->twoStepFlag = other.twoStepFlag; + this->unicastFlag = other.unicastFlag; + this->ptpProfileSpecific_1 = other.ptpProfileSpecific_1; + this->ptpProfileSpecific_2 = other.ptpProfileSpecific_2; + this->reserved = other.reserved; + this->leap61 = other.leap61; + this->leap59 = other.leap59; + this->currentUtcOffsetValid = other.currentUtcOffsetValid; + this->ptpTimescale = other.ptpTimescale; + this->timeTraceable = other.timeTraceable; + this->frequencyTraceable = other.frequencyTraceable; +} + +cPtpHeaderFlags::~cPtpHeaderFlags() +{ +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +void +cPtpHeaderFlags::ReadFromBuffer( const Octet flagField[] ) +{ + alternateMasterFlag = (flagField[0] & PTP_FLAG_ALT_MASTER_MASK ) == PTP_FLAG_ALT_MASTER_MASK; + twoStepFlag = (flagField[0] & PTP_FLAG_TWO_STEP_MASK_MASK ) == PTP_FLAG_TWO_STEP_MASK_MASK; + unicastFlag = (flagField[0] & PTP_FLAG_UNICAST_MASK_MASK ) == PTP_FLAG_UNICAST_MASK_MASK; + ptpProfileSpecific_1 = (flagField[0] & PTP_FLAG_PROF_SPEC_1_MASK ) == PTP_FLAG_PROF_SPEC_1_MASK; + ptpProfileSpecific_2 = (flagField[0] & PTP_FLAG_PROF_SPEC_2_MASK ) == PTP_FLAG_PROF_SPEC_2_MASK; + reserved = (flagField[0] & PTP_FLAG_RESERVED_MASK ) == PTP_FLAG_RESERVED_MASK; + + leap61 = (flagField[1] & PTP_FLAG_LEAP_61_MASK ) == PTP_FLAG_LEAP_61_MASK; + leap59 = (flagField[1] & PTP_FLAG_LEAP_59_MASK ) == PTP_FLAG_LEAP_59_MASK; + currentUtcOffsetValid = (flagField[1] & PTP_FLAG_CURR_UTC_VALID_MASK ) == PTP_FLAG_CURR_UTC_VALID_MASK; + ptpTimescale = (flagField[1] & PTP_FLAG_PTP_TIMESCALE_MASK ) == PTP_FLAG_PTP_TIMESCALE_MASK; + timeTraceable = (flagField[1] & PTP_FLAG_TIME_TRACABLE_MASK ) == PTP_FLAG_TIME_TRACABLE_MASK; + frequencyTraceable = (flagField[1] & PTP_FLAG_FREQ_TRACABLE_MASK ) == PTP_FLAG_FREQ_TRACABLE_MASK; +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +void +cPtpHeaderFlags::SaveToBuffer( Octet flagField[] ) +{ + flagField[ 0 ] = ( alternateMasterFlag ? PTP_FLAG_ALT_MASTER_MASK : 0x00 ) | + ( twoStepFlag ? PTP_FLAG_TWO_STEP_MASK_MASK : 0x00 ) | + ( unicastFlag ? PTP_FLAG_UNICAST_MASK_MASK : 0x00 ) | + ( ptpProfileSpecific_1 ? PTP_FLAG_PROF_SPEC_1_MASK : 0x00 ) | + ( ptpProfileSpecific_2 ? PTP_FLAG_PROF_SPEC_2_MASK : 0x00 ) | + ( reserved ? PTP_FLAG_RESERVED_MASK : 0x00 ); + + flagField[ 1 ] = ( leap61 ? PTP_FLAG_LEAP_61_MASK : 0x00 ) | + ( leap59 ? PTP_FLAG_LEAP_59_MASK : 0x00 ) | + ( currentUtcOffsetValid ? PTP_FLAG_CURR_UTC_VALID_MASK : 0x00 ) | + ( ptpTimescale ? PTP_FLAG_PTP_TIMESCALE_MASK : 0x00 ) | + ( timeTraceable ? PTP_FLAG_TIME_TRACABLE_MASK : 0x00 ) | + ( frequencyTraceable ? PTP_FLAG_FREQ_TRACABLE_MASK : 0x00 ); +} + +// ------------------------------------------------------ +// Compare operators +// ------------------------------------------------------ +bool +cPtpHeaderFlags::operator== (const cPtpHeaderFlags& other) const +{ + if + ( + ( other.alternateMasterFlag == this->alternateMasterFlag ) && + ( other.twoStepFlag == this->twoStepFlag ) && + ( other.unicastFlag == this->unicastFlag ) && + ( other.ptpProfileSpecific_1 == this->ptpProfileSpecific_1 ) && + ( other.ptpProfileSpecific_2 == this->ptpProfileSpecific_2 ) && + ( other.reserved == this->reserved ) && + ( other.leap61 == this->leap61 ) && + ( other.leap59 == this->leap59 ) && + ( other.currentUtcOffsetValid == this->currentUtcOffsetValid ) && + ( other.ptpTimescale == this->ptpTimescale ) && + ( other.timeTraceable == this->timeTraceable ) && + ( other.frequencyTraceable == this->frequencyTraceable ) + ) + { + return true; + } + else + { + return false; + } +} + +bool +cPtpHeaderFlags::operator!= (const cPtpHeaderFlags& other) const +{ + return !(this->operator ==(other)); +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ + +cPtpHeaderFlags& +cPtpHeaderFlags::operator=(const cPtpHeaderFlags& other) +{ + this->alternateMasterFlag = other.alternateMasterFlag; + this->twoStepFlag = other.twoStepFlag; + this->unicastFlag = other.unicastFlag; + this->ptpProfileSpecific_1 = other.ptpProfileSpecific_1; + this->ptpProfileSpecific_2 = other.ptpProfileSpecific_2; + this->reserved = other.reserved; + this->leap61 = other.leap61; + this->leap59 = other.leap59; + this->currentUtcOffsetValid = other.currentUtcOffsetValid; + this->ptpTimescale = other.ptpTimescale; + this->timeTraceable = other.timeTraceable; + this->frequencyTraceable = other.frequencyTraceable; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const cPtpHeaderFlags& o) +{ + os << o.GetString(); // no endl! + + return os; +} + +// ------------------------------------------------------ +// Print +// ------------------------------------------------------ +void +cPtpHeaderFlags::Print() const +{ + EV << "Header flags:" << endl; + EV << endl; + EV << "[" << (alternateMasterFlag ? "x" : " ") << "]" << " Alternate master" << endl; + EV << "[" << (twoStepFlag ? "x" : " ") << "]" << " Two step" << endl; + EV << "[" << (unicastFlag ? "x" : " ") << "]" << " Unicast" << endl; + EV << "[" << (ptpProfileSpecific_1 ? "x" : " ") << "]" << " Profile specific 1" << endl; + EV << "[" << (ptpProfileSpecific_2 ? "x" : " ") << "]" << " Profile specific 2" << endl; + EV << "[" << (reserved ? "x" : " ") << "]" << " Reserved" << endl; + EV << "[" << (leap61 ? "x" : " ") << "]" << " Leap61" << endl; + EV << "[" << (leap59 ? "x" : " ") << "]" << " Leap59" << endl; + EV << "[" << (currentUtcOffsetValid ? "x" : " ") << "]" << " Current UTC offset valid" << endl; + EV << "[" << (ptpTimescale ? "x" : " ") << "]" << " PTP time scale" << endl; + EV << "[" << (timeTraceable ? "x" : " ") << "]" << " Time traceable" << endl; + EV << "[" << (frequencyTraceable ? "x" : " ") << "]" << " Frequency traceable" << endl; +} + +std::string +cPtpHeaderFlags::GetString() const +{ + std::stringstream ss; + + ss << (alternateMasterFlag ? " AM" : " - "); + ss << (twoStepFlag ? " 2s" : " - "); + ss << (unicastFlag ? "uni" : " - "); + ss << (ptpProfileSpecific_1 ? "ps1" : " - "); + ss << (ptpProfileSpecific_2 ? "ps2" : " - "); + ss << (reserved ? " r" : " - "); + ss << (leap61 ? "l61" : " - "); + ss << (leap59 ? "l51" : " - "); + ss << (currentUtcOffsetValid ? "UTC" : " - "); + ss << (ptpTimescale ? "PTP" : " - "); + ss << (timeTraceable ? " TT" : " - "); + ss << (frequencyTraceable ? " FT" : " - "); + + return ss.str(); +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.h b/src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.h new file mode 100644 index 0000000..3ef3fe0 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_HeaderFlags.h @@ -0,0 +1,83 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_HEADER_FLAGS_H_ +#define LIBPTP_PTP_HEADER_FLAGS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_Constants.h" +#include "PTP_ByteBuffers.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class cPtpHeaderFlags +{ + public: + + Boolean alternateMasterFlag; + Boolean twoStepFlag; + Boolean unicastFlag; + Boolean ptpProfileSpecific_1; + Boolean ptpProfileSpecific_2; + Boolean reserved; + Boolean leap61; + Boolean leap59; + Boolean currentUtcOffsetValid; + Boolean ptpTimescale; + Boolean timeTraceable; + Boolean frequencyTraceable; + + // Constructors/Destructor + cPtpHeaderFlags(); + cPtpHeaderFlags( const Octet flagField[] ); + cPtpHeaderFlags( const cPtpHeaderFlags& other ); + ~cPtpHeaderFlags(); + + // Setters + void ReadFromBuffer( const Octet flagField[] ); + + // Getters + void SaveToBuffer( Octet flagField[] ); + + // Operators + bool operator== (const cPtpHeaderFlags& other) const; + bool operator!= (const cPtpHeaderFlags& other) const; + cPtpHeaderFlags& operator= (const cPtpHeaderFlags& other); + + friend std::ostream& operator<<(std::ostream& os, const cPtpHeaderFlags& o); + + // Debug functions + void Print() const; + std::string GetString() const; +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PTPText.cc b/src/Software/PTP_Stack/DataTypes/PTP_PTPText.cc new file mode 100644 index 0000000..16c8857 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PTPText.cc @@ -0,0 +1,109 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PTPText.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cPTPText::cPTPText() +{ + s.assign(""); +} + +cPTPText::cPTPText( const char *Str ) +{ + s.max_size(); + s.assign(Str); +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +cPTPText::~cPTPText() +{ +} + +// ------------------------------------------------------ +// Setters/Getters +// ------------------------------------------------------ +UInteger8 cPTPText::GetLengthField() +{ + if( s.length() > 255 ) + return (UInteger8) 255; + else + return (UInteger8) s.length(); +} + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool cPTPText::operator== (const cPTPText& other) +{ + return (other.s == this->s); +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cPTPText& cPTPText::operator= (const cPTPText& other) +{ + this->s = other.s; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Assign operator (basic string) +// ------------------------------------------------------ +cPTPText& cPTPText::operator= (const char *Str) +{ + this->s.assign( Str ); + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PTPText.h b/src/Software/PTP_Stack/DataTypes/PTP_PTPText.h new file mode 100644 index 0000000..90b55ab --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PTPText.h @@ -0,0 +1,78 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PTPTEXT_H_ +#define LIBPTP_PTP_PTPTEXT_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// PTP text, see clause 5.3.9 in IEEE 1588-2008 +class cPTPText +{ + private: + + // Types + struct PTPText_t + { + UInteger8 lengthField; // Length of pTextField + Octet *pTextField; // Size defined by lengthField + }; + + // Resources + std::string s; + + protected: + + public: + + // Constructors + cPTPText(); + cPTPText( const char *Str ); + ~cPTPText(); + + // Setters/Getters + UInteger8 GetLengthField(); + + // Operators + bool operator== (const cPTPText& other); + cPTPText &operator= (const cPTPText& other); + cPTPText &operator= (const char *Str); + + // Debug functions +}; + + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PortAddress.cc b/src/Software/PTP_Stack/DataTypes/PTP_PortAddress.cc new file mode 100644 index 0000000..2515138 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PortAddress.cc @@ -0,0 +1,104 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PortAddress.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cPortAddress::cPortAddress() +{ + this->networkProtocol = PROTOCOL_UNKNOWN; + this->address = ""; +} + +cPortAddress::cPortAddress +( + const NetworkProtocol_t networkProtocol, + const char * address +) +{ + this->networkProtocol = networkProtocol; + this->address = address; +} + + +// ------------------------------------------------------ +// Setters/Getters +// ------------------------------------------------------ +// TODO: Implement + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool cPortAddress::operator== (const cPortAddress& other) +{ + if + ( + ( other.networkProtocol == this->networkProtocol ) && + ( other.address == this->address ) + ) + { + return true; + } + else + { + return false; + } + +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cPortAddress& cPortAddress::operator= (const cPortAddress& other) +{ + this->networkProtocol = other.networkProtocol; + this->address = other.address; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PortAddress.h b/src/Software/PTP_Stack/DataTypes/PTP_PortAddress.h new file mode 100644 index 0000000..2257643 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PortAddress.h @@ -0,0 +1,73 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PORTADDRESS_H_ +#define LIBPTP_PTP_PORTADDRESS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Port address, see clause 5.3.6 in IEEE 1588-2008 +class cPortAddress +{ + private: + struct PortAddress_t + { + Enumeration16 networkProtocol; // Protocol of physical layer + UInteger16 addressLength; // Length of pAddressField + Octet *pAddressField; // Size defined by addressLength + }; + + NetworkProtocol_t networkProtocol; // Protocol of physical layer + std::string address; + + protected: + + public: + + // Constructors + cPortAddress(); + cPortAddress( const NetworkProtocol_t networkProtocol, + const char * address); + + // Setters/Getters + // TODO: Implement + + // Operators + bool operator== (const cPortAddress& other); + cPortAddress& operator= (const cPortAddress& other); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.cc b/src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.cc new file mode 100644 index 0000000..2ad26e1 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.cc @@ -0,0 +1,250 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PortIdentity.h" +#include "ByteOrder.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cPortIdentity::cPortIdentity() +{ + this->clockIdentity = ""; + this->portNumber = 0; +} + +cPortIdentity::cPortIdentity( const char *ClockStr, UInteger16 portNumber ) +{ + this->clockIdentity = ClockStr; + this->portNumber = portNumber; +} + +cPortIdentity::cPortIdentity( const cClockIdentity clockIdentity, const UInteger16 portNumber ) +{ + this->clockIdentity = clockIdentity; + this->portNumber = portNumber; +} + +cPortIdentity::cPortIdentity( const BufPtpPortId_t *pBuf ) +{ + ReadFromBuffer( pBuf ); +} + +cPortIdentity::cPortIdentity( const cPortIdentity& other ) +{ + this->clockIdentity = other.clockIdentity; + this->portNumber = other.portNumber; +} + +cPortIdentity::~cPortIdentity() +{ +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cClockIdentity& +cPortIdentity::ClockIdentity() +{ + return this->clockIdentity; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cPortIdentity::SetPortNumber( UInteger16 portNumber ) +{ + this->portNumber = portNumber; +} + +void +cPortIdentity::ReadFromBuffer( const BufPtpPortId_t *pBuf ) +{ + this->clockIdentity.ReadFromBuffer( &pBuf->clockIdentity ); + this->portNumber = NetToHostUI16( pBuf->portNumber ); +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +UInteger16 +cPortIdentity::GetPortNumber() const +{ + return this->portNumber; +} + +std::string +cPortIdentity::GetString() const +{ + std::stringstream ss; + + ss << clockIdentity << ", Port " << portNumber; // no endl! + + return ss.str(); +} + +Octet +cPortIdentity::GetByteAt( size_t idx ) +{ + Octet ret = 0; + + if( idx <= 7 ) + { + ret = this->clockIdentity.GetByteAt( idx ); + } + else if( idx == 8 ) + { + ret = (Octet) (this->portNumber >> 8); + } + else if( idx == 9 ) + { + ret = (Octet) this->portNumber; + } + + return ret; +} + +void +cPortIdentity::SaveToBuffer( BufPtpPortId_t *pBuf ) +{ + clockIdentity.SaveToBuffer( &pBuf->clockIdentity ); + + pBuf->portNumber = HostToNetUI16( portNumber ); +} + +// ------------------------------------------------------ +// Compare operators +// ------------------------------------------------------ +bool +cPortIdentity::operator== (const cPortIdentity& other) const +{ + if + ( + ( this->clockIdentity == other.clockIdentity ) && + ( this->portNumber == other.portNumber ) + ) + { + return true; + } + else + { + return false; + } +} + +bool +cPortIdentity::operator!= (const cPortIdentity& other) const +{ + return !(this->operator ==(other)); +} + +bool +operator<(cPortIdentity const& lhs, cPortIdentity const& rhs) +{ + if( lhs.clockIdentity < rhs.clockIdentity) + return true; + + if( lhs.clockIdentity == rhs.clockIdentity ) + return lhs.portNumber < rhs.portNumber; + + return false; +} + +bool +operator>(cPortIdentity const& lhs, cPortIdentity const& rhs) +{ + if( lhs.clockIdentity > rhs.clockIdentity) + return true; + + if( lhs.clockIdentity == rhs.clockIdentity ) + return lhs.portNumber > rhs.portNumber; + + return false; +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cPortIdentity& +cPortIdentity::operator= (const cPortIdentity& other) +{ + this->clockIdentity = other.clockIdentity; + this->portNumber = other.portNumber; + + // By convention, always return *this + return *this; +} + +cPortIdentity& +cPortIdentity::operator= (const cClockIdentity& Clock) +{ + this->clockIdentity = Clock; + this->portNumber = 0; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const cPortIdentity& o) +{ + os << o.GetString(); // no endl! + + return os; +} + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +cPortIdentity::Print() +{ + EV << "PortIdentity: " << GetString() << endl; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.h b/src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.h new file mode 100644 index 0000000..8667b58 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PortIdentity.h @@ -0,0 +1,91 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PORTIDENTITY_H_ +#define LIBPTP_PTP_PORTIDENTITY_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ClockIdentity.h" +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_ByteBuffers.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Port identity, see clause 5.3.5 and 7.5.2 in IEEE 1588-2008 +class cPortIdentity +{ + private: + cClockIdentity clockIdentity; // + UInteger16 portNumber; // 1-N, where N is the number of ports + + protected: + + public: + + // Constructors + cPortIdentity(); + cPortIdentity( const char *ClockStr, UInteger16 portNumber ); + cPortIdentity( const cClockIdentity clockIdentity, const UInteger16 portNumber ); + cPortIdentity( const BufPtpPortId_t *pBuf ); + cPortIdentity( const cPortIdentity& other ); + ~cPortIdentity(); + + + // Instance methods + cClockIdentity& ClockIdentity(); + + // Setters + void SetPortNumber( UInteger16 portNumber ); + void ReadFromBuffer( const BufPtpPortId_t *pBuf ); + + //Getters + UInteger16 GetPortNumber() const; + std::string GetString() const; + Octet GetByteAt( size_t idx ); + + void SaveToBuffer( BufPtpPortId_t *pBuf ); + + // Operators + bool operator== (const cPortIdentity& other) const; + bool operator!= (const cPortIdentity& other) const; + friend bool operator<(cPortIdentity const& lhs, cPortIdentity const& rhs); + friend bool operator>(cPortIdentity const& lhs, cPortIdentity const& rhs); + + cPortIdentity& operator= (const cPortIdentity& other); + cPortIdentity& operator= (const cClockIdentity& Clock); + + friend std::ostream& operator<<(std::ostream& os, const cPortIdentity& o); + + // Debug functions + void Print(); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.cc b/src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.cc new file mode 100644 index 0000000..7099daf --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.cc @@ -0,0 +1,483 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ + +std::ostream& +operator<<(std::ostream& os, const tPtpMessageType& o ) +{ + switch( o ) + { + // Event + case PTP_TYPE_SYNC: os << "Sync"; break; + case PTP_TYPE_DELAY_REQ: os << "Delay Request"; break; + case PTP_TYPE_PDELAY_REQ: os << "Peer Delay Request"; break; + case PTP_TYPE_PDELAY_RESP: os << "Peer Delay Response"; break; + + // General + case PTP_TYPE_FOLLOW_UP: os << "Sync Follow Up"; break; + case PTP_TYPE_DELAY_RESP: os << "Delay Response"; break; + case PTP_TYPE_PDELAY_RESP_FU: os << "Peer Delay Response Follow Up"; break; + case PTP_TYPE_ANNOUNCE: os << "Announce"; break; + case PTP_TYPE_SIGNALING: os << "Signaling"; break; + case PTP_TYPE_MANAGEMENT: os << "Management"; break; + + default: + case PTP_TYPE_INVALID: os << "Invalid"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const PTP_Profile_t& o ) +{ + switch( o ) + { + case PTP_PROFILE_CUSTOM: os << "Custom profile"; break; + case PTP_PROFILE_DEFAULT_E2E: os << "Default profile, E2E"; break; + case PTP_PROFILE_DEFAULT_P2P: os << "Default profile, P2P"; break; + case PTP_PROFILE_POWER: os << "Power profile, C37.238"; break; + + default: os << "Unknown profile"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const PTP_ClockType_t& o) +{ + switch( o ) + { + case PTP_CLOCK_TYPE_ORDINARY: os << "ordinary/boundary clock"; break; + case PTP_CLOCK_TYPE_TRANSPARENT: os << "transparent clock"; break; + default: os << "unsupported clock type"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const PTP_BMCA_t& o ) +{ + switch( o ) + { + case BMCA_1588_2008_DEFAULT: os << "Default 1588-2008 Best Master Clock Algorithm"; break; + default: os << "Unknown Best Mast Clock Algorithm"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const PTP_MgmtProtocol_t&o ) +{ + switch( o ) + { + case MGMT_1588_2008_DEFAULT: os << "Default 1588-2008 management protocol"; break; + case MGMT_SNMP: os << "Simple Network Management Protocol (SNMP)"; break; + default: os << "Unknown management protocol"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const NetworkProtocol_t& o) +{ + switch( o ) + { + case PROTOCOL_UDP_IPv4: os << "UDP/IPv4"; break; + case PROTOCOL_UDP_IPv6: os << "UDP/IPv6"; break; + case PROTOCOL_IEEE_802_3: os << "IEEE 802.3"; break; + case PROTOCOL_DEVICE_NET: os << "DeviceNet"; break; + case PROTOCOL_CONTROL_NET: os << "ControlNet"; break; + case PROTOCOL_PROFINET: os << "ProfiNet"; break; + case PROTOCOL_UNKNOWN: os << "unknown protol"; break; + default: if(( 0x007 <= o ) && (o <= 0xEFFF)) + { + os << "reserved protocol"; + } + else if(( 0x007 <= o ) && (o <= 0xEFFF)) + { + os << "reserved profile protocol"; + } + else + { + os << "unknown protocol"; + } + break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const ClockClass_t& o) +{ + switch( o ) + { + case CLOCK_CLASS_PRIMARY: os << "primary"; break; + case CLOCK_CLASS_PRIMARY_HOLDOVER: os << "primary (holdover)"; break; + case CLOCK_CLASS_APP_SPECIFIC: os << "app specific"; break; + case CLOCK_CLASS_APP_SPECIFIC_HOLDOVER: os << "app specific (holdover)"; break; + case CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_A: os << "primary (holdover, degrade A)"; break; + case CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_A: os << "app specific (holdover, degrade A)"; break; + case CLOCK_CLASS_ALTERNATE_PTP_1_1: os << "alternate PTP profile 1.1"; break; + case CLOCK_CLASS_ALTERNATE_PTP_1_2: os << "alternate PTP profile 1.2"; break; + case CLOCK_CLASS_ALTERNATE_PTP_1_3: os << "alternate PTP profile 1.3"; break; + case CLOCK_CLASS_ALTERNATE_PTP_1_4: os << "alternate PTP profile 1.4"; break; + case CLOCK_CLASS_ALTERNATE_PTP_2_1: os << "alternate PTP profile 2.1"; break; + case CLOCK_CLASS_ALTERNATE_PTP_2_2: os << "alternate PTP profile 2.2"; break; + case CLOCK_CLASS_ALTERNATE_PTP_2_3: os << "alternate PTP profile 2.3"; break; + case CLOCK_CLASS_ALTERNATE_PTP_2_4: os << "alternate PTP profile 2.4"; break; + case CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_B: os << "primary (holdover, degrade B)"; break; + case CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_B: os << "app specific (holdover, degrade B)"; break; + case CLOCK_CLASS_ALTERNATE_PTP_3_1: os << "alternate PTP profile 3.1"; break; + case CLOCK_CLASS_ALTERNATE_PTP_3_2: os << "alternate PTP profile 3.2"; break; + case CLOCK_CLASS_ALTERNATE_PTP_3_3: os << "alternate PTP profile 3.3"; break; + case CLOCK_CLASS_ALTERNATE_PTP_3_4: os << "alternate PTP profile 3.4"; break; + case CLOCK_CLASS_DEFAULT: os << "default"; break; + case CLOCK_CLASS_PTP_V1: os << "PTPv1"; break; + case CLOCK_CLASS_SLAVE_ONLY: os << "slave only"; break; + + default: if + ( + ( 0 == o ) || + (( 9 <= o ) && ( o <= 10 )) + ) + { + os << "reserved for future PTP versions"; + } + else if + ( + (( 1 <= o ) && ( o <= 5 )) || + ( o == 8 ) || + (( 11 <= o ) && ( o <= 12 )) || + (( 15 <= o ) && ( o <= 51 )) || + (( 53 <= o ) && ( o <= 57 )) || + (( 59 <= o ) && ( o <= 67 )) || + (( 123 <= o ) && ( o <= 127 )) || + (( 128 <= o ) && ( o <= 132 )) || + (( 188 <= o ) && ( o <= 192 )) || + (( 194 <= o ) && ( o <= 215 )) || + (( 233 <= o ) && ( o <= 247 )) || + (( 249 <= o ) && ( o <= 250 )) || + (( 252 <= o ) && ( o <= 254 )) + ) + { + os << "reserved"; + } + else + { + os << "unknown"; + } + break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const clockAccuracy_t& o) +{ + switch( o ) + { + case CLOCK_ACCURACY_25_NS: os << "<25 ns"; break; + case CLOCK_ACCURACY_100_NS: os << "<100 ns"; break; + case CLOCK_ACCURACY_250_NS: os << "<250 ns"; break; + case CLOCK_ACCURACY_1_US: os << "<1 us"; break; + case CLOCK_ACCURACY_2_5_US: os << "<2.5 us"; break; + case CLOCK_ACCURACY_10_US: os << "<10 us"; break; + case CLOCK_ACCURACY_25_US: os << "<25 us"; break; + case CLOCK_ACCURACY_100_US: os << "<100 us"; break; + case CLOCK_ACCURACY_250_US: os << "<250 us"; break; + case CLOCK_ACCURACY_1_MS: os << "<1 ms"; break; + case CLOCK_ACCURACY_2_5_MS: os << "<2.5 ms"; break; + case CLOCK_ACCURACY_10_MS: os << "<10 ms"; break; + case CLOCK_ACCURACY_25_MS: os << "<25 ms"; break; + case CLOCK_ACCURACY_100_MS: os << "<100 ms"; break; + case CLOCK_ACCURACY_250_MS: os << "<250 ms"; break; + case CLOCK_ACCURACY_1_S: os << "<1 s"; break; + case CLOCK_ACCURACY_10_S: os << "<10 s"; break; + case CLOCK_ACCURACY_OVER_10_S: os << ">10 s"; break; + case CLOCK_ACCURACY_PROFILE_SPECIFIC_1: os << "profile specific 1"; break; + case CLOCK_ACCURACY_PROFILE_SPECIFIC_2: os << "profile specific 2"; break; + case CLOCK_ACCURACY_PROFILE_SPECIFIC_3: os << "profile specific 3"; break; + case CLOCK_ACCURACY_PROFILE_SPECIFIC_4: os << "profile specific 4"; break; + case CLOCK_ACCURACY_UNKNOWN: os << "unknown"; break; + + default: if + ( + (( 0x01 <= o ) && ( o <= 0x1F )) || + (( 0x32 <= o ) && ( o <= 0x79 )) || + ( o == 0xFF ) + ) + { + os << "reserved"; + } + else if(( 0x84 <= o ) && ( o <= 0xFD )) + { + os << "profile specific"; + } + else + { + os << "unknown"; + } + break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const timeSource_t& o) +{ + switch( o ) + { + case TIME_SRC_ATOMIC_CLOCK: os << "atomic clock"; break; + case TIME_SRC_GPS: os << "satellite based"; break; + case TIME_SRC_TERRESTRIAL_RADIO: os << "radio distributed time"; break; + case TIME_SRC_PTP: os << "PTP-based source external to the domain"; break; + case TIME_SRC_NTP: os << "NTP or SNTP"; break; + case TIME_SRC_HAND_SET: os << "hand set"; break; + case TIME_SRC_OTHER: os << "other"; break; + case TIME_SRC_INTERNAL_OSCILLATOR: os << "internal oscillator"; break; + case TIME_SRC_PROFILE_SPECIFIC_1: os << "profile specific 1"; break; + case TIME_SRC_PROFILE_SPECIFIC_2: os << "profile specific 2"; break; + case TIME_SRC_PROFILE_SPECIFIC_3: os << "profile specific 3"; break; + case TIME_SRC_PROFILE_SPECIFIC_4: os << "profile specific 4"; break; + + default: if(( 0xF4 <= o ) && ( o <= 0xFE )) + { + os << "profile specific"; + } + else if( o == 0xFF ) + { + os << "reserved"; + } + else + { + os << "unknown"; + } + break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const portState_t& o) +{ + switch( o ) + { + case PORT_STATE_INITIALIZING: os << "Initializing"; break; + case PORT_STATE_FAULTY: os << "Faulty"; break; + case PORT_STATE_DISABLED: os << "Disabled"; break; + case PORT_STATE_LISTENING: os << "Listening"; break; + case PORT_STATE_PRE_MASTER: os << "Pre-Master"; break; + case PORT_STATE_MASTER: os << "Master"; break; + case PORT_STATE_PASSIVE: os << "Passive"; break; + case PORT_STATE_UNCALIBRATED: os << "Uncalibrated"; break; + case PORT_STATE_SLAVE: os << "Slave"; break; + + default: os << "unknown"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const portStateDecision_t& o) +{ + switch( o ) + { + case PORT_SD_LIST: os << "Stay in listening"; break; + case PORT_SD_M1: os << "Master M1 (Grandmaster)"; break; + case PORT_SD_M2: os << "Master M2 (Grandmaster)"; break; + case PORT_SD_M3: os << "Master M3"; break; + case PORT_SD_P1: os << "Passive P1"; break; + case PORT_SD_P2: os << "Passive P2"; break; + case PORT_SD_S1: os << "Slave"; break; + + default: os << "unknown"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const portEvent_t& o) +{ + switch( o ) + { + case PORT_EVENT_POWERUP: os << "power up"; break; + case PORT_EVENT_INITIALIZE: os << "initialize"; break; + case PORT_EVENT_FAULT_DETECTED: os << "fault detected"; break; + case PORT_EVENT_FAULT_CLEARED: os << "fault cleared"; break; + case PORT_EVENT_STATE_DECISION: os << "state decision"; break; + case PORT_EVENT_ANNOUNCE_RCV_TIMEOUT: os << "announce receive timeout"; break; + case PORT_EVENT_QUALIFICATION_TIMEOUT: os << "qualification timeout"; break; + case PORT_EVENT_MASTER_CLOCK_SELECTED: os << "master clock selected"; break; + case PORT_EVENT_SYNCHRONIZATION_FAULT: os << "synchronization fault"; break; + case PORT_EVENT_DESIGNATED_ENABLE: os << "designated enable"; break; + case PORT_EVENT_DESIGNATED_DISABLE: os << "designated disable"; break; + + default: os << "unknown"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const delayMechanism_t& o) +{ + switch( o ) + { + case DELAY_MECH_E2E: os << "E2E"; break; + case DELAY_MECH_P2P: os << "P2P"; break; + case DELAY_MECH_DISABLED: os << "disabled"; break; + + default: os << "unknown"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const domainNumber_t& o) +{ + switch( o ) + { + case DOMAIN_DEFAULT: os << "default"; break; + case DOMAIN_ALTERNATE_1: os << "alternate domain 1"; break; + case DOMAIN_ALTERNATE_2: os << "alternate domain 2"; break; + case DOMAIN_ALTERNATE_3: os << "alternate domain 3"; break; + case DOMAIN_USER_1: os << "user defined domain 1"; break; + case DOMAIN_USER_2: os << "user defined domain 2"; break; + case DOMAIN_USER_3: os << "user defined domain 3"; break; + case DOMAIN_USER_4: os << "user defined domain 4"; break; + + default: if(( 4 <= o ) && ( o <= 127 )) + { + os << "user defined domain"; + } + else if(( 128 <= o ) && ( o <= 255 )) + { + os << "reserved"; break; + } + else + { + os << "unknown"; break; + } + break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const SeverityCode_t& o) +{ + switch( o ) + { + case SERVIRITY_EMERGENCY: os << "emergency"; break; + case SERVIRITY_ALERT: os << "alert"; break; + case SERVIRITY_CRITICAL: os << "critical"; break; + case SERVIRITY_ERROR: os << "error"; break; + case SERVIRITY_WARNING: os << "warning"; break; + case SERVIRITY_NOTICE: os << "notice"; break; + case SERVIRITY_INFORMATIONAL: os << "informational"; break; + case SERVIRITY_DEBUG: os << "debug"; break; + + default: os << "unknown"; break; + } + + return os; +} + +std::ostream& +operator<<(std::ostream& os, const TLV_Type_t& o) +{ + switch( o ) + { + case TLV_INVALID: os << "invalid"; break; + case TLV_MANAGEMENT: os << "management"; break; + case TLV_MANAGEMENT_ERROR_STATUS: os << "management error status"; break; + case TLV_ORGANIZATION_EXTENSION: os << "organization extension"; break; + case TLV_REQUEST_UNICAST_TRANSMISSION: os << "request unicast transmission"; break; + case TLV_GRANT_UNICAST_TRANSMISSION: os << "grand unicast transmission"; break; + case TLV_CANCEL_UNICAST_TRANSMISSION: os << "cancel unicast transmission"; break; + case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: os << "acknowledge cancel unicast transmission"; break; + case TLV_PATH_TRACE: os << "path trace"; break; + case TLV_ALTERNATE_TIME_OFFSET_INDICATOR: os << "alternate time offset indicator"; break; + case TLV_AUTHENTICATION: os << "authentication"; break; + case TLV_AUTHENTICATION_CHALLENGE: os << "authentication challenge"; break; + case TLV_SECURITY_ASSOCIATION_UPDATE: os << "security association update"; break; + case TLV_CUM_FREQ_SCALE_FACTOR_OFFSET: os << "cumulative frequency scale factor offset"; break; + + default: if(( 0x2004 <= o ) && ( o <= 0x3FFF )) + { + os << "reserved for Experimental TLVs"; + } + else if(( 0x4000 <= o ) && ( o <= 0xFFFF )) + { + os << "reserved"; + } + else + { + os << "unknown"; + } + break; + } + + return os; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.h b/src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.h new file mode 100644 index 0000000..57f2ea0 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_PrimitiveDataTypes.h @@ -0,0 +1,375 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PRIMITIVE_DATATYPES_H_ +#define LIBPTP_PTP_PRIMITIVE_DATATYPES_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include +#include + +// ====================================================== +// Types +// ====================================================== + +// ------------------------------------------------------ +// Primitive data types +// ------------------------------------------------------ +typedef bool Boolean; +typedef uint8_t Enumeration4; +typedef uint8_t Enumeration8; +typedef uint16_t Enumeration16; +typedef uint8_t UInteger4; +typedef int8_t Integer8; +typedef uint8_t UInteger8; +typedef int16_t Integer16; +typedef uint16_t UInteger16; +typedef int32_t Integer32; +typedef uint32_t UInteger32; +typedef uint64_t UInteger48; +typedef int64_t Integer64; +typedef uint8_t Nibble; +typedef uint8_t Octet; + +// ------------------------------------------------------ +// Enumerations +// ------------------------------------------------------ + +enum tPtpMessageType +{ + // Event + PTP_TYPE_SYNC = 0x00, + PTP_TYPE_DELAY_REQ = 0x01, + PTP_TYPE_PDELAY_REQ = 0x02, + PTP_TYPE_PDELAY_RESP = 0x03, + + // General + PTP_TYPE_FOLLOW_UP = 0x08, + PTP_TYPE_DELAY_RESP = 0x09, + PTP_TYPE_PDELAY_RESP_FU = 0x0A, + PTP_TYPE_ANNOUNCE = 0x0B, + PTP_TYPE_SIGNALING = 0x0C, + PTP_TYPE_MANAGEMENT = 0x0D, + + PTP_TYPE_INVALID = 0xFF, +}; + +// PTP Profile type +typedef enum +{ + PTP_PROFILE_CUSTOM, + PTP_PROFILE_DEFAULT_E2E, + PTP_PROFILE_DEFAULT_P2P, + PTP_PROFILE_POWER, +} +PTP_Profile_t; + +// Delay mechanism, see table 9 in IEEE 1588-2008 +typedef enum +{ + PTP_CLOCK_TYPE_ORDINARY, // includes boundary clocks + PTP_CLOCK_TYPE_TRANSPARENT, // +} +PTP_ClockType_t; + +// Best master clock algorithm +typedef enum +{ + BMCA_1588_2008_DEFAULT, // Default BMC algorithm as specified in IEEE1588-2008 +} +PTP_BMCA_t; + +// Management protocol +typedef enum +{ + MGMT_1588_2008_DEFAULT, // Default management protocol as specified in IEEE1588-2008 + MGMT_SNMP, // Simple network management protocol +} +PTP_MgmtProtocol_t; + +// Network protocol, see clause 7.4.1 and table 3 in IEEE 1588-2008 +typedef enum +{ + // 0x0000 Reserved + PROTOCOL_UDP_IPv4 = 0x0001, // Annex D + PROTOCOL_UDP_IPv6 = 0x0002, // Annex E + PROTOCOL_IEEE_802_3 = 0x0003, // Annex F + PROTOCOL_DEVICE_NET = 0x0004, // Annex G + PROTOCOL_CONTROL_NET = 0x0005, // Annex H + PROTOCOL_PROFINET = 0x0006, // Annex I + // 0x0007 - 0xEFFF Reserved for assignment by the Precise Networked Clock Working Group + // of the IM/ST Committee + // 0xF000 - 0xFFFD Reserved for assignment in a PTP profile + PROTOCOL_UNKNOWN = 0xFFFE, // + // 0xFFFF Reserved +} +NetworkProtocol_t; + +// Clock class, see clause 7.6.2.4 and table 5 in IEEE 1588-2008 +typedef enum +{ + // 0 Reserved to enable compatibility with future versions + // 1-5 Reserved + CLOCK_CLASS_PRIMARY = 6, // 6 Synchronized to primary reference time source + CLOCK_CLASS_PRIMARY_HOLDOVER = 7, // 7 Previously class 6, currently in holdover + // 8 Reserved + // 9-10 Reserved to enable compatibility with future versions + // 11-12 Reserved + CLOCK_CLASS_APP_SPECIFIC = 13, // 13 Synchronized to application specific reference time source + CLOCK_CLASS_APP_SPECIFIC_HOLDOVER = 14, // 14 Previously class 13, currently in holdover + // 15-51 Reserved + CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_A = 52, // 52 Degradation A for class 7 + // 53-57 Reserved + CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_A = 58, // 58 Degradation A for class 14 + // 59-67 Reserved + CLOCK_CLASS_ALTERNATE_PTP_1_1 = 68, // 68-122 For use by alternate PTP profiles + CLOCK_CLASS_ALTERNATE_PTP_1_2 = 69, // + CLOCK_CLASS_ALTERNATE_PTP_1_3 = 70, // + CLOCK_CLASS_ALTERNATE_PTP_1_4 = 71, // + // 123-127 Reserved + // 128-132 Reserved + CLOCK_CLASS_ALTERNATE_PTP_2_1 = 133, // 133-170 For use by alternate PTP profiles + CLOCK_CLASS_ALTERNATE_PTP_2_2 = 134, // + CLOCK_CLASS_ALTERNATE_PTP_2_3 = 135, // + CLOCK_CLASS_ALTERNATE_PTP_2_4 = 136, // + // 171-186 Reserved + CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_B = 187, // 187 Degradation B for class 7 + // 188-192 Reserved + CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_B = 193, // 193 Degradation B for class 14 + // 194-215 Reserved + CLOCK_CLASS_ALTERNATE_PTP_3_1 = 216, // 216-219 For use by alternate PTP profiles + CLOCK_CLASS_ALTERNATE_PTP_3_2 = 217, // + CLOCK_CLASS_ALTERNATE_PTP_3_3 = 218, // + CLOCK_CLASS_ALTERNATE_PTP_3_4 = 219, // + // 233-247 Reserved + CLOCK_CLASS_DEFAULT = 248, // 248 Default clock class, used if no other applies + // 249-250 Reserved + CLOCK_CLASS_PTP_V1 = 251, // 251 Reserved for version 1 compatibility + // 252-254 Reserved + CLOCK_CLASS_SLAVE_ONLY = 255, // 255 For slave-only clocks +} +ClockClass_t; + +// Clock accuracy, see table 6 in IEEE 1588-2008 +typedef enum +{ + // 1-1F Reserved + CLOCK_ACCURACY_25_NS = 0x20, // < 25ns + CLOCK_ACCURACY_100_NS = 0x21, // < 100ns + CLOCK_ACCURACY_250_NS = 0x22, // < 250ns + CLOCK_ACCURACY_1_US = 0x23, // < 1us + CLOCK_ACCURACY_2_5_US = 0x24, // < 2.5us + CLOCK_ACCURACY_10_US = 0x25, // < 10us + CLOCK_ACCURACY_25_US = 0x26, // < 25us + CLOCK_ACCURACY_100_US = 0x27, // < 100us + CLOCK_ACCURACY_250_US = 0x28, // < 250us + CLOCK_ACCURACY_1_MS = 0x29, // < 1ms + CLOCK_ACCURACY_2_5_MS = 0x2A, // < 2.5ms + CLOCK_ACCURACY_10_MS = 0x2B, // < 10ms + CLOCK_ACCURACY_25_MS = 0x2C, // < 25ms + CLOCK_ACCURACY_100_MS = 0x2D, // < 100ms + CLOCK_ACCURACY_250_MS = 0x2E, // < 250ms + CLOCK_ACCURACY_1_S = 0x2F, // < 1s + CLOCK_ACCURACY_10_S = 0x30, // < 10s + CLOCK_ACCURACY_OVER_10_S = 0x31, // > 10s + // 32-79 Reserved + CLOCK_ACCURACY_PROFILE_SPECIFIC_1 = 0x80, // Alternate PTP profile + CLOCK_ACCURACY_PROFILE_SPECIFIC_2 = 0x81, // Alternate PTP profile + CLOCK_ACCURACY_PROFILE_SPECIFIC_3 = 0x82, // Alternate PTP profile + CLOCK_ACCURACY_PROFILE_SPECIFIC_4 = 0x83, // Alternate PTP profile + // 84-FD Alternate PTP profile + CLOCK_ACCURACY_UNKNOWN = 0xFE, // Accuracy unknown + // FF Reserved +} +clockAccuracy_t; + +// Time source, see table 7 in IEEE 1588-2008 +typedef enum +{ + TIME_SRC_ATOMIC_CLOCK = 0x10, // Atomic clocks + TIME_SRC_GPS = 0x20, // Any satellite based time source + TIME_SRC_TERRESTRIAL_RADIO = 0x30, // Radio distributed time + TIME_SRC_PTP = 0x40, // Any PTP source outside the domain + TIME_SRC_NTP = 0x50, // NTP or SNTP + TIME_SRC_HAND_SET = 0x60, // Set by human interaction + TIME_SRC_OTHER = 0x90, // Other time source + TIME_SRC_INTERNAL_OSCILLATOR = 0xA0, // Based on free-running oscillator + TIME_SRC_PROFILE_SPECIFIC_1 = 0xF0, // Alternate PTP profile specific value + TIME_SRC_PROFILE_SPECIFIC_2 = 0xF1, // Alternate PTP profile specific value + TIME_SRC_PROFILE_SPECIFIC_3 = 0xF2, // Alternate PTP profile specific value + TIME_SRC_PROFILE_SPECIFIC_4 = 0xF3, // Alternate PTP profile specific value + // F4-FE Alternate PTP profile specific value + // FF Reserved +} +timeSource_t; + +// Port state, see table 8 in IEEE 1588-2008 +typedef enum +{ + PORT_STATE_INITIALIZING = 0x01, // + PORT_STATE_FAULTY = 0x02, // + PORT_STATE_DISABLED = 0x03, // + PORT_STATE_LISTENING = 0x04, // + PORT_STATE_PRE_MASTER = 0x05, // + PORT_STATE_MASTER = 0x06, // + PORT_STATE_PASSIVE = 0x07, // + PORT_STATE_UNCALIBRATED = 0x08, // + PORT_STATE_SLAVE = 0x09, // +} +portState_t; + +// Port state decisions as returned by state decision algorithm (Figure 26) +typedef enum +{ + PORT_SD_LIST = 10, // + PORT_SD_M1 = 43, // Numerical values for state decisions were chosen arbitrary + PORT_SD_M2 = 42, // + PORT_SD_M3 = 41, // Higher value implies state is more 'active' + PORT_SD_P1 = 22, // + PORT_SD_P2 = 21, // + PORT_SD_S1 = 31, // +} +portStateDecision_t; + +// Port events, see table 11 in IEEE 1588-2008 +typedef enum +{ + PORT_EVENT_POWERUP, // All ports + PORT_EVENT_INITIALIZE, // All ports + PORT_EVENT_INITIALIZE_DONE, // All ports, non-PTP-standard event + PORT_EVENT_FAULT_DETECTED, // All affected ports + PORT_EVENT_FAULT_CLEARED, // All affected ports + PORT_EVENT_STATE_DECISION, // All ports + PORT_EVENT_ANNOUNCE_RCV_TIMEOUT, // Single port + PORT_EVENT_QUALIFICATION_TIMEOUT, // Single port + PORT_EVENT_MASTER_CLOCK_SELECTED, // Single port + PORT_EVENT_SYNCHRONIZATION_FAULT, // Single port, trigger is implementation specific + PORT_EVENT_DESIGNATED_ENABLE, // Single port + PORT_EVENT_DESIGNATED_DISABLE, // Single port +} +portEvent_t; + +// Delay mechanism, see table 9 in IEEE 1588-2008 +typedef enum +{ + DELAY_MECH_E2E = 0x01, // + DELAY_MECH_P2P = 0x02, // + DELAY_MECH_DISABLED = 0xFE, // +} +delayMechanism_t; + +// Domain number, see table 2 in IEEE 1588-2008 +typedef enum +{ + DOMAIN_DEFAULT = 0, // 0 Default domain + DOMAIN_ALTERNATE_1 = 1, // 1-3 Alternate domains + DOMAIN_ALTERNATE_2 = 2, // + DOMAIN_ALTERNATE_3 = 3, // + DOMAIN_USER_1 = 4, // 4-127 User-defined domains + DOMAIN_USER_2 = 5, // + DOMAIN_USER_3 = 6, // + DOMAIN_USER_4 = 7, // + DOMAIN_RESERVED = 128, // 128-255 Reserved +} +domainNumber_t; + +// Severity Code, table 46 in IEEE 1588-2008 +typedef enum +{ + SERVIRITY_EMERGENCY = 0x00, // System is unusable + SERVIRITY_ALERT = 0x01, // Immediate action needed + SERVIRITY_CRITICAL = 0x02, // Critical conditions + SERVIRITY_ERROR = 0x03, // Error conditions + SERVIRITY_WARNING = 0x04, // Warning conditions + SERVIRITY_NOTICE = 0x05, // Normal but significant condition + SERVIRITY_INFORMATIONAL = 0x06, // Informational messages + SERVIRITY_DEBUG = 0x07, // Debug-level messages + // 0x08-0xFF Reserved +} +SeverityCode_t; + +// TLV Type, table 34 in IEEE 1588-2008 +typedef enum +{ + // Not defined in standard + TLV_INVALID = 0x0000, // 0x0000 Reserved + + // Standard TLVs + TLV_MANAGEMENT = 0x0001, // + TLV_MANAGEMENT_ERROR_STATUS = 0x0002, // + TLV_ORGANIZATION_EXTENSION = 0x0003, // + + // Optional unicast message negotiation TLVs + TLV_REQUEST_UNICAST_TRANSMISSION = 0x0004, // + TLV_GRANT_UNICAST_TRANSMISSION = 0x0005, // + TLV_CANCEL_UNICAST_TRANSMISSION = 0x0006, // + TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION = 0x0007, // + + // Optional path trace mechanism TLV + TLV_PATH_TRACE = 0x0008, // + + // Optional alternate timescale TLV + TLV_ALTERNATE_TIME_OFFSET_INDICATOR = 0x0009, // + // 0x000A - 0x1FFF Reserved for standard TLVs + // Experimental TLVs + // - + + // Security TLVs + TLV_AUTHENTICATION = 0x2000, // + TLV_AUTHENTICATION_CHALLENGE = 0x2001, // + TLV_SECURITY_ASSOCIATION_UPDATE = 0x2002, // + + // Cumulative frequency scale factor offset + TLV_CUM_FREQ_SCALE_FACTOR_OFFSET = 0x2003, // + // 0x2004 - 0x3FFF Reserved for Experimental TLVs + // 0x4000 - 0xFFFF Reserved +} +TLV_Type_t; + +// ====================================================== +// Declarations +// ====================================================== + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& operator<<(std::ostream& os, const tPtpMessageType& o ); +std::ostream& operator<<(std::ostream& os, const PTP_Profile_t& o ); +std::ostream& operator<<(std::ostream& os, const PTP_ClockType_t& o ); +std::ostream& operator<<(std::ostream& os, const PTP_BMCA_t& o ); +std::ostream& operator<<(std::ostream& os, const PTP_MgmtProtocol_t& o ); +std::ostream& operator<<(std::ostream& os, const NetworkProtocol_t& o ); +std::ostream& operator<<(std::ostream& os, const ClockClass_t& o ); +std::ostream& operator<<(std::ostream& os, const clockAccuracy_t& o ); +std::ostream& operator<<(std::ostream& os, const timeSource_t& o ); +std::ostream& operator<<(std::ostream& os, const portState_t& o ); +std::ostream& operator<<(std::ostream& os, const portStateDecision_t& o ); +std::ostream& operator<<(std::ostream& os, const portEvent_t& o ); +std::ostream& operator<<(std::ostream& os, const delayMechanism_t& o ); +std::ostream& operator<<(std::ostream& os, const domainNumber_t& o ); +std::ostream& operator<<(std::ostream& os, const SeverityCode_t& o ); +std::ostream& operator<<(std::ostream& os, const TLV_Type_t& o ); + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_TLV.cc b/src/Software/PTP_Stack/DataTypes/PTP_TLV.cc new file mode 100644 index 0000000..54fa479 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_TLV.cc @@ -0,0 +1,105 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_TLV.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cTLV::cTLV() +{ + this->tlvType = TLV_INVALID; +} + +cTLV::cTLV +( + TLV_Type_t tlvType, + UInteger16 lengthField, + Octet *pValueField +) +{ + this->tlvType = tlvType; + + Value.assign( pValueField, pValueField + lengthField); +} + +// ------------------------------------------------------ +// Setters/Getters +// ------------------------------------------------------ +// TODO + +// ------------------------------------------------------ +// Compare operator +// ------------------------------------------------------ +bool +cTLV::operator== (const cTLV& other) +{ + if + ( + ( other.tlvType == this->tlvType ) && + ( other.Value == this->Value ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Assign operator +// ------------------------------------------------------ +cTLV& +cTLV::operator= (const cTLV& other) +{ + this->tlvType = other.tlvType; + this->Value = other.Value; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_TLV.h b/src/Software/PTP_Stack/DataTypes/PTP_TLV.h new file mode 100644 index 0000000..85b9b96 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_TLV.h @@ -0,0 +1,74 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TLV_H_ +#define LIBPTP_PTP_TLV_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Type Length Value, see clause 5.3.8 in IEEE 1588-2008 +class cTLV +{ + private: + struct TLV_t + { + Enumeration16 tlvType; // + UInteger16 lengthField; // Length of lengthField + Octet *pValueField; // Size defined by lengthField + }; + + TLV_Type_t tlvType; + std::vector Value; + + protected: + + public: + + // Constructors + cTLV(); + cTLV( TLV_Type_t tlvType, + UInteger16 lengthField, + Octet *pValueField); + + // Setters/Getters + // TODO + + // Operators + bool operator== (const cTLV& other); + cTLV& operator= (const cTLV& other); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.cc b/src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.cc new file mode 100644 index 0000000..488dff5 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.cc @@ -0,0 +1,273 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_TimeInterval.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors / Destructor +// ------------------------------------------------------ +cTimeInterval::cTimeInterval() +{ + this->scaledNanoseconds = 0; +} + +cTimeInterval::cTimeInterval( simtime_t t ) +{ + // This conversion looses the fractional part of nanoseconds + this->scaledNanoseconds = t.inUnit( SIMTIME_NS ) << 16; +} + +cTimeInterval::cTimeInterval( Integer64 scaledNanoseconds ) +{ + this->scaledNanoseconds = scaledNanoseconds; +} + +cTimeInterval::cTimeInterval( const cTimeInterval& other ) +{ + this->scaledNanoseconds = other.scaledNanoseconds; +} + +cTimeInterval::~cTimeInterval() +{ +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +simtime_t +cTimeInterval::GetSimTime() +{ + Integer64 PicoSecs; + Integer64 NanoSecs; + + NanoSecs = this->scaledNanoseconds >> 16; + PicoSecs = (this->scaledNanoseconds - (NanoSecs << 16)); + PicoSecs *= 1000; + PicoSecs >>= 16; + + PicoSecs += NanoSecs * 1000; + + // This conversion looses the fractional part of picoseconds + simtime_t t( PicoSecs, SIMTIME_PS ); + + return t; +} + +std::string +cTimeInterval::GetString() const +{ + std::stringstream ss; + + ss << scaledNanoseconds << " [2^-16 ns]"; + + return ss.str(); +} + +Integer64 +cTimeInterval::GetScaledNanoseconds() const +{ + return scaledNanoseconds; +} + +// ------------------------------------------------------ +// Compare operators +// ------------------------------------------------------ +bool +cTimeInterval::operator== (const cTimeInterval& other) +{ + return other.scaledNanoseconds == this->scaledNanoseconds; +} + +bool +cTimeInterval::operator!= (const cTimeInterval& other) +{ + return !operator==(other); +} + +bool +cTimeInterval::operator== (const simtime_t& other) +{ + cTimeInterval ti( other ); + + return ti.scaledNanoseconds == this->scaledNanoseconds; +} + +bool +cTimeInterval::operator!= (const simtime_t& other) +{ + return !operator==(other); +} + +// ------------------------------------------------------ +// Assign operators +// ------------------------------------------------------ +cTimeInterval& +cTimeInterval::operator= (const cTimeInterval& other) +{ + this->scaledNanoseconds = other.scaledNanoseconds; + + // By convention, always return *this + return *this; +} + +cTimeInterval& +cTimeInterval::operator= (const Integer64 ScaledNanoSeconds) +{ + this->scaledNanoseconds = ScaledNanoSeconds; + + // By convention, always return *this + return *this; +} + +cTimeInterval& +cTimeInterval::operator= (const simtime_t& other) +{ + Integer64 PicoSecs; + + this->scaledNanoseconds = other.inUnit( SIMTIME_NS ) << 16; // Nanoseconds + + PicoSecs = other.remainderForUnit( SIMTIME_NS ).inUnit( SIMTIME_PS ); + PicoSecs <<= 16; + PicoSecs /= 1000; + + this->scaledNanoseconds += PicoSecs; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Numerical operators +// ------------------------------------------------------ + +const cTimeInterval +cTimeInterval::operator+(const cTimeInterval &other) const +{ + cTimeInterval tmp( *this ); + + return (tmp += other); +} + +const cTimeInterval +cTimeInterval::operator-(const cTimeInterval &other) const +{ + cTimeInterval tmp( *this ); + + return (tmp -= other); +} + +const cTimeInterval +cTimeInterval::operator*(const double d) const +{ + cTimeInterval tmp( *this ); + + return (tmp *= d); +} + +const cTimeInterval +cTimeInterval::operator/(const double d) const +{ + cTimeInterval tmp( *this ); + + return (tmp /= d); +} + +cTimeInterval & +cTimeInterval::operator+=(const cTimeInterval &other) +{ + this->scaledNanoseconds += other.scaledNanoseconds; + + // By convention, always return *this + return *this; +} + +cTimeInterval & +cTimeInterval::operator-=(const cTimeInterval &other) +{ + this->scaledNanoseconds -= other.scaledNanoseconds; + + // By convention, always return *this + return *this; +} + +cTimeInterval & +cTimeInterval::operator*=(const double d) +{ + this->scaledNanoseconds *= d; + + // By convention, always return *this + return *this; +} + +cTimeInterval & +cTimeInterval::operator/=(const double d) +{ + this->scaledNanoseconds /= d; + + // By convention, always return *this + return *this; +} + +const cTimeInterval +cTimeInterval::operator-() const +{ + return cTimeInterval( -this->scaledNanoseconds ); +} + +// ------------------------------------------------------ +// Stream operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const cTimeInterval& o) +{ + os << o.GetString(); // no endl! + + return os; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.h b/src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.h new file mode 100644 index 0000000..c900eac --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_TimeInterval.h @@ -0,0 +1,90 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TIMEINTERVAL_H_ +#define LIBPTP_PTP_TIMEINTERVAL_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Time interval, see clause 5.3.2 in IEEE 1588-2008 +class cTimeInterval +{ + private: + Integer64 scaledNanoseconds; // [ns], scaled by 2^16 + + protected: + + public: + // Constructors + cTimeInterval(); + cTimeInterval( simtime_t t ); + cTimeInterval( Integer64 scaledNanoseconds ); + cTimeInterval( const cTimeInterval& other ); + + // Destructor + ~cTimeInterval(); + + // Setters + + // Getters + simtime_t GetSimTime(); + std::string GetString() const; + Integer64 GetScaledNanoseconds() const; + + // Operators + bool operator== (const cTimeInterval& other); + bool operator!= (const cTimeInterval& other); + bool operator== (const simtime_t& other); + bool operator!= (const simtime_t& other); + cTimeInterval& operator= (const cTimeInterval& other); + cTimeInterval& operator= (const Integer64 ScaledNanoSeconds); + cTimeInterval& operator= (const simtime_t& other); + + const cTimeInterval operator+(const cTimeInterval &other) const; + const cTimeInterval operator-(const cTimeInterval &other) const; + const cTimeInterval operator*(const double d) const; + const cTimeInterval operator/(const double d) const; + const cTimeInterval operator-() const; + + cTimeInterval &operator+=(const cTimeInterval &other); + cTimeInterval &operator-=(const cTimeInterval &other); + cTimeInterval &operator*=(const double d); + cTimeInterval &operator/=(const double d); + + friend std::ostream& operator<<(std::ostream& os, const cTimeInterval& o); +}; + +#endif diff --git a/src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.cc b/src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.cc new file mode 100644 index 0000000..21a1674 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.cc @@ -0,0 +1,222 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_TimeStamp.h" + +#include "ByteOrder.h" +#include "PhysicalConstants.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors +// ------------------------------------------------------ +cTimeStamp::cTimeStamp() +{ + secondsField = 0; + nanosecondsField = 0; +} + +cTimeStamp::cTimeStamp( UInteger48 secondsField, + UInteger32 nanosecondsField ) +: secondsField(secondsField), + nanosecondsField(nanosecondsField) +{ +} + +cTimeStamp::cTimeStamp( simtime_t timestamp ) +{ + this->operator=(timestamp); +} + +cTimeStamp::cTimeStamp( BufPtpTimeStamp_t *pBuf ) +{ + ReadFromBuffer( pBuf ); +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +cTimeStamp::Set( UInteger48 secondsField, + UInteger32 nanosecondsField ) +{ + this->secondsField = secondsField; + this->nanosecondsField = nanosecondsField; +} + +void +cTimeStamp::ReadFromBuffer( BufPtpTimeStamp_t *pBuf ) +{ + secondsField = BufToHostUI48( pBuf->seconds, sizeof(pBuf->seconds) ); + nanosecondsField = NetToHostUI32( pBuf->nanoseconds ); +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +simtime_t +cTimeStamp::GetSimTime() +{ + int64_t NanoSecs = secondsField * NANOSECS_PER_S + nanosecondsField; + + return simtime_t(NanoSecs, SIMTIME_NS); +} + +UInteger48 +cTimeStamp::GetSeconds() const +{ + return secondsField; +} + +UInteger32 +cTimeStamp::GetNanoSeconds() const +{ + return nanosecondsField; +} + +std::string +cTimeStamp::GetString() const +{ + std::stringstream ss; + + ss << secondsField * NANOSECS_PER_S + nanosecondsField << " [ns]"; + + return ss.str(); +} + +void +cTimeStamp::SaveToBuffer( BufPtpTimeStamp_t *pBuf ) +{ + HostToBufUI48( secondsField, pBuf->seconds, sizeof(pBuf->seconds) ); + pBuf->nanoseconds = HostToNetUI32( nanosecondsField ); +} + +// ------------------------------------------------------ +// Compare operator (TimeStamp) +// ------------------------------------------------------ +bool +cTimeStamp::operator== (const cTimeStamp& other) +{ + if + ( + ( other.secondsField == this->secondsField ) && + ( other.nanosecondsField == this->nanosecondsField ) + ) + { + return true; + } + else + { + return false; + } +} + +// ------------------------------------------------------ +// Compare operator (simtime_t) +// ------------------------------------------------------ +bool +cTimeStamp::operator== (const simtime_t& other) +{ + return other == GetSimTime(); +} + +// ------------------------------------------------------ +// Assignment operator (TimeStamp) +// ------------------------------------------------------ +cTimeStamp & +cTimeStamp::operator= (const cTimeStamp& other) +{ + this->secondsField = other.secondsField; + this->nanosecondsField = other.nanosecondsField; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Assignment operator (simtime_t) +// ------------------------------------------------------ +cTimeStamp & +cTimeStamp::operator= (const simtime_t& other) +{ + this->secondsField = other.inUnit( SIMTIME_S ); + this->nanosecondsField = other.remainderForUnit( SIMTIME_S ).inUnit( SIMTIME_NS ); + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Assignment operator (int64_t) +// ------------------------------------------------------ +cTimeStamp & +cTimeStamp::operator= (const int64_t& other_ps) +{ + int64_t tmp; + int64_t NanoSecs; + int64_t Secs; + + tmp = (other_ps / 1000); + NanoSecs = tmp % NANOSECS_PER_S; + Secs = tmp / NANOSECS_PER_S; + + this->secondsField = Secs; + this->nanosecondsField = NanoSecs; + + // By convention, always return *this + return *this; +} + +// ------------------------------------------------------ +// Ouput operators +// ------------------------------------------------------ +std::ostream& +operator<<(std::ostream& os, const cTimeStamp& o) +{ + os << o.GetString(); // no endl! + + return os; +} diff --git a/src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.h b/src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.h new file mode 100644 index 0000000..0ce40c3 --- /dev/null +++ b/src/Software/PTP_Stack/DataTypes/PTP_TimeStamp.h @@ -0,0 +1,87 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TIMESTAMP_H_ +#define LIBPTP_PTP_TIMESTAMP_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_ByteBuffers.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// Time stamp, see clause 5.3.3 in IEEE 1588-2008 +class cTimeStamp +{ + private: + UInteger48 secondsField; // [s] + UInteger32 nanosecondsField; // [ns], always < 10^9 + + protected: + + public: + + // Constructors + cTimeStamp(); + cTimeStamp( UInteger48 secondsField, + UInteger32 nanosecondsField ); + cTimeStamp( simtime_t timestamp ); + cTimeStamp( BufPtpTimeStamp_t *pBuf ); + + // Setters + void Set(UInteger48 secondsField, + UInteger32 nanosecondsField ); + + void ReadFromBuffer( BufPtpTimeStamp_t *pBuf ); + + // Getters + simtime_t GetSimTime(); + UInteger48 GetSeconds() const; + UInteger32 GetNanoSeconds() const; + std::string GetString() const; + + void SaveToBuffer( BufPtpTimeStamp_t *pBuf ); + + // Operators + bool operator== (const cTimeStamp& other); + bool operator== (const simtime_t& other); + cTimeStamp& operator= (const cTimeStamp& other); + cTimeStamp& operator= (const simtime_t& other); + cTimeStamp& operator= (const int64_t& other_ps); + + friend std::ostream& operator<<(std::ostream& os, const cTimeStamp& o); + + // Debug functions +}; + +#endif diff --git a/src/Software/PTP_Stack/Includes/PTP.h b/src/Software/PTP_Stack/Includes/PTP.h new file mode 100644 index 0000000..5f134a3 --- /dev/null +++ b/src/Software/PTP_Stack/Includes/PTP.h @@ -0,0 +1,65 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_H_ +#define LIBPTP_PTP_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// Basic stuff +#include "PTP_Constants.h" +#include "PTP_PrimitiveDataTypes.h" + +// Derived data types +#include "PTP_ClockIdentity.h" +#include "PTP_ClockQuality.h" +#include "PTP_FaultRecord.h" +#include "PTP_PortAddress.h" +#include "PTP_PortIdentity.h" +#include "PTP_PTPText.h" +#include "PTP_TimeInterval.h" +#include "PTP_TimeStamp.h" +#include "PTP_TLV.h" + +// Data sets +#include "PTP_CurrentDS.h" +#include "PTP_DefaultDS.h" +#include "PTP_ParentDS.h" +#include "PTP_TimePropertiesDS.h" +#include "PTP_PortDS.h" +#include "PTP_TransparentClockDefaultDS.h" +#include "PTP_TransparentClockPortDS.h" +#include "PTP_ForeignMasterDS.h" + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +#endif diff --git a/src/Software/PTP_Stack/Includes/PTP_ByteBuffers.h b/src/Software/PTP_Stack/Includes/PTP_ByteBuffers.h new file mode 100644 index 0000000..a95c54d --- /dev/null +++ b/src/Software/PTP_Stack/Includes/PTP_ByteBuffers.h @@ -0,0 +1,150 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_BYTE_BUFFERS_H_ +#define LIBPTP_PTP_BYTE_BUFFERS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +struct __attribute__((packed)) BufPtpClockId_t +{ + Octet data[8]; +}; + +struct __attribute__((packed)) BufPtpPortId_t +{ + BufPtpClockId_t clockIdentity; + UInteger16 portNumber; +}; + +struct __attribute__((packed)) BufPtpTimeStamp_t +{ + Octet seconds[6]; + UInteger32 nanoseconds; +}; + +struct __attribute__((packed)) BufPtpClockQual_t +{ + UInteger8 clockClass; + Enumeration8 clockAccuracy; + UInteger16 offsetScaledLogVariance; +}; + +struct __attribute__((packed)) BufPtpHeader_t +{ + UInteger8 transportSpecific_messageType; + UInteger8 reserved_versionPTP; + UInteger16 messageLength; + UInteger8 domainNumber; + Octet reserved_1; + Octet flagField[2]; + Integer64 correctionField; + Octet reserved_2[4]; + BufPtpPortId_t sourcePortIdentity; + UInteger16 sequenceId; + UInteger8 controlField; + Integer8 logMessageInterval; +}; + +struct __attribute__((packed)) BufPtpAnnounce_t +{ + BufPtpTimeStamp_t originTimestamp; + Integer16 currentUtcOffset; + Octet reserved_0; + UInteger8 grandmasterPriority1; + BufPtpClockQual_t clockQuality; + UInteger8 grandmasterPriority2; + BufPtpClockId_t grandmasterIdentity; + UInteger16 stepsRemoved; + Enumeration8 timeSource; +}; + +struct __attribute__((packed)) BufPtpSync_t +{ + BufPtpTimeStamp_t originTimestamp; +}; + +struct __attribute__((packed)) BufPtpFollowUp_t +{ + BufPtpTimeStamp_t preciseOriginTimestamp; +}; + +struct __attribute__((packed)) BufPtpDelayReq_t +{ + BufPtpTimeStamp_t originTimestamp; +}; + +struct __attribute__((packed)) BufPtpDelayResp_t +{ + BufPtpTimeStamp_t receiveTimestamp; + BufPtpPortId_t requestingPortIdentity; +}; + +struct __attribute__((packed)) BufPtpPDelayReq_t +{ + BufPtpTimeStamp_t originTimestamp; + Octet reserved[10]; +} +; + +struct __attribute__((packed)) BufPtpPDelayResp_t +{ + BufPtpTimeStamp_t requestReceiptTimestamp; + BufPtpPortId_t requestingPortIdentity; +}; + +struct __attribute__((packed)) BufPtpPDelayRespFU_t +{ + BufPtpTimeStamp_t responseOriginTimestamp; + BufPtpPortId_t requestingPortIdentity; +}; + +struct __attribute__((packed)) BufPtpData_t +{ + BufPtpHeader_t Header; + union + { + BufPtpAnnounce_t Announce; + BufPtpSync_t Sync; + BufPtpFollowUp_t FollowUp; + BufPtpDelayReq_t DelayReq; + BufPtpDelayResp_t DelayResp; + BufPtpPDelayReq_t PDelayReq; + BufPtpPDelayResp_t PDelayResp; + BufPtpPDelayRespFU_t PDelayRespFU; + } + PtpFrame; +}; + +#endif diff --git a/src/Software/PTP_Stack/Includes/PTP_Constants.h b/src/Software/PTP_Stack/Includes/PTP_Constants.h new file mode 100644 index 0000000..8f2cfe3 --- /dev/null +++ b/src/Software/PTP_Stack/Includes/PTP_Constants.h @@ -0,0 +1,70 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_CONSTANTS_H_ +#define LIBPTP_PTP_CONSTANTS_H_ + +// ====================================================== +// Includes +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +#define PTP_CLOCK_IDENTITY_SIZE 8 // Length of Clock Identity array +#define PTP_DEFAULT_PHASE_CHANGE_RATE 0x7FFFFFFF // +#define PTP_UTC_OFFSET_2006 33 // [s] +#define PTP_VERSION_IEEE_1588_2008 2 // Version number of IEEE 1588-2008 + +#define PTP_MSG_ANNOUNCE_LEN 64 // Message lengths as given in 13 +#define PTP_MSG_SYNC_LEN 44 // +#define PTP_MSG_FOLLOW_UP_LEN 44 // +#define PTP_MSG_DEL_REQ_LEN 44 // +#define PTP_MSG_DEL_RESP_LEN 54 // +#define PTP_MSG_PDEL_REQ_LEN 54 // +#define PTP_MSG_PDEL_RESP_LEN 54 // +#define PTP_MSG_PDEL_RESP_FU_LEN 54 // + + +#define PTP_FOREIGN_MASTER_TIME_WINDOW 4 // [announceInterval], 9.3.2.4.4 +#define PTP_FOREIGN_MASTER_THRESHOLD 2 // [announce messages], 9.3.2.4.4 + +#define PTP_LOG_MSG_INTVL_DELAY_REQ 0x7F // Table 24 +#define PTP_LOG_MSG_INTVL_SIGNALING 0x7F // +#define PTP_LOG_MSG_INTVL_MANAGEMENT 0x7F // +#define PTP_LOG_MSG_INTVL_PDELAY_REQ 0x7F // +#define PTP_LOG_MSG_INTVL_PDELAY_RESP 0x7F // +#define PTP_LOG_MSG_INTVL_PDELAY_RESP_FU 0x7F // + +#define PTP_MSG_CTRL_SYNC 0x00 // Table 23 +#define PTP_MSG_CTRL_DEL_REQ 0x01 // +#define PTP_MSG_CTRL_FOLLOW_UP 0x02 // +#define PTP_MSG_CTRL_DEL_RESP 0x03 // +#define PTP_MSG_CTRL_MGMT 0x04 // +#define PTP_MSG_CTRL_OTHERS 0x05 // + +// ====================================================== +// Types +// ====================================================== + +#endif /* PTP_CONSTANTS_H_ */ diff --git a/src/Software/PTP_Stack/Includes/PTP_Ethernet.h b/src/Software/PTP_Stack/Includes/PTP_Ethernet.h new file mode 100644 index 0000000..9035175 --- /dev/null +++ b/src/Software/PTP_Stack/Includes/PTP_Ethernet.h @@ -0,0 +1,66 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_ETHERNET_H_ +#define LIBPTP_PTP_ETHERNET_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" +#include "PTP_ByteBuffers.h" + +// ====================================================== +// Declarations +// ====================================================== + +//extern const int PTP_ETH_TYPE; + +#define PTP_ETH_TYPE 0x88F7 // + +#define PTP_ETH_MC_DEFAULT_MAC "01:1B:19:00:00:00" // Table F.1 +#define PTP_ETH_MC_PDELAY_MAC "01:80:C2:00:00:0E" // + +#define ETH_HEADER_LEN 14 +#define PTP_HEADER_LEN 34 +#define ETH_PTP_FRAME_OFFSET ( ETH_HEADER_LEN + PTP_HEADER_LEN ) +#define ETH_PTP_MSG_ANNOUNCE_LEN ( ETH_HEADER_LEN + PTP_MSG_ANNOUNCE_LEN ) +#define ETH_PTP_MSG_SYNC_LEN ( ETH_HEADER_LEN + PTP_MSG_SYNC_LEN ) +#define ETH_PTP_MSG_FOLLOW_UP_LEN ( ETH_HEADER_LEN + PTP_MSG_FOLLOW_UP_LEN ) +#define ETH_PTP_MSG_DEL_REQ_LEN ( ETH_HEADER_LEN + PTP_MSG_DEL_REQ_LEN ) +#define ETH_PTP_MSG_DEL_RESP_LEN ( ETH_HEADER_LEN + PTP_MSG_DEL_RESP_LEN ) +#define ETH_PTP_MSG_PDEL_REQ_LEN ( ETH_HEADER_LEN + PTP_MSG_PDEL_REQ_LEN ) +#define ETH_PTP_MSG_PDEL_RESP_LEN ( ETH_HEADER_LEN + PTP_MSG_PDEL_RESP_LEN ) +#define ETH_PTP_MSG_PDEL_RESP_FU_LEN ( ETH_HEADER_LEN + PTP_MSG_PDEL_RESP_FU_LEN ) + +// ====================================================== +// Types +// ====================================================== + +#endif + + + + + + diff --git a/src/Software/PTP_Stack/PTP_EventMsg/PTP_EventMsg.msg b/src/Software/PTP_Stack/PTP_EventMsg/PTP_EventMsg.msg new file mode 100644 index 0000000..8356bf8 --- /dev/null +++ b/src/Software/PTP_Stack/PTP_EventMsg/PTP_EventMsg.msg @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ================================================= +// Make needed C++ stuff available +// ================================================= + +cplusplus {{ +#include +}}; + +// ================================================= +// Class announcements +// ================================================= + +class noncobject portEvent_t; + +// ================================================= +// Type definitions +// ================================================= + +// ================================================= +// Message definitions +// ================================================= + +message PTP_EventMsg { + int PortID; + portEvent_t Event; +} diff --git a/src/Software/PTP_Stack/PTP_Messages/PTPv2.msg b/src/Software/PTP_Stack/PTP_Messages/PTPv2.msg new file mode 100644 index 0000000..0f17275 --- /dev/null +++ b/src/Software/PTP_Stack/PTP_Messages/PTPv2.msg @@ -0,0 +1,216 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ================================================= +// Make needed C++ stuff available +// ================================================= +cplusplus {{ +#include "PTP_HeaderFlags.h" +#include "PTP_PortIdentity.h" +#include "PTP_ClockIdentity.h" +#include "PTP_ClockQuality.h" +#include "PTP_TimeInterval.h" +#include "PTP_TimeStamp.h" +#include "PTP_PrimitiveDataTypes.h" +#include "LocalTimeStamp.h" +#include +}}; + +// ================================================= +// Class announcements +// ================================================= + +class noncobject Boolean; +class noncobject Enumeration4; +class noncobject Enumeration8; +class noncobject Enumeration16; +class noncobject Integer16; +class noncobject UInteger16; +class noncobject Integer32; +class noncobject UInteger32; +class noncobject Integer64; +class noncobject Octet; + +class noncobject cPtpHeaderFlags; +class noncobject cPortIdentity; +class noncobject cClockIdentity; +class noncobject cClockQuality; +class noncobject cTimeInterval; +class noncobject cTimeStamp; +class noncobject tPtpMessageType; +class noncobject cLocalTimeStamp; + +class noncobject simtime_t; + +// ================================================= +// Type definitions +// ================================================= + +// ================================================= +// Message definitions +// ================================================= + +// ---------------------------------------------- +// Basis PTP frame +// ---------------------------------------------- +packet PTPv2_Frame +{ + // Internal house keeping stuff + cLocalTimeStamp IngressTimeStamp; + + // Generic Header + uint8_t transportSpecific; + tPtpMessageType MessageType; + uint8_t reserved_0; + uint8_t versionPTP; + UInteger16 messageLength; + uint8_t domainNumber; + uint8_t reserved_1; + cPtpHeaderFlags flagField; + cTimeInterval correctionField; + uint8_t reserved_2 [4]; + cPortIdentity sourcePortIdentity; + UInteger16 sequenceId; + uint8_t controlField; + int8_t logMessageInterval; +} + +// ------------------------------------------------- +// Announce +// ------------------------------------------------- +packet PTPv2_AnnounceFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/Announce"; + + cTimeStamp originTimestamp; + Integer16 currentUtcOffset; + uint8_t reserved_0; + uint8_t grandmasterPriority1; + cClockQuality grandmasterClockQuality; + uint8_t grandmasterPriority2; + cClockIdentity grandmasterIdentity; + UInteger16 stepsRemoved; + uint8_t timeSource; + + // OMNeT only + int GrandMasterModuleID; +} + +// ------------------------------------------------- +// Sync +// ------------------------------------------------- +packet PTPv2_SyncFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/Sync"; + + cTimeStamp originTimestamp; +} + +// ------------------------------------------------- +// Follow_Up +// ------------------------------------------------- +packet PTPv2_Follow_UpFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/SyncFU"; + + cTimeStamp preciseOriginTimestamp; +} + +// ------------------------------------------------- +// Delay_Req +// ------------------------------------------------- +packet PTPv2_Delay_ReqFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/DelayReq"; + + cTimeStamp originTimestamp; +} + +// ------------------------------------------------- +// Delay_Resp +// ------------------------------------------------- +packet PTPv2_Delay_RespFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/DelayResp"; + + cTimeStamp receiveTimestamp; + cPortIdentity requestingPortIdentity; + + // ------------------------------------ + simtime_t ReqEgressTime; // Piggy back own Tx time back to app +} + +// ------------------------------------------------- +// PDelay_Req +// ------------------------------------------------- +packet PTPv2_PDelay_ReqFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/PDelayReq"; + + cTimeStamp originTimestamp; + uint8_t reservedDelayReq [10]; +} + +// ------------------------------------------------- +// PDelay_Resp +// ------------------------------------------------- +packet PTPv2_PDelay_RespFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/PDelayResp"; + + cTimeStamp requestReceiptTimestamp; + cPortIdentity requestingPortIdentity; + + // ------------------------------------ + simtime_t ReqEgressTime; // Piggy back own Tx time back to app +} + +// ------------------------------------------------- +// Pdelay_Resp_Follow_Up +// ------------------------------------------------- +packet PTPv2_PDelay_Resp_FU_Frame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/PDelayRespFU"; + + cTimeStamp responseOriginTimestamp; + cPortIdentity requestingPortIdentity; +} + +// ------------------------------------------------- +// Signaling +// ------------------------------------------------- +packet PTPv2_SignalingFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/Signaling"; + + cPortIdentity targetPortIdentity; +} + +// ------------------------------------------------- +// Management +// ------------------------------------------------- +packet PTPv2_ManagementFrame extends PTPv2_Frame +{ + string displayString = "i=PTP/Messages/Management"; + + cPortIdentity targetPortIdentity; +} diff --git a/src/Software/PTP_Stack/PTP_Stack.cc b/src/Software/PTP_Stack/PTP_Stack.cc new file mode 100644 index 0000000..b50db3e --- /dev/null +++ b/src/Software/PTP_Stack/PTP_Stack.cc @@ -0,0 +1,1050 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include +#include + +#include "MACAddress.h" + +#include "PTP.h" +#include "PTP_Port.h" +#include "PTP_Stack.h" + +#include "PTP_CustomProfileChecker.h" +#include "PTP_DefaultE2EProfileChecker.h" +#include "PTP_DefaultP2PProfileChecker.h" +#include "PTP_PowerProfileChecker.h" + +#include "PTP_Parser.h" + +#include "PtpPortConfig_m.h" +#include "PTP_EventMsg_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(PTP_Stack); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal utilities +// ------------------------------------------------------ +void +PTP_Stack::CheckPortIdx( int PortIdx ) +{ + if + ( + ( PortIdx < 0 ) || + ( PortIdx >= PtpGateSize ) + ) + { + throw cRuntimeError( "Caught an invalid PortIdx: %d", PortIdx ); + } +} + +// ------------------------------------------------------------ +// Clock servo handling +// ------------------------------------------------------------ +void +PTP_Stack::StartClockServo() +{ + if( !pClockServo->IsEnabled() ) + { + pClockServo->SetScaleFactorBounds( pClock->GetScaleFactor_LowerBound_ppb(), pClock->GetScaleFactor_UpperBound_ppb() ); + pClockServo->SetScaleFactor_ppb( pClock->GetScaleFactor_ppb() ); + pClockServo->Enable(); + } +} + +void +PTP_Stack::StopClockServo() +{ + pClockServo->Disable(); +} + +// ------------------------------------------------------ +// Parse parameters +// ------------------------------------------------------ +void +PTP_Stack::ParseStackParameters() +{ + // ------------------------------------------------------------------------- + // Parse parameters for Internal data + // ------------------------------------------------------------------------- + ModuleID = par( "NodeNumber" ).longValue(); + if( ModuleID < 0 ) + { + ModuleID = getId(); + par( "NodeNumber" ).setLongValue( ModuleID ); + } + PTP_ClockType = cPTP_Parser::ParsePtpClockType( par( "PTP_ClockType" ).stringValue() ); + Active_E2E_TC = par( "Active_E2E_TC" ).boolValue(); + MaxOffsetFromMaster = simtime_t( par( "MaxOffsetFromMaster" ).doubleValue() ); + + // ------------------------------------------------------------------------- + // Parse parameters for default data set + // ------------------------------------------------------------------------- + SlaveOnly = par( "SlaveOnly" ).boolValue(); + TwoStepFlag = par( "PTP_TwoStepFlag" ).boolValue(); + Priority1 = par( "Priority1" ).longValue(); + Priority2 = par( "Priority2" ).longValue(); + offsetScaledLogVariance = par( "OffsetScaledLogVariance" ).longValue(); + ClockAccuracy = cPTP_Parser::ParseClockAccuracy ( par( "ClockAccuracy" ).stringValue() ); + ClockClass = cPTP_Parser::ParseClockClass ( par( "ClockClass" ).stringValue() ); + PrimaryDomain = cPTP_Parser::ParseDomainNumber ( par( "DomainNumber" ).stringValue() ); + + if( par( "AutoMAC" ).boolValue() ) + { + MACAddress MAC( "C0:FF:EE:00:00:00" ); + + MAC.setAddressByte( 3, (ModuleID >> 16 & 0xFF) ); + MAC.setAddressByte( 4, (ModuleID >> 8 & 0xFF) ); + MAC.setAddressByte( 5, (ModuleID >> 0 & 0xFF) ); + + par( "MAC_Address" ).setStringValue( MAC.str() ); + } + + MAC = MACAddress( par( "MAC_Address" ).stringValue() ); + + if( par( "UseMacForClockID" ).boolValue() ) + { + ClockIdentity = MAC; + } + else + { + ClockIdentity = par( "ClockID" ).stringValue(); + } + + // Write back parsed clock ID + par( "ClockID" ).setStringValue( ClockIdentity.GetString() ); + + // ------------------------------------------------------------------------- + // Parse parameters for current data set + // ------------------------------------------------------------------------- + // Nothing needs to be done here + + // ------------------------------------------------------------------------- + // Parse parameters for parent data set + // ------------------------------------------------------------------------- + // Nothing needs to be done here + + // ------------------------------------------------------------------------- + // Parse parameters for Time properties data set + // ------------------------------------------------------------------------- + // Nothing needed for current data set + + // ------------------------------------------------------------------------- + // Parse PTP Event configuration + // ------------------------------------------------------------------------- + ParsePtpEvents(); + + // ------------------------------------------------------------------------- + // Debug configuration + // ------------------------------------------------------------------------- + EnableDebugOutput = par( "EnableDebugOutput" ).boolValue(); +} + +void +PTP_Stack::ParsePortParameters() +{ + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx++ ) + { + // ------------------------------------------------------------------------- + // Parse parameters for Port Data Sets + // ------------------------------------------------------------------------- + // Timing characteristics + delay mechanism are currently taken from app + LogSyncInterval = cPTP_Parser::ParseParameterInt8 ( this, "LogSyncInterval" ); + LogMinDelayReqInterval = cPTP_Parser::ParseParameterInt8 ( this, "LogMinDelayReqInterval" ); + LogMinPdelayReqInterval = cPTP_Parser::ParseParameterInt8 ( this, "LogMinPdelayReqInterval" ); + LogAnnounceInterval = cPTP_Parser::ParseParameterInt8 ( this, "LogAnnounceInterval" ); + AnnounceReceiptTimeout = cPTP_Parser::ParseParameterUInt8 ( this, "AnnounceReceiptTimeout" ); + Asymmetry = simtime_t( par( "Asymmetry" ).doubleValue() ); + } +} + +void +PTP_Stack::ParsePtpEvents() +{ + // Remark: currently only a simple parameter interface exists for fault simulation + + if( par( "SimulateFault" ).boolValue() ) + { + simtime_t FaultTime = simtime_t( par( "FaultTime" ).doubleValue() ); + simtime_t FaultDuration = simtime_t( par( "FaultDuration" ).doubleValue() ); + + int PortID = par( "FaultPortID" ).longValue(); + + if( PortID != 0 ) + { + CheckPortIdx( PortID - 1 ); + } + + if( FaultTime < SIMTIME_ZERO ) + { + error( "Real time value for the simulated fault occurrence must be strictly positive." ); + } + + if( FaultDuration < SIMTIME_ZERO ) + { + error( "Real time value for the simulated fault duration must be strictly positive." ); + } + + PTP_EventMsg *FaultMsg = new PTP_EventMsg( "PTP Fault Event" ); + PTP_EventMsg *RecoveryMsg = new PTP_EventMsg( "PTP Recovery Event" ); + + FaultMsg->setPortID( PortID ); + RecoveryMsg->setPortID( PortID ); + + FaultMsg->setEvent( PORT_EVENT_FAULT_DETECTED ); + RecoveryMsg->setEvent( PORT_EVENT_FAULT_CLEARED ); + + scheduleAt( FaultTime, FaultMsg ); + scheduleAt( FaultTime + FaultDuration, RecoveryMsg ); + } +} + + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +PTP_Stack::PTP_Stack() + : AppStateDecision() +{ + pProfileChecker = nullptr; +} + +// ------------------------------------------------------ +// Destructor +// ------------------------------------------------------ +PTP_Stack::~PTP_Stack() +{ + delete[] pPorts; + delete pProfileChecker; +} + +// ------------------------------------------------------ +// API functions for services +// ------------------------------------------------------ + +void +PTP_Stack::SetOffsetFromMaster( simtime_t offsetFromMaster ) +{ + currentDS.SetOffsetFromMaster( offsetFromMaster ); + + this->offsetFromMaster = offsetFromMaster; +} + +void +PTP_Stack::SetMeanPathDelay( simtime_t meanPathDelay ) +{ + currentDS.SetMeanPathDelay( meanPathDelay ); + + this->meanPathDelay = meanPathDelay; +} + +void +PTP_Stack::IssueFrame( PTPv2_Frame *pPTP, UInteger16 PortNumber ) +{ + assert( PortNumber >= 1 ); + + take(pPTP); + send( pPTP, PtpOutGateID + PortNumber - 1 ); +} + +void +PTP_Stack::ConfigNIC_MAC() +{ + PtpNicConfig_MAC *pConfig = new PtpNicConfig_MAC( "PTP NIC config: MAC" ); + + pConfig->setType( PTP_NIC_CONF_TYPE_MAC ); + pConfig->setMAC( MAC ); + + send( pConfig, ConfigGateID ); +} + +void +PTP_Stack::ConfigPortPathDelay( uint16_t PortID, simtime_t meanPathDelay ) +{ + PtpPortConfig_PathDelay *pConfig = new PtpPortConfig_PathDelay( "PTP port config: path delay" ); + + pConfig->setType( PTP_PORT_CONF_TYPE_PATH_DELAY ); + pConfig->setMeanPathDelay( meanPathDelay ); + + send( pConfig, ConfigGateID + PortID - 1 ); +} + +void +PTP_Stack::ConfigPortAsymmetry( uint16_t PortID, simtime_t Asymmetry ) +{ + PtpPortConfig_Asymmetry *pConfig = new PtpPortConfig_Asymmetry( "PTP port config: asymmetry" ); + + pConfig->setType( PTP_PORT_CONF_TYPE_ASYMMETRY ); + pConfig->setAsymmetry( Asymmetry ); + + send( pConfig, ConfigGateID + PortID - 1 ); +} + +void +PTP_Stack::EmitSignal_offsetFromMaster_raw( simtime_t offsetFromMaster_raw ) +{ + emit( offsetFromMaster_raw_SigId, offsetFromMaster_raw ); +} + +void +PTP_Stack::EmitSignal_meanPathDelay_raw( simtime_t meanPathDelay_raw ) +{ + emit( meanPathDelay_raw_SigId, meanPathDelay_raw ); +} + + +// ------------------------------------------------------ +// Debug functions +// ------------------------------------------------------ +void +PTP_Stack::PrintInfo() +{ + EV << "================================================" << endl; + EV << "PTP Clock information:" << endl; + EV << "================================================" << endl; + EV << endl; + EV << "ModuleID: " << ModuleID << endl; + EV << "Clock ModuleID " << pClock->getId() << endl; + + EV << "PTP clock type: " << PTP_ClockType << endl; + EV << "Delay Mechanism: " << DelayMechanism << endl; + EV << "Number of Ports: " << PtpGateSize << endl; + + defaultDS.ClockIdentity().Print(); + EV << "================================================" << endl; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +PTP_Stack::ParseResourceParameters() +{ + Enabled = par( "Enabled" ).boolValue(); + + if( Enabled ) + { + ClockPath = par( "ClockPath" ).stringValue(); + ClockServoPath = par( "ClockServoPath" ).stringValue(); + + BMCA = cPTP_Parser::ParseBestMasterClockAlgorithm( par( "PTP_BestMasterClockAlgorithm" ).stringValue() ); + MgmtProtocol = cPTP_Parser::ParseManagementProtocol( par( "PTP_ManagementProtocol" ).stringValue() ); + PTP_Profile = cPTP_Parser::ParsePtpProfile( par( "PTP_Profile" ).stringValue() ); + } +} + +void +PTP_Stack::AllocateResources() +{ + if( !Enabled ) + { + return; + } + + PtpInGateID = gateBaseId( "PtpIn" ); + PtpOutGateID = gateBaseId( "PtpOut" ); + RequestGateID = gateBaseId( "PortRequest" ); + ConfigGateID = gateBaseId( "PortConfig" ); + PtpGateSize = gateSize( "PtpOut" ); + + if + ( + ( gateSize( "PtpIn" ) != PtpGateSize ) || + ( gateSize( "PtpOut" ) != PtpGateSize ) || + ( gateSize( "PortRequest" ) != PtpGateSize ) || + ( gateSize( "PortConfig" ) != PtpGateSize ) + ) + { + error( "Size of vector gates must match." ); + } + + pPorts = new cPTP_Port[ (size_t) PtpGateSize ]; + pClock = check_and_cast( getModuleByPath( ClockPath.c_str() ) ); + pClockServo = check_and_cast( getModuleByPath( ClockServoPath.c_str() ) ); + + switch( PTP_Profile ) + { + default: + case PTP_PROFILE_CUSTOM: pProfileChecker = new PTP_CustomProfileChecker(); + break; + + case PTP_PROFILE_DEFAULT_E2E: pProfileChecker = new PTP_DefaultE2EProfileChecker(); + break; + + case PTP_PROFILE_DEFAULT_P2P: pProfileChecker = new PTP_DefaultP2PProfileChecker(); + break; + + case PTP_PROFILE_POWER: pProfileChecker = new PTP_PowerProfileChecker(); + break; + } +} + +void +PTP_Stack::InitHierarchy() +{ + if( !Enabled ) + { + return; + } + + currentDS.SetParentModule ( this ); + parentDS.SetParentModule ( this ); + timePropertiesDS.SetParentModule( this ); + + defaultDS.SetHierarchy ( this ); + + AppStateDecision.SetParentModule( this ); + AppStateDecision.SetCallableParentModule( this ); + AppStateDecision.SetHierarchy ( this, pClock ); + + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].SetParentModule( this ); + pPorts[PortIdx].SetHierarchy( this, pClock, pClockServo, PortIdx + 1 ); + } +} + +void +PTP_Stack::ParseParameters() +{ + if( !Enabled ) + { + return; + } + + ParseStackParameters(); + ParsePortParameters(); +} + +void +PTP_Stack::RegisterSignals() +{ + if( !Enabled ) + { + return; + } + + offsetFromMaster_raw_SigId = registerSignal("offsetFromMaster_raw"); + meanPathDelay_raw_SigId = registerSignal("meanPathDelay_raw"); + + // WATCH variables + offsetFromMaster = SIMTIME_ZERO; + meanPathDelay = SIMTIME_ZERO; + + WATCH(offsetFromMaster); + WATCH(meanPathDelay); + WATCH(parentDS.parentPortIdentity); + WATCH(parentDS.grandmasterIdentity); +} + +void +PTP_Stack::InitInternalState() +{ + if( !Enabled ) + { + return; + } + + // ------------------------------------------------------------------------- + // Populate default data set + // ------------------------------------------------------------------------- + defaultDS.SetTwoStepFlag(TwoStepFlag); + defaultDS.SetNumberPorts(PtpGateSize); + + defaultDS.ClockIdentity() = ClockIdentity; + + defaultDS.SetSlaveOnly(SlaveOnly); // Has to be initialized before clockClass + + defaultDS.ClockQuality().SetClockClass( ClockClass ); + defaultDS.ClockQuality().SetClockAccuracy( ClockAccuracy ); + defaultDS.ClockQuality().SetOffsetScaledLogVar( offsetScaledLogVariance ); + defaultDS.SetDomainNumber(PrimaryDomain); + + defaultDS.SetPriority1( Priority1 ); + defaultDS.SetPriority2( Priority2 ); + + // Save contents of defaultDS in D0 for future comparison + D0 = defaultDS; + + // ------------------------------------------------------------------------- + // Populate current data set + // This data set has to be maintained during runtime, + // thus it is only initialized to dummy values + // ------------------------------------------------------------------------- + currentDS.SetMeanPathDelay( cTimeInterval() ); + currentDS.SetOffsetFromMaster( cTimeInterval() ); + currentDS.SetStepsRemoved(0); + + // ------------------------------------------------------------------------- + // Populate parent data set (Initialize mostly to defaultDS-values + // ------------------------------------------------------------------------- + parentDS.ParentPortIdentity().ClockIdentity() = defaultDS.ClockIdentity(); // 8.2.3.2 + parentDS.ParentPortIdentity().SetPortNumber(0); // 8.2.3.2 + + parentDS.SetParentStats(false); // 8.2.3.3 + 8.2.3.4 + parentDS.SetObservedParentOffsetScaledLogVariance(0); // 8.2.3.4 + parentDS.SetObservedParentClockPhaseChangeRate(PTP_DEFAULT_PHASE_CHANGE_RATE); // 8.2.3.5 + + parentDS.SetGrandmasterIdentity( defaultDS.ClockIdentity() ); // 8.2.3.6 + parentDS.GrandmasterClockQuality() = defaultDS.ClockQuality(); // 8.2.3.7 + + parentDS.SetGrandmasterPriority1( defaultDS.GetPriority1() ); // 8.2.3.8 + parentDS.SetGrandmasterPriority2( defaultDS.GetPriority2() ); // 8.2.3.8 + + parentDS.SetGrandMasterModuleID( defaultDS.GetModuleID() ); + + // ------------------------------------------------------------------------- + // Populate time properties data set + // ------------------------------------------------------------------------- + + // 8.2.4.8 + // 8.2.4.1: Needs to be initialized before the other variables + // False implies ARB (arbitrary) time scale + timePropertiesDS.SetPtpTimescale(false); + + // 8.2.4.2 + // 7.2.3, Note: UTC offset on 1 January 2006 00:00 was +33 seconds + timePropertiesDS.SetCurrentUtcOffset( PTP_UTC_OFFSET_2006 ); + + // 8.2.4.3 + timePropertiesDS.SetCurrentUtcOffsetValid(false); + + // 8.2.4.4 + timePropertiesDS.SetLeap59( 0 ); + + // 8.2.4.5 + timePropertiesDS.SetLeap61( 0 ); + + // 8.2.4.6 + timePropertiesDS.SetTimeTraceable(false); + + // 8.2.4.7 + timePropertiesDS.SetFrequencyTraceable(false); + + // 8.2.4. + timePropertiesDS.SetTimeSource( TIME_SRC_INTERNAL_OSCILLATOR ); + + // ------------------------------------------------------------------------- + // Calculate values for internal housekeeping + // ------------------------------------------------------------------------- + StateDecisionInterval = pow( 2.0, cPTP_Parser::ParseParameterInt8 ( this, "LogAnnounceInterval" ) ); + + DelayMechanism = cPTP_Parser::ParseDelayMechanism( par( "PTP_DelayMechanism" ).stringValue() ); + + // ------------------------------------------------------------------------- + // Transparent Clock Data Sets + // ------------------------------------------------------------------------- + // + // transparentClockDefaultDS + transparentClockPortDS are currently not + // implemented, as TC use the defaultDS and portDS of ordinary clocks + // + // ------------------------------------------------------------------------- + + // TODO: care about transparent clock data sets + + // Init ports + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + cPortDS &portDS = pPorts[PortIdx].PortDS(); + + // Static members + portDS.PortIdentity().ClockIdentity() = defaultDS.ClockIdentity(); + portDS.PortIdentity().SetPortNumber( PortIdx+1 ); + + // Dynamic members + portDS.SetPortState( PORT_STATE_INITIALIZING ); + portDS.SetLogMinDelayReqInterval( LogMinDelayReqInterval ); + portDS.SetPeerMeanPathDelay( cTimeInterval() ); // 8.2.5.3.3 Init value is 0 + + // Configurable members + portDS.SetLogAnnounceInterval ( LogAnnounceInterval ); // + portDS.SetAnnounceReceiptTimeout ( AnnounceReceiptTimeout ); // + portDS.SetLogSyncInterval ( LogSyncInterval ); // + portDS.SetDelayMechanism ( DelayMechanism ); // E2E or P2P + portDS.SetLogMinPdelayReqInterval ( LogMinPdelayReqInterval ); // + portDS.SetVersionNumber ( PTP_VERSION_IEEE_1588_2008); // Has to be 2 for IEEE 1588-2008 + portDS.SetAsymmetry ( Asymmetry ); + + // Init foreign master data set of port + pPorts[PortIdx].ForeignMasterDS().SetForeignMasterTimeWindow( 4.0 * pow(2.0, LogAnnounceInterval) ); // 9.3.2.4.4: ForeignMasterTimeWindow = 4 * announceInterval + } + + // Clock servo + StopClockServo(); + + // House keeping + AppStateDecision.SetInterval( StateDecisionInterval ); + + timePropertiesDS_Backup = timePropertiesDS; + + // Init NIC Ctrl + ConfigNIC_MAC(); +} + +void +PTP_Stack::InitSignals() +{ +} + +void +PTP_Stack::FinishInit() +{ + if( !Enabled ) + { + return; + } + + // Check profile parameters + pProfileChecker->CheckParametersFixed(this); + pProfileChecker->CheckParametersInit(this); + + // Simulate initial startup + StartInitialize(); +} + +void +PTP_Stack::PrintDebugOutput() +{ + if( !Enabled ) + { + return; + } + + if( EnableDebugOutput ) + { + PrintInfo(); + + defaultDS.Print(); + currentDS.Print(); + parentDS.Print(); + timePropertiesDS.Print(); + + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].PortDS().Print(); + } + } +} + +void +PTP_Stack::ForwardInit( int stage ) +{ + // Data sets + currentDS.initialize( stage ); + parentDS.initialize( stage ); + timePropertiesDS.initialize( stage ); + + // Services + AppStateDecision.initialize( stage ); + + // Profiles + pProfileChecker->initialize(stage); + + // Ports + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].initialize( stage); + } +} + +// ------------------------------------------------------ +// Handle Message +// ------------------------------------------------------ +void PTP_Stack::handleMessage(cMessage *pMsg) +{ + if( Enabled ) + { + if( pMsg->isSelfMessage() ) + { + // ------------------------------------------------------ + // PTP Events + // ------------------------------------------------------ + PTP_EventMsg *pEventMsg = check_and_cast(pMsg); + + HandleEvent( pEventMsg->getEvent(), pEventMsg->getPortID() ); + } + else + { + // ------------------------------------------------------ + // PTP Frames + // ------------------------------------------------------ + if( pMsg->getArrivalGate()->getBaseId() == PtpInGateID ) + { + int PortIdx = pMsg->getArrivalGateId() - PtpInGateID; + + CheckPortIdx( PortIdx ); + + HandlePtpFrame( check_and_cast(pMsg), PortIdx ); + } + // ------------------------------------------------------ + // PTP Port Requests + // ------------------------------------------------------ + else + { + HandlePortRequ( check_and_cast(pMsg) ); + } + } + } + + delete pMsg; +} + +// ------------------------------------------------------ +// Finish +// ------------------------------------------------------ +void PTP_Stack::finish() +{ +} + +void +PTP_Stack::HandleTimejump( simtime_t Delta ) +{ + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].HandleTimejump( Delta ); + } +} + +void +PTP_Stack::HandlePortStateDec( portStateDecision_t StateDecision ) +{ + // Update data sets + switch( StateDecision ) + { + // This instance is the grandmaster of the system + case PORT_SD_M1: + case PORT_SD_M2: + { + BecomeGrandMaster(); + break; + } + + // At least one port is in slave state + case PORT_SD_S1: + { + BecomeSlave( Ebest ); + break; + } + + default: + { + // Don't update data sets in all other cases + break; + } + } +} + +// ------------------------------------------------------ +// Clock API +// ------------------------------------------------------ +void +PTP_Stack::HandleClockEvent( cClockEvent& ClockEvent ) +{ + switch( ClockEvent.GetID1() ) + { + default: + { + error( "Unexpected ClockEvent" ); + break; + } + } +} + +// ------------------------------------------------------ +// Protocol functions +// ------------------------------------------------------ +void +PTP_Stack::HandleEvent( portEvent_t Event, int PortID ) +{ + switch( Event ) + { + case PORT_EVENT_INITIALIZE: if( PortID == 0 ) + { + StartInitialize(); + } + else + { + error( "Initialize event can only happen on all ports at once." ); + } + break; + + case PORT_EVENT_INITIALIZE_DONE: if( PortID == 0 ) + { + StopInitialize(); + } + else + { + error( "InitializeDone event can only happen on all ports at once." ); + } + break; + + case PORT_EVENT_FAULT_DETECTED: // Fall-through + case PORT_EVENT_FAULT_CLEARED: if( PortID == 0 ) + { + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].HandleEvent( Event ); + } + } + else + { + pPorts[PortID-1].HandleEvent( Event ); + } + break; + + case PORT_EVENT_POWERUP: // Fall-through + case PORT_EVENT_ANNOUNCE_RCV_TIMEOUT: // Fall-through + case PORT_EVENT_QUALIFICATION_TIMEOUT: // Fall-through + case PORT_EVENT_MASTER_CLOCK_SELECTED: // Fall-through + case PORT_EVENT_SYNCHRONIZATION_FAULT: // Fall-through + + default: // Ignore event + break; + } +} + +void +PTP_Stack::HandlePtpFrame( PTPv2_Frame *pPtpFrame, int PortIdx ) +{ + if( defaultDS.GetDomainNumber() != pPtpFrame->getDomainNumber() ) + { + return; + } + + pPorts[ PortIdx ].HandlePtpFrame( pPtpFrame ); +} + +void +PTP_Stack::HandlePortRequ( PtpPortRequ *pRequ ) +{ + int PortIdx = pRequ->getArrivalGateId() - RequestGateID; + + CheckPortIdx( PortIdx ); + + switch( (tPtpPortRequ) pRequ->getType() ) + { + case PTP_PORT_REQU_TYPE_TRIG_SFU: + { + pPorts[ PortIdx ].HandleSyncFURequ( check_and_cast(pRequ) ); + break; + } + case PTP_PORT_REQU_TYPE_TRIG_PDFU: + { + pPorts[ PortIdx ].HandlePDelayFURequ( check_and_cast(pRequ) ); + break; + } + case PTP_PORT_REQU_TYPE_FAULT: + { + pPorts[ PortIdx ].HandleEvent( PORT_EVENT_FAULT_DETECTED ); + break; + } + + case PTP_PORT_REQU_TYPE_RECOVERY: + { + pPorts[ PortIdx ].HandleEvent( PORT_EVENT_FAULT_CLEARED ); + break; + } + } +} + +void +PTP_Stack::StartInitialize() +{ + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].HandleEvent( PORT_EVENT_INITIALIZE ); + } + + AppStateDecision.StopInterval(); + + // Schedule end-of-init + + PTP_EventMsg *EndInitMsg = new PTP_EventMsg( "PTP End Init Event" ); + + EndInitMsg->setPortID( 0 ); + EndInitMsg->setEvent( PORT_EVENT_INITIALIZE_DONE ); + + scheduleAt( simTime() + simtime_t( par( "StartupInterval" ).doubleValue() ), EndInitMsg ); +} + +void +PTP_Stack::StopInitialize() +{ + // Start state machine by ending init phase + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].HandleEvent( PORT_EVENT_INITIALIZE_DONE ); + } + + // Start STATE_DECISION_EVENT interval + if( Enabled ) + { + AppStateDecision.StartInterval(); + } +} + +void +PTP_Stack::BecomeSlave( cForeignClockDS Ebest ) +{ + // Current DS + currentDS.SetStepsRemoved( Ebest.GetStepsRemoved() + 1 ); + + // Parent DS + parentDS.ParentPortIdentity() = Ebest.SenderPortIdentity(); + parentDS.GrandmasterClockQuality() = Ebest.ClockQuality(); + + parentDS.SetGrandmasterIdentity ( Ebest.ClockIdentity() ); + parentDS.SetGrandmasterPriority1 ( Ebest.GetPriority1() ); + parentDS.SetGrandmasterPriority2 ( Ebest.GetPriority2() ); + parentDS.SetGrandMasterModuleID ( Ebest.GetModuleID() ); + + // Time properties DS + timePropertiesDS.SetCurrentUtcOffset ( Ebest.GetCurrentUtcOffset() ); + timePropertiesDS.SetCurrentUtcOffsetValid ( Ebest.FlagsField().currentUtcOffsetValid ); + timePropertiesDS.SetLeap59 ( Ebest.FlagsField().leap59 ); + timePropertiesDS.SetLeap61 ( Ebest.FlagsField().leap61 ); + timePropertiesDS.SetTimeTraceable ( Ebest.FlagsField().timeTraceable ); + timePropertiesDS.SetFrequencyTraceable ( Ebest.FlagsField().frequencyTraceable ); + timePropertiesDS.SetPtpTimescale ( Ebest.FlagsField().ptpTimescale ); + timePropertiesDS.SetTimeSource ( Ebest.GetTimeSource() ); + + // Activate clock servo + StartClockServo(); +} + +void +PTP_Stack::BecomeGrandMaster() +{ + // Current DS + currentDS.SetStepsRemoved( 0 ); + currentDS.SetOffsetFromMaster( 0 ); + currentDS.SetMeanPathDelay( 0 ); + + // Parent DS + parentDS.ParentPortIdentity().ClockIdentity() = defaultDS.ClockIdentity(); + parentDS.ParentPortIdentity().SetPortNumber( 0 ); + + parentDS.GrandmasterClockQuality() = defaultDS.ClockQuality(); + + parentDS.SetGrandmasterIdentity( defaultDS.ClockIdentity()); + parentDS.SetGrandmasterPriority1( defaultDS.GetPriority1() ); + parentDS.SetGrandmasterPriority2( defaultDS.GetPriority2() ); + parentDS.SetGrandMasterModuleID( defaultDS.GetModuleID() ); + + // Time properties DS + timePropertiesDS = timePropertiesDS_Backup; + + // Deactivate clock servo + StopClockServo(); +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ +void +PTP_Stack::SetMACAddress( MACAddress MAC ) +{ + EnterModuleSilent(); + + assert( simTime() == SIMTIME_ZERO ); + + this->MAC = MAC; + + // Forward new MAC + if( par( "UseMacForClockID" ).boolValue() ) + { + SetClockIdentity( cClockIdentity( MAC ) ); + } + par( "MAC_Address" ).setStringValue( MAC.str() ); + ConfigNIC_MAC(); + + LeaveModule(); +} + +void +PTP_Stack::SetClockIdentity( cClockIdentity ClockID ) +{ + EnterModuleSilent(); + + assert( simTime() == SIMTIME_ZERO ); + + defaultDS.ClockIdentity() = ClockID; + D0.ClockIdentity() = ClockID; + parentDS.ParentPortIdentity().ClockIdentity() = ClockID; + + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].PortDS().PortIdentity().ClockIdentity() = ClockID; + } + + par( "ClockID" ).setStringValue( ClockID.GetString() ); + + LeaveModule(); +} + +void +PTP_Stack::Disable() +{ + EnterModuleSilent(); + + AppStateDecision.StopInterval(); + + for( int PortIdx = 0; PortIdx < PtpGateSize; PortIdx ++ ) + { + pPorts[PortIdx].ChangeState( PORT_STATE_DISABLED ); + } + + LeaveModule(); +} + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ +int +PTP_Stack::GetModuleID() +{ + return ModuleID; +} diff --git a/src/Software/PTP_Stack/PTP_Stack.h b/src/Software/PTP_Stack/PTP_Stack.h new file mode 100644 index 0000000..32fcb42 --- /dev/null +++ b/src/Software/PTP_Stack/PTP_Stack.h @@ -0,0 +1,274 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_STACK_H_ +#define LIBPTP_PTP_STACK_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "MACAddress.h" + +#include "PTP.h" +#include "PTP_Port.h" +#include "AppStateDecision.h" +#include "AppAnnounce.h" + +#include "PTP_ProfileChecker.h" + +#include "IClockEventSink.h" +#include "ScheduleClock.h" +#include "IClockServo.h" +#include "ModuleInitBase.h" + +#include "PTPv2_m.h" +#include "PtpPortRequ_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_Stack: public cModuleInitBase, public IClockEventSink +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + cScheduleClock *pClock; + IClockServo *pClockServo; + cPTP_Port *pPorts; + PTP_ProfileChecker *pProfileChecker; + int PtpInGateID; + int PtpOutGateID; + int RequestGateID; + int ConfigGateID; + int PtpGateSize; + int ModuleID; + cClockEvent FinishBootupEvent; + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + bool Enabled; + PTP_Profile_t PTP_Profile; + PTP_ClockType_t PTP_ClockType; + bool Active_E2E_TC; + PTP_BMCA_t BMCA; + PTP_MgmtProtocol_t MgmtProtocol; + delayMechanism_t DelayMechanism; + std::string ClockPath; + std::string ClockServoPath; + simtime_t Asymmetry; + + // Initial values for default data set + bool TwoStepFlag; // + bool SlaveOnly; // + uint8_t Priority1; // + uint8_t Priority2; // + clockAccuracy_t ClockAccuracy; // + ClockClass_t ClockClass; // + UInteger16 offsetScaledLogVariance; // + domainNumber_t PrimaryDomain; // Primary domain number + MACAddress MAC; + cClockIdentity ClockIdentity; + + // Debug config + bool EnableDebugOutput; + + // Port config + int8_t LogMinDelayReqInterval; + int8_t LogAnnounceInterval; + int8_t LogSyncInterval; + int8_t LogMinPdelayReqInterval; + uint8_t AnnounceReceiptTimeout; + + // ------------------------------------------------------------ + // Synchronization + // ------------------------------------------------------------ + simtime_t MaxOffsetFromMaster; + + // ------------------------------------------------------------ + // Data sets + // ------------------------------------------------------------ + cDefaultDS defaultDS; // Default Data Set + cCurrentDS currentDS; // Current Data Set + cParentDS parentDS; // Parent Data Set + cTimePropertiesDS timePropertiesDS; // Time Properties Data Set + cTimePropertiesDS timePropertiesDS_Backup; // Backup copy of the Time Properties Data Set + cForeignClockDS D0; // Default Data Set in comparable form + + // ------------------------------------------------------------ + // Services + // ------------------------------------------------------------ + cAppStateDecision AppStateDecision; + simtime_t StateDecisionInterval; + + // ------------------------------------------------------------ + // Internal state handling + // ------------------------------------------------------------ + cForeignClockDS Ebest; + + // Parent DS + simsignal_t offsetFromMaster_raw_SigId; + simsignal_t meanPathDelay_raw_SigId; + + // ------------------------------------------------------------ + // Watch variables + // ------------------------------------------------------------ + simtime_t offsetFromMaster; + simtime_t meanPathDelay; + + // ------------------------------------------------------------ + // Debug functions + // ------------------------------------------------------------ + void PrintInfo(); + + // ------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------ + void CheckPortIdx( int PortIdx ); + + // ------------------------------------------------------------ + // Clock servo handling + // ------------------------------------------------------------ + void StartClockServo(); + void StopClockServo(); + + // ------------------------------------------------------------ + // Functions for initialization + // ------------------------------------------------------------ + void ParseStackParameters(); + void ParsePortParameters(); + void ParsePtpEvents(); + + // ------------------------------------------------------------ + // State handling + // ------------------------------------------------------------ + void StartInitialize(); + void StopInitialize(); + void HandleEvent ( portEvent_t Event, int PortID ); + void HandlePtpFrame ( PTPv2_Frame *pPtpFrame, int PortIdx); + void HandlePortRequ ( PtpPortRequ *pRequ ); + + void BecomeSlave( cForeignClockDS Ebest ); + void BecomeGrandMaster(); + + protected: + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseResourceParameters(); + void AllocateResources(); + void InitHierarchy(); + void ParseParameters(); + void RegisterSignals(); + void InitInternalState(); + void InitSignals(); + void FinishInit(); + void DebugOutput(); + void PrintDebugOutput(); + + void ForwardInit( int stage ); + + // ------------------------------------------------------------ + // OMNeT API + // ------------------------------------------------------------ + void handleMessage(cMessage *msg); + void finish(); + + // ------------------------------------------------------------ + // Port service API + // ------------------------------------------------------------ + void HandleTimejump( simtime_t Delta ); + + // ------------------------------------------------------------ + // Service API + // ------------------------------------------------------------ + void HandlePortStateDec( portStateDecision_t StateDecision ); + + public: + + // ------------------------------------------------------------ + // Constructors/Destructor + // ------------------------------------------------------------ + PTP_Stack(); + ~PTP_Stack(); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + int GetModuleID(); + + // ------------------------------------------------------------ + // API functions for services + // ------------------------------------------------------------ + void SetOffsetFromMaster( simtime_t offsetFromMaster ); + void SetMeanPathDelay( simtime_t meanPathDelay ); + void IssueFrame( PTPv2_Frame *pPTP, UInteger16 PortNumber ); + void ConfigNIC_MAC(); + void ConfigPortPathDelay( uint16_t PortID, simtime_t meanPathDelay ); + void ConfigPortAsymmetry( uint16_t PortID, simtime_t Asymmetry ); + void EmitSignal_offsetFromMaster_raw( simtime_t offsetFromMaster_raw ); + void EmitSignal_meanPathDelay_raw( simtime_t meanPathDelay_raw ); + + // ------------------------------------------------------------ + // Clock API + // ------------------------------------------------------------ + void HandleClockEvent( cClockEvent& ClockEvent ); + + // ------------------------------------------------------------ + // API for other modules + // ------------------------------------------------------------ + void SetMACAddress( MACAddress MAC ); + void SetClockIdentity( cClockIdentity ClockID ); + void Disable(); + + // ------------------------------------------------------------ + // Friends + // ------------------------------------------------------------ + friend class cAppService; + friend class cPTP_Port; + friend class cAppAnnounce; + friend class cAppSync; + friend class cAppDelay; + friend class cAppPDelay; + friend class cAppStateDecision; + friend class PTP_DefaultProfileChecker; + friend class PTP_DefaultE2EProfileChecker; + friend class PTP_DefaultP2PProfileChecker; + friend class PTP_PowerProfileChecker; +}; + +#endif diff --git a/src/Software/PTP_Stack/PTP_Stack.ned b/src/Software/PTP_Stack/PTP_Stack.ned new file mode 100644 index 0000000..48faea9 --- /dev/null +++ b/src/Software/PTP_Stack/PTP_Stack.ned @@ -0,0 +1,185 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Software.PTP_Stack; + +simple PTP_Stack +{ + parameters: + @display("i=PTP/Components/PTP_Stack/PTP_Stack"); + + // Simulation parameters + bool Enabled = default(true); + int NodeNumber = default(-1); + double StartupInterval @unit(s) = default( uniform(10ms, 100ms) ); + + // Parameters for internal state + string PTP_Profile = default("PTP_PROFILE_CUSTOM"); + string PTP_ClockType = default("PTP_CLOCK_TYPE_ORDINARY"); + string PTP_BestMasterClockAlgorithm = default("BMCA_1588_2008_DEFAULT"); + string PTP_ManagementProtocol = default("MGMT_1588_2008_DEFAULT"); + bool Active_E2E_TC = default(false); + + // Resource config + bool UseMacForClockID = default(true); + bool AutoMAC = default(true); + string MAC_Address = default("C0:FF:EE:BA:D0:1D"); + string ClockID = default("C0 FF EE 00 00 BA D0 1D"); + string ClockPath = default("^.Clock"); + string ClockServoPath = default("^.ClockServo"); + + // Parameters for default data set + bool PTP_TwoStepFlag = default(false); + bool SlaveOnly = default(false); + int Priority1 = default(128); + int Priority2 = default(128); + int OffsetScaledLogVariance = default(100); + string ClockClass = default("CLOCK_CLASS_SLAVE_ONLY"); + string ClockAccuracy = default("CLOCK_ACCURACY_UNKNOWN"); + string DomainNumber = default("DOMAIN_DEFAULT"); + + // Synchronization parameters + double MaxOffsetFromMaster @unit(s) = default( 100us ); + + // Filtering parameters + string offsetFromMasterFilter_Type = default("MOVING_AVG_FILTER"); + int offsetFromMasterFilter_Len = default( 5 ); + bool offsetFromMasterFilter_DiscardMinMax = default(true); + string meanPathDelayFilter_Type = default("MOVING_AVG_FILTER"); + int meanPathDelayFilter_Len = default( 5 ); + bool meanPathDelayFilter_DiscardMinMax = default(true); + string meanPeerDelayFilter_Type = default("MOVING_AVG_FILTER"); + int meanPeerDelayFilter_Len = default( 5 ); + bool meanPeerDelayFilter_DiscardMinMax = default(true); + + // Fault simulation + bool SimulateFault = default( false ); + double FaultTime @unit(s) = default( 0s ); + double FaultDuration @unit(s) = default( 1s ); + int FaultPortID = default( 0 ); + + // Profile specific parameters + bool PowerProfile_GrandmasterCapable = default( true ); + bool PowerProfile_PreferredGrandmaster = default( true ); + + // Debug + bool EnableDebugOutput = default( false ); + bool StateDecision_EnableBriefDebugOutput = default( false ); + bool StateDecision_EnableDetailedDebugOutput = default( false ); + bool Port_StateChange_EnableDebugOutput = default( false ); + bool Port_Sync_EnableDebugOutput = default( false ); + bool Port_Delay_EnableDebugOutput = default( false ); + bool Port_PDelay_EnableDebugOutput = default( false ); + + // Display options + string StateDec_TooltipPath = default("^"); + + // Port characteristics + // Default values taken from J.3.2) PTP attribute values + int LogAnnounceInterval = default(1); // Exponent in range -128 ... 127, [s] + int AnnounceReceiptTimeout = default(3); // Number of missed Announce-intervals before ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES is triggered + int LogSyncInterval = default(0); // Exponent in range -128 ... 127, [s] + int LogMinDelayReqInterval = default(0); // Exponent in range -128 ... 127, [s] + int LogMinPdelayReqInterval = default(0); + string PTP_DelayMechanism = default("DELAY_MECH_E2E"); + + double Asymmetry@unit(s) = default(-62ns); // See 1588 chapter 11.6 + // Rx delay 208ns + // Tx delay 84ns + // --> asymmetry -62ns + + // ----------------------------------------------------------------------- + // Signals + // ----------------------------------------------------------------------- + + // BMC + @signal[EbestModuleID](type=long); + + // CurrentDS + @signal[offsetFromMaster](type=simtime_t); + @signal[offsetFromMaster_raw](type=simtime_t); + @signal[stepsRemoved](type=unsigned long); + @signal[meanPathDelay](type=simtime_t); + @signal[meanPathDelay_raw](type=simtime_t); + + // ParentDS + + // Ports + @signal[Port_*_peerMeanPathDelay](type=simtime_t); + @signal[Port_*_peerMeanPathDelay_raw](type=simtime_t); + @signal[Port_*_portState](type=long); + + @signal[Port_*_AnnounceRcvd](type=long); + @signal[Port_*_SyncRcvd](type=long); + @signal[Port_*_SyncFuRcvd](type=long); + @signal[Port_*_DelReqRcvd](type=long); + @signal[Port_*_DelRespRcvd](type=long); + @signal[Port_*_PDelReqRcvd](type=long); + @signal[Port_*_PDelRespRcvd](type=long); + @signal[Port_*_PDelRespFuRcvd](type=long); + + @signal[Port_*_ErbestModuleID](type=long); + @signal[Port_*_StateDecision](type=long); + @signal[Port_*_ForgeinMasterDS_Cnt](type=long); + + // ----------------------------------------------------------------------- + // Statistics + // ----------------------------------------------------------------------- + + // BMC + @statistic[EbestModuleID](record=vector?); + + // CurrentDS + @statistic[offsetFromMaster](record=stats,vector?); + @statistic[offsetFromMaster_raw](record=stats,vector?); + @statistic[stepsRemoved](record=stats,vector?); + @statistic[meanPathDelay](record=stats,vector?); + @statistic[meanPathDelay_raw](record=stats,vector?); + + // ParentDS + + // Ports + @statisticTemplate[peerMeanPathDelay](record=vector?); + @statisticTemplate[peerMeanPathDelay_raw](record=vector?); + @statisticTemplate[portState](enum="INITIALIZING=1,FAULTY=2,DISABLED=3,LISTENING=4,PRE_MASTER=5,MASTER=6,PASSIVE=7,UNCALIBRATED=8,SLAVE=9";record=vector?); + + @statisticTemplate[AnnounceRcvd](record=vector?); + @statisticTemplate[SyncRcvd](record=vector?); + @statisticTemplate[SyncFuRcvd](record=vector?); + @statisticTemplate[DelReqRcvd](record=vector?); + @statisticTemplate[DelRespRcvd](record=vector?); + @statisticTemplate[PDelReqRcvd](record=vector?); + @statisticTemplate[PDelRespRcvd](record=vector?); + @statisticTemplate[PDelRespFuRcvd](record=vector?); + + @statisticTemplate[ErbestModuleID](record=vector?); + @statisticTemplate[StateDecision](enum="PORT_SD_LIST=10,PORT_SD_M1=43,PORT_SD_M2=42,PORT_SD_M3=41,PORT_SD_P1=22,PORT_SD_P2=21,PORT_SD_S1=31";record=vector?); + @statisticTemplate[ForeignMasterDS_Cnt](record=vector?); + + gates: + + input PtpIn[]; // PTP traffic + output PtpOut[]; + + output PortConfig[]; + input PortRequest[]; +} diff --git a/src/Software/PTP_Stack/ParameterParser/PTP_Parser.cc b/src/Software/PTP_Stack/ParameterParser/PTP_Parser.cc new file mode 100644 index 0000000..ca06a59 --- /dev/null +++ b/src/Software/PTP_Stack/ParameterParser/PTP_Parser.cc @@ -0,0 +1,342 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_Parser.h" +#include "ParameterParser.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +ParseType PTP_ClockTypeParse[] = +{ + { PTP_CLOCK_TYPE_ORDINARY, "PTP_CLOCK_TYPE_ORDINARY" }, + { PTP_CLOCK_TYPE_TRANSPARENT, "PTP_CLOCK_TYPE_TRANSPARENT" }, +}; + +ParseType PTP_ProfileParse[] = +{ + { PTP_PROFILE_CUSTOM, "PTP_PROFILE_CUSTOM" }, + { PTP_PROFILE_DEFAULT_E2E, "PTP_PROFILE_DEFAULT_E2E" }, + { PTP_PROFILE_DEFAULT_P2P, "PTP_PROFILE_DEFAULT_P2P" }, + { PTP_PROFILE_POWER, "PTP_PROFILE_POWER" }, +}; + +ParseType BestMasterClockAlgorithmParse[] = +{ + { BMCA_1588_2008_DEFAULT, "BMCA_1588_2008_DEFAULT" }, +}; + +ParseType MgmtProtocolParse[] = +{ + { MGMT_1588_2008_DEFAULT, "MGMT_1588_2008_DEFAULT" }, + { MGMT_SNMP, "MGMT_SNMP" }, +}; + +ParseType ClockClassParse[] = +{ + { CLOCK_CLASS_PRIMARY, "CLOCK_CLASS_PRIMARY" }, + { CLOCK_CLASS_PRIMARY_HOLDOVER, "CLOCK_CLASS_PRIMARY_HOLDOVER" }, + { CLOCK_CLASS_APP_SPECIFIC, "CLOCK_CLASS_APP_SPECIFIC" }, + { CLOCK_CLASS_APP_SPECIFIC_HOLDOVER, "CLOCK_CLASS_APP_SPECIFIC_HOLDOVER" }, + { CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_A, "CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_A" }, + { CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_A, "CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_A" }, + { CLOCK_CLASS_ALTERNATE_PTP_1_1, "CLOCK_CLASS_ALTERNATE_PTP_1_1" }, + { CLOCK_CLASS_ALTERNATE_PTP_1_2, "CLOCK_CLASS_ALTERNATE_PTP_1_2" }, + { CLOCK_CLASS_ALTERNATE_PTP_1_3, "CLOCK_CLASS_ALTERNATE_PTP_1_3" }, + { CLOCK_CLASS_ALTERNATE_PTP_1_4, "CLOCK_CLASS_ALTERNATE_PTP_1_4" }, + { CLOCK_CLASS_ALTERNATE_PTP_2_1, "CLOCK_CLASS_ALTERNATE_PTP_2_1" }, + { CLOCK_CLASS_ALTERNATE_PTP_2_2, "CLOCK_CLASS_ALTERNATE_PTP_2_2" }, + { CLOCK_CLASS_ALTERNATE_PTP_2_3, "CLOCK_CLASS_ALTERNATE_PTP_2_3" }, + { CLOCK_CLASS_ALTERNATE_PTP_2_4, "CLOCK_CLASS_ALTERNATE_PTP_2_4" }, + { CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_B, "CLOCK_CLASS_PRIMARY_HOLDOVER_DEGRADE_B" }, + { CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_B, "CLOCK_CLASS_APP_SPECIFIC_HOLDOVER_DEGRADE_B" }, + { CLOCK_CLASS_ALTERNATE_PTP_3_1, "CLOCK_CLASS_ALTERNATE_PTP_3_1" }, + { CLOCK_CLASS_ALTERNATE_PTP_3_2, "CLOCK_CLASS_ALTERNATE_PTP_3_2" }, + { CLOCK_CLASS_ALTERNATE_PTP_3_3, "CLOCK_CLASS_ALTERNATE_PTP_3_3" }, + { CLOCK_CLASS_ALTERNATE_PTP_3_4, "CLOCK_CLASS_ALTERNATE_PTP_3_4" }, + { CLOCK_CLASS_DEFAULT, "CLOCK_CLASS_DEFAULT" }, + { CLOCK_CLASS_PTP_V1, "CLOCK_CLASS_PTP_V1" }, + { CLOCK_CLASS_SLAVE_ONLY, "CLOCK_CLASS_SLAVE_ONLY" }, +}; + +ParseType ClockAccuracyParse[] = +{ + { CLOCK_ACCURACY_25_NS, "CLOCK_ACCURACY_25_NS" }, + { CLOCK_ACCURACY_100_NS, "CLOCK_ACCURACY_100_NS" }, + { CLOCK_ACCURACY_250_NS, "CLOCK_ACCURACY_250_NS" }, + { CLOCK_ACCURACY_1_US, "CLOCK_ACCURACY_1_US" }, + { CLOCK_ACCURACY_2_5_US, "CLOCK_ACCURACY_2_5_US" }, + { CLOCK_ACCURACY_10_US, "CLOCK_ACCURACY_10_US" }, + { CLOCK_ACCURACY_25_US, "CLOCK_ACCURACY_25_US" }, + { CLOCK_ACCURACY_100_US, "CLOCK_ACCURACY_100_US" }, + { CLOCK_ACCURACY_250_US, "CLOCK_ACCURACY_250_US" }, + { CLOCK_ACCURACY_1_MS, "CLOCK_ACCURACY_1_MS" }, + { CLOCK_ACCURACY_2_5_MS, "CLOCK_ACCURACY_2_5_MS" }, + { CLOCK_ACCURACY_10_MS, "CLOCK_ACCURACY_10_MS" }, + { CLOCK_ACCURACY_25_MS, "CLOCK_ACCURACY_25_MS" }, + { CLOCK_ACCURACY_100_MS, "CLOCK_ACCURACY_100_MS" }, + { CLOCK_ACCURACY_250_MS, "CLOCK_ACCURACY_250_MS" }, + { CLOCK_ACCURACY_1_S, "CLOCK_ACCURACY_1_S" }, + { CLOCK_ACCURACY_10_S, "CLOCK_ACCURACY_10_S" }, + { CLOCK_ACCURACY_OVER_10_S, "CLOCK_ACCURACY_OVER_10_S" }, + { CLOCK_ACCURACY_PROFILE_SPECIFIC_1, "CLOCK_ACCURACY_PROFILE_SPECIFIC_1" }, + { CLOCK_ACCURACY_PROFILE_SPECIFIC_2, "CLOCK_ACCURACY_PROFILE_SPECIFIC_2" }, + { CLOCK_ACCURACY_PROFILE_SPECIFIC_3, "CLOCK_ACCURACY_PROFILE_SPECIFIC_3" }, + { CLOCK_ACCURACY_PROFILE_SPECIFIC_4, "CLOCK_ACCURACY_PROFILE_SPECIFIC_4" }, + { CLOCK_ACCURACY_UNKNOWN, "CLOCK_ACCURACY_UNKNOWN" }, +}; + +ParseType TimeSourceParse[] = +{ + { TIME_SRC_ATOMIC_CLOCK, "TIME_SRC_ATOMIC_CLOCK" }, + { TIME_SRC_GPS, "TIME_SRC_GPS" }, + { TIME_SRC_TERRESTRIAL_RADIO, "TIME_SRC_TERRESTRIAL_RADIO" }, + { TIME_SRC_PTP, "TIME_SRC_PTP" }, + { TIME_SRC_NTP, "TIME_SRC_NTP" }, + { TIME_SRC_HAND_SET, "TIME_SRC_HAND_SET" }, + { TIME_SRC_OTHER, "TIME_SRC_OTHER" }, + { TIME_SRC_INTERNAL_OSCILLATOR, "TIME_SRC_INTERNAL_OSCILLATOR" }, + { TIME_SRC_PROFILE_SPECIFIC_1, "TIME_SRC_PROFILE_SPECIFIC_1" }, + { TIME_SRC_PROFILE_SPECIFIC_2, "TIME_SRC_PROFILE_SPECIFIC_2" }, + { TIME_SRC_PROFILE_SPECIFIC_3, "TIME_SRC_PROFILE_SPECIFIC_3" }, + { TIME_SRC_PROFILE_SPECIFIC_4, "TIME_SRC_PROFILE_SPECIFIC_4" }, +}; + +ParseType DelayMechParse[] = +{ + { DELAY_MECH_E2E, "DELAY_MECH_E2E" }, + { DELAY_MECH_P2P, "DELAY_MECH_P2P" }, + { DELAY_MECH_DISABLED, "DELAY_MECH_DISABLED" }, +}; + +ParseType DomainNumberParse[] = +{ + { DOMAIN_DEFAULT, "DOMAIN_DEFAULT" }, + { DOMAIN_ALTERNATE_1, "DOMAIN_ALTERNATE_1" }, + { DOMAIN_ALTERNATE_2, "DOMAIN_ALTERNATE_2" }, + { DOMAIN_ALTERNATE_3, "DOMAIN_ALTERNATE_3" }, + { DOMAIN_USER_1, "DOMAIN_USER_1" }, + { DOMAIN_USER_2, "DOMAIN_USER_2" }, + { DOMAIN_USER_3, "DOMAIN_USER_3" }, + { DOMAIN_USER_4, "DOMAIN_USER_4" }, + { DOMAIN_RESERVED, "DOMAIN_RESERVED" }, +}; + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Clock type +// ------------------------------------------------------ +PTP_ClockType_t +cPTP_Parser::ParsePtpClockType(const char *Str) +{ + return Parse( PTP_ClockTypeParse, ArrayLen(PTP_ClockTypeParse), Str ); +} + +// ------------------------------------------------------ +// PTP Profile +// ------------------------------------------------------ +PTP_Profile_t +cPTP_Parser::ParsePtpProfile(const char *Str) +{ + return Parse( PTP_ProfileParse, ArrayLen(PTP_ProfileParse), Str ); +} + +// ------------------------------------------------------ +// Best Master Clock Algorithm +// ------------------------------------------------------ +PTP_BMCA_t +cPTP_Parser::ParseBestMasterClockAlgorithm(const char *Str) +{ + return Parse( BestMasterClockAlgorithmParse, ArrayLen(BestMasterClockAlgorithmParse), Str ); +} + +// ------------------------------------------------------ +// Management protocol +// ------------------------------------------------------ +PTP_MgmtProtocol_t +cPTP_Parser::ParseManagementProtocol(const char *Str) +{ + return Parse( MgmtProtocolParse, ArrayLen(MgmtProtocolParse), Str ); +} + +// ------------------------------------------------------ +// Clock class +// ------------------------------------------------------ +ClockClass_t +cPTP_Parser::ParseClockClass(const char *Str) +{ + return Parse( ClockClassParse, ArrayLen(ClockClassParse), Str ); +} + +// ------------------------------------------------------ +// Clock accuracy +// ------------------------------------------------------ +clockAccuracy_t +cPTP_Parser::ParseClockAccuracy (const char *Str) +{ + return Parse( ClockAccuracyParse, ArrayLen(ClockAccuracyParse), Str ); +} + +// ------------------------------------------------------ +// Time source +// ------------------------------------------------------ +timeSource_t +cPTP_Parser::ParseTimeSource(const char *Str) +{ + return Parse( TimeSourceParse, ArrayLen(TimeSourceParse), Str ); +} + +// ------------------------------------------------------ +// Delay mechanism +// ------------------------------------------------------ +delayMechanism_t +cPTP_Parser::ParseDelayMechanism(const char *Str) +{ + return Parse( DelayMechParse, ArrayLen(DelayMechParse), Str ); +} + +// ------------------------------------------------------ +// Domain number +// ------------------------------------------------------ +domainNumber_t +cPTP_Parser::ParseDomainNumber(const char *Str) +{ + return Parse( DomainNumberParse, ArrayLen(DomainNumberParse), Str ); +} + +// ------------------------------------------------------ +// +// ------------------------------------------------------ +int8_t +cPTP_Parser::ParseParameterInt8( cComponent *pComp, const char *ParName) +{ + int x; + + x = pComp->par( ParName ).longValue(); + + if( x >= -128 && x <= 127 ) + return (int8_t) x; + else + throw cRuntimeError("Parsing exception: Value for '%' is out of range.", ParName ); +} + +// ------------------------------------------------------ +// +// ------------------------------------------------------ +uint8_t +cPTP_Parser::ParseParameterUInt8( cComponent *pComp, const char *ParName) +{ + int x; + + x = pComp->par( ParName ).longValue(); + + if( x >= 0 && x <= 255 ) + return (int8_t) x; + else + throw cRuntimeError("Parsing exception: Value for '%' is out of range.", ParName ); +} + +// ------------------------------------------------------ +// +// ------------------------------------------------------ +int +cPTP_Parser::ParsePortParameter(int PortNumber, cComponent *pComp, const char *ParName) +{ + std::stringstream StringStream; + std::string FullName; + + int x; + + StringStream << "PORT_" << PortNumber << "_" << ParName; + FullName = StringStream.str(); + + x = pComp->par( FullName.data() ).longValue(); + + return x; +} + +// ------------------------------------------------------ +// +// ------------------------------------------------------ +int8_t +cPTP_Parser::ParsePortParameterInt8(int PortNumber, cComponent *pComp, const char *ParName) +{ + int x; + + x = ParsePortParameter( PortNumber, pComp, ParName ); + + if( x >= -128 && x <= 127 ) + return (int8_t) x; + else + throw cRuntimeError("Parsing exception: Value for '%' of Port %d is out of range.", ParName, PortNumber ); +} + +// ------------------------------------------------------ +// +// ------------------------------------------------------ +uint8_t +cPTP_Parser::ParsePortParameterUInt8(int PortNumber, cComponent *pComp, const char *ParName) +{ + int x; + + x = ParsePortParameter( PortNumber, pComp, ParName ); + + if( x >= 0 && x <= 255 ) + return (uint8_t) x; + else + throw cRuntimeError("Parsing exception: Value for '%' of Port %d is out of range.", ParName, PortNumber ); +} + +// ------------------------------------------------------ +// +// ------------------------------------------------------ +delayMechanism_t +cPTP_Parser::ParsePortParameterDelayMech(int PortNumber, cComponent *pComp ) +{ + std::stringstream StringStream; + std::string FullName; + + StringStream << "PORT_" << PortNumber << "_DelayMechanism"; + FullName = StringStream.str(); + + return ParseDelayMechanism( pComp->par( FullName.data() ).stringValue() ); +} diff --git a/src/Software/PTP_Stack/ParameterParser/PTP_Parser.h b/src/Software/PTP_Stack/ParameterParser/PTP_Parser.h new file mode 100644 index 0000000..391610a --- /dev/null +++ b/src/Software/PTP_Stack/ParameterParser/PTP_Parser.h @@ -0,0 +1,66 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PARSER_H_ +#define LIBPTP_PTP_PARSER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP.h" + +// ====================================================== +// Types +// ====================================================== + +class cPTP_Parser +{ + public: + + static PTP_ClockType_t ParsePtpClockType (const char *Str); + static PTP_Profile_t ParsePtpProfile (const char *Str); + static PTP_BMCA_t ParseBestMasterClockAlgorithm (const char *Str); + static PTP_MgmtProtocol_t ParseManagementProtocol (const char *Str); + + static ClockClass_t ParseClockClass (const char *Str); + static clockAccuracy_t ParseClockAccuracy (const char *Str); + static timeSource_t ParseTimeSource (const char *Str); + static delayMechanism_t ParseDelayMechanism (const char *Str); + static domainNumber_t ParseDomainNumber (const char *Str); + + static int8_t ParseParameterInt8 ( cComponent *pComp, const char *ParName ); + static uint8_t ParseParameterUInt8( cComponent *pComp, const char *ParName ); + + // Port specific parameters + static int ParsePortParameter (int PortNumber, cComponent *pComp, const char *ParName); + static int8_t ParsePortParameterInt8 (int PortNumber, cComponent *pComp, const char *ParName); + static uint8_t ParsePortParameterUInt8 (int PortNumber, cComponent *pComp, const char *ParName); + static delayMechanism_t ParsePortParameterDelayMech (int PortNumber, cComponent *pComp ); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif + diff --git a/src/Software/PTP_Stack/Port/PTP_Port.cc b/src/Software/PTP_Stack/Port/PTP_Port.cc new file mode 100644 index 0000000..e6b85f2 --- /dev/null +++ b/src/Software/PTP_Stack/Port/PTP_Port.cc @@ -0,0 +1,1016 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP.h" +#include "PTP_Stack.h" +#include "PTP_Port.h" + +#include "DynamicSignals.h" + +#include "PTPv2_m.h" +#include "PtpPortConfig_m.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructor +// ------------------------------------------------------ +cPTP_Port::cPTP_Port() +: cSubmoduleInitBase(), + appAnnounce (), + appSync (), + appDelay (), + appPDelay () +{ + this->pApp = nullptr; +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ +cPortDS& +cPTP_Port::PortDS() +{ + return this->portDS; +} + +cForeignMasterDS& +cPTP_Port::ForeignMasterDS() +{ + return this->foreignMasterDS; +} + +cAppAnnounce& +cPTP_Port::AppAnnounce() +{ + return this->appAnnounce; +} + +cAppSync& +cPTP_Port::AppSync() +{ + return this->appSync; +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +cPTP_Port::ParseParameters() +{ + StateChange_EnableDebugOutput = pParentModule->par( "Port_StateChange_EnableDebugOutput" ).boolValue(); +} + +void +cPTP_Port::InitHierarchy() +{ + // Data sets + portDS.SetParentModule( pParentModule ); + portDS.SetHierarchy( this ); + foreignMasterDS.SetParentModule( pParentModule ); + foreignMasterDS.SetHierarchy( this, pClock ); + + // Services + appAnnounce.SetParentModule ( pParentModule ); + appSync.SetParentModule ( pParentModule ); + appDelay.SetParentModule ( pParentModule ); + appPDelay.SetParentModule ( pParentModule ); + + appAnnounce.SetCallableParentModule ( pApp ); + appSync.SetCallableParentModule ( pApp ); + appDelay.SetCallableParentModule ( pApp ); + appPDelay.SetCallableParentModule ( pApp ); + + appAnnounce.SetHierarchy ( pApp, this, pClock ); + appSync.SetHierarchy ( pApp, this, pClock, pClockServo ); + appDelay.SetHierarchy ( pApp, this, pClock ); + appPDelay.SetHierarchy ( pApp, this, pClock ); +} + +void +cPTP_Port::RegisterSignals() +{ + StateDecision_SigId = RegisterDynamicSignal( "StateDecision" ); +} + +void +cPTP_Port::InitInternalState() +{ + // Set up timeouts + appSync.SetInterval ( pow( 2.0, portDS.GetLogSyncInterval() ) ); + appDelay.SetInterval ( pow( 2.0, portDS.GetLogMinDelayReqInterval() ) ); + appPDelay.SetInterval ( pow( 2.0, portDS.GetLogMinPdelayReqInterval() ) ); + appAnnounce.SetInterval ( pow( 2.0, portDS.GetLogAnnounceInterval() ) ); + appAnnounce.SetTimeout( portDS.GetAnnounceReceiptTimeout() * pow( 2.0, portDS.GetLogAnnounceInterval() ) ); +} + +void +cPTP_Port::InitSignals() +{ +} + +void +cPTP_Port::ForwardInit( int stage ) +{ + // Data sets + portDS.initialize ( stage ); + foreignMasterDS.initialize ( stage ); + + // Services + appAnnounce.initialize ( stage ); + appSync.initialize ( stage ); + appDelay.initialize ( stage ); + appPDelay.initialize ( stage ); +} + +void +cPTP_Port::SetHierarchy( PTP_Stack *pApp, cScheduleClock *pClock, IClockServo *pClockServo, UInteger16 portNumber ) +{ + this->pApp = pApp; + this->pClock = pClock; + this->pClockServo = pClockServo; + this->portNumber = portNumber; +} + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +cPTP_Port::ChangeState( portState_t NewState ) +{ + if( portDS.GetPortState() == NewState ) + { + return; + } + + if( StateChange_EnableDebugOutput ) + { + EV << "------------------------------------------------------------------------------------------------------------" << endl; + EV << "Port " << portDS.PortIdentity().GetPortNumber() << ": "; + EV << "Changing port state from " << portDS.GetPortState() << " to " << NewState << endl; + EV << "------------------------------------------------------------------------------------------------------------" << endl; + } + + // Change service activity according to new port state + switch( NewState ) + { + case PORT_STATE_INITIALIZING: + case PORT_STATE_FAULTY: + case PORT_STATE_DISABLED: + { + appSync.StopInterval(); + appSync.StopTimeout(); + appSync.StopListening(); + appAnnounce.StopListening(); + appAnnounce.StopInterval(); + appDelay.StopInterval(); + appPDelay.StopInterval(); + break; + } + + case PORT_STATE_LISTENING: + { + appSync.StopInterval(); + appSync.StopTimeout(); + appSync.StopListening(); + appAnnounce.StopInterval(); + appDelay.StopInterval(); + + appAnnounce.StartListening(); + + switch( portDS.GetDelayMechanism() ) + { + case DELAY_MECH_P2P: appPDelay.StartInterval(); + break; + + default: + case DELAY_MECH_E2E: + case DELAY_MECH_DISABLED: appPDelay.StopInterval(); + break; + } + break; + } + + case PORT_STATE_PRE_MASTER: + { + appSync.StopInterval(); + appSync.StopListening(); + appAnnounce.StopListening(); + appAnnounce.StopInterval(); + appDelay.StopInterval(); + + appSync.StartTimeout(); + + switch( portDS.GetDelayMechanism() ) + { + case DELAY_MECH_P2P: appPDelay.StartInterval(); + break; + + default: + case DELAY_MECH_E2E: + case DELAY_MECH_DISABLED: appPDelay.StopInterval(); + break; + } + break; + } + + case PORT_STATE_MASTER: + { + appSync.StopTimeout(); + appSync.StopListening(); + appAnnounce.StopListening(); + appDelay.StopInterval(); + + appSync.StartInterval(); + appAnnounce.StartInterval(); + + switch( portDS.GetDelayMechanism() ) + { + case DELAY_MECH_P2P: appPDelay.StartInterval(); + break; + + default: + case DELAY_MECH_E2E: + case DELAY_MECH_DISABLED: appPDelay.StopInterval(); + break; + } + break; + } + + case PORT_STATE_PASSIVE: + { + appSync.StopInterval(); + appSync.StopTimeout(); + appSync.StopListening(); + appAnnounce.StopInterval(); + appDelay.StopInterval(); + + appAnnounce.StartListening(); + + switch( portDS.GetDelayMechanism() ) + { + case DELAY_MECH_P2P: appPDelay.StartInterval(); + break; + + default: + case DELAY_MECH_E2E: + case DELAY_MECH_DISABLED: appPDelay.StopInterval(); + break; + } + break; + } + + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + { + appSync.StopInterval(); + appSync.StopTimeout(); + appAnnounce.StopInterval(); + + appSync.StartListening(); + appAnnounce.StartListening(); + + switch( portDS.GetDelayMechanism() ) + { + case DELAY_MECH_P2P: appDelay.StopInterval(); + appPDelay.StartInterval(); + break; + + case DELAY_MECH_E2E: if + ( + ( pApp->PTP_ClockType == PTP_CLOCK_TYPE_ORDINARY ) || + ( + ( pApp->PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( pApp->Active_E2E_TC ) + ) + ) + { + appDelay.StartInterval(); + } + else + { + appDelay.StopInterval(); + } + appPDelay.StopInterval(); + break; + + default: + case DELAY_MECH_DISABLED: appPDelay.StopInterval(); + appDelay.StopInterval(); + break; + } + break; + } + } + + // Set new state + portDS.SetPortState( NewState ); +} + +void +cPTP_Port::FinishInitPhase() +{ + if( portDS.GetPortState() == PORT_STATE_INITIALIZING ) + { + // Start state machine + if( pApp->Enabled ) + { + ChangeState( PORT_STATE_LISTENING ); + } + else + { + ChangeState( PORT_STATE_DISABLED ); + } + } + else + { + throw cRuntimeError("Port: Was told to finish init state when not in init state."); + } +} + +void +cPTP_Port::HandleEvent( portEvent_t Event ) +{ + switch( Event ) + { + case PORT_EVENT_POWERUP: + { + ChangeState( PORT_STATE_INITIALIZING ); + break; + } + + case PORT_EVENT_INITIALIZE: + { + ChangeState( PORT_STATE_INITIALIZING ); + break; + } + + case PORT_EVENT_INITIALIZE_DONE: + { + if( portDS.GetPortState() != PORT_STATE_INITIALIZING ) + { + throw cRuntimeError( "PTP Port: Received INITIALIZE_DONE event in a non-Initializing state" ); + } + + if( pApp->Enabled ) + { + ChangeState( PORT_STATE_LISTENING ); + } + else + { + ChangeState( PORT_STATE_DISABLED ); + } + break; + } + + case PORT_EVENT_FAULT_DETECTED: + { + switch( portDS.GetPortState() ) + { + case PORT_STATE_LISTENING: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + case PORT_STATE_PRE_MASTER: + case PORT_STATE_MASTER: + case PORT_STATE_PASSIVE: + case PORT_STATE_FAULTY: + { + ChangeState( PORT_STATE_FAULTY ); + break; + } + + default: + { + // Ignore this event + break; + } + } + break; + } + + case PORT_EVENT_FAULT_CLEARED: + { + if( portDS.GetPortState() == PORT_STATE_FAULTY ) + { + pApp->StartInitialize(); + } + break; + } + + case PORT_EVENT_STATE_DECISION: + { + // This event is handled in the application for all ports + break; + } + + case PORT_EVENT_ANNOUNCE_RCV_TIMEOUT: + { + switch( portDS.GetPortState() ) + { + case PORT_STATE_LISTENING: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + { + if( pApp->defaultDS.GetSlaveOnly() ) + { + ChangeState( PORT_STATE_LISTENING ); + } + else if + ( + ( pApp->PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( pApp->parentDS.GetGrandmasterIdentity() != pApp->defaultDS.ClockIdentity() ) + ) + { + ChangeState( PORT_STATE_LISTENING ); + } + else + { + ChangeState( PORT_STATE_MASTER ); + } + break; + } + + case PORT_STATE_PASSIVE: + { + ChangeState( PORT_STATE_MASTER ); + break; + } + + default: + { + // Ignore this event + break; + } + } + break; + } + + case PORT_EVENT_QUALIFICATION_TIMEOUT: + { + if( portDS.GetPortState() == PORT_STATE_PRE_MASTER ) + { + ChangeState( PORT_STATE_MASTER ); + } + } + + case PORT_EVENT_MASTER_CLOCK_SELECTED: + { + if( portDS.GetPortState() == PORT_STATE_UNCALIBRATED ) + { + ChangeState( PORT_STATE_SLAVE ); + } + break; + } + + case PORT_EVENT_SYNCHRONIZATION_FAULT: + { + if( portDS.GetPortState() == PORT_STATE_SLAVE ) + { + ChangeState( PORT_STATE_UNCALIBRATED ); + } + break; + } + + case PORT_EVENT_DESIGNATED_ENABLE: + { + if( portDS.GetPortState() == PORT_STATE_DISABLED ) + { + ChangeState( PORT_STATE_INITIALIZING ); + } + } + + case PORT_EVENT_DESIGNATED_DISABLE: + { + switch( portDS.GetPortState() ) + { + case PORT_STATE_LISTENING: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + case PORT_STATE_PRE_MASTER: + case PORT_STATE_MASTER: + case PORT_STATE_PASSIVE: + case PORT_STATE_FAULTY: + { + ChangeState( PORT_STATE_DISABLED ); + break; + } + + default: + { + // Ignore this event + break; + } + } + break; + } + } +} + +void +cPTP_Port::HandleStateDecision( portStateDecision_t StateDecision, bool NewMaster ) +{ + portState_t CurState = portDS.GetPortState(); + portState_t NextState; + + switch( CurState ) + { + case PORT_STATE_INITIALIZING: + case PORT_STATE_FAULTY: + case PORT_STATE_DISABLED: + { + // These states do not react on State decisions + return; + } + + default: + break; + } + + // The qualificationTimeoutInterval shall be N multiplied by the announceInterval (see 7.7.2.2), in seconds, where: + // a) If the recommended state = MASTER event was based on decision points M1 or M2 of Figure 26, N shall be 0 + // b) If the recommended state = MASTER event was based on decision point M3 of Figure 26, N shall be the value incremented by 1 (one) of the currentDS.stepsRemoved field. + if + ( + ( StateDecision == PORT_SD_M1 ) || + ( StateDecision == PORT_SD_M2 ) + ) + { + appSync.SetTimeout( SIMTIME_ZERO ); + } + else if( StateDecision == PORT_SD_M3 ) + { + size_t N = pApp->currentDS.GetStepsRemoved() + 1; + simtime_t announceInterval = pow( 2.0, portDS.GetLogAnnounceInterval() ); + + appSync.SetTimeout( announceInterval * N ); + } + + switch( StateDecision ) + { + case PORT_SD_LIST: + { + if( CurState == PORT_STATE_LISTENING ) + { + return; + } + else + { + throw cRuntimeError("REMAIN_LISTENING called in state other than Listening"); + } + break; + } + + case PORT_SD_M1: + case PORT_SD_M2: + case PORT_SD_M3: + { + switch( CurState ) + { + default: + return; + + case PORT_STATE_LISTENING: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + case PORT_STATE_PASSIVE: + case PORT_STATE_PRE_MASTER: + + if + ( + ( pApp->defaultDS.GetSlaveOnly() ) || + ( + ( pApp->PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( StateDecision == PORT_SD_M3 ) + ) + ) + { + NextState = PORT_STATE_LISTENING; + } + else + { + NextState = PORT_STATE_PRE_MASTER; + } + break; + + case PORT_STATE_MASTER: + + if + ( + ( pApp->defaultDS.GetSlaveOnly() ) || + ( + ( pApp->PTP_ClockType == PTP_CLOCK_TYPE_TRANSPARENT ) && + ( StateDecision == PORT_SD_M3 ) + ) + ) + { + NextState = PORT_STATE_PASSIVE; + } + else + { + NextState = PORT_STATE_MASTER; + } + break; + } + break; + } + + // Recommended state == passive + case PORT_SD_P1: + case PORT_SD_P2: + { + switch( CurState ) + { + default: + return; + + case PORT_STATE_LISTENING: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_SLAVE: + case PORT_STATE_PRE_MASTER: + case PORT_STATE_MASTER: + case PORT_STATE_PASSIVE: + + NextState = PORT_STATE_PASSIVE; + break; + } + break; + } + + // Recommended state == slave + case PORT_SD_S1: + { + switch( CurState ) + { + default: + return; + + case PORT_STATE_LISTENING: + case PORT_STATE_UNCALIBRATED: + case PORT_STATE_PRE_MASTER: + case PORT_STATE_MASTER: + case PORT_STATE_PASSIVE: + + NextState = PORT_STATE_UNCALIBRATED; + break; + + case PORT_STATE_SLAVE: + + if( NewMaster ) + { + NextState = PORT_STATE_UNCALIBRATED; + } + else + { + return; + } + break; + } + break; + } + } + + ChangeState( NextState ); +} + +void +cPTP_Port::HandlePtpFrame( PTPv2_Frame *pPtpFrame ) +{ + switch( pPtpFrame->getMessageType() ) + { + case PTP_TYPE_SYNC: + case PTP_TYPE_FOLLOW_UP: + { + appSync.HandleMsg( pPtpFrame ); + appDelay.HandleMsg( pPtpFrame ); + break; + } + + case PTP_TYPE_DELAY_REQ: + case PTP_TYPE_DELAY_RESP: + { + appDelay.HandleMsg( pPtpFrame ); + break; + } + + case PTP_TYPE_PDELAY_REQ: + case PTP_TYPE_PDELAY_RESP: + case PTP_TYPE_PDELAY_RESP_FU: + { + appPDelay.HandleMsg( pPtpFrame ); + break; + } + + case PTP_TYPE_ANNOUNCE: + { + this->appAnnounce.HandleMsg(pPtpFrame); + break; + } + + case PTP_TYPE_SIGNALING: + case PTP_TYPE_MANAGEMENT: + { + // Not implemented + break; + } + + default: + { + break; + } + } +} + +void +cPTP_Port::HandleSyncFURequ( PtpPortRequ_TrigSyncFU *pRequ ) +{ + appSync.TriggerFollowUp( &pRequ->getSyncFrame() ); +} + +void +cPTP_Port::HandlePDelayFURequ( PtpPortRequ_TrigPDelayFU *pRequ ) +{ + appPDelay.TriggerFollowUp( &pRequ->getPDelayFrame() ); +} + +cForeignClockDS +cPTP_Port::GetErbest() +{ + return foreignMasterDS.GetErbest(); +} + +// ------------------------------------------------------ +// State Decision Algorithm +// Specified in Chapter 9.3.3 and Figure 26 +// ------------------------------------------------------ + +portStateDecision_t +cPTP_Port::GetStateDecision( cForeignClockDS Ebest, UInteger16 EbestPortID, bool EnableDebugOutput ) +{ + ClockClass_t D0_ClockClass; + ClockCompReturn_t CompReturn; + portStateDecision_t StateDecision = PORT_SD_LIST; + + if( EnableDebugOutput ) + { + EV << "Calculating state decision for Port " << portDS.PortIdentity().GetPortNumber() << endl; + } + + if + ( + ( foreignMasterDS.GetErbest().IsEmpty() ) && + ( portDS.GetPortState() == PORT_STATE_LISTENING ) + ) + { + if( EnableDebugOutput ) + { + EV << "--> state decision: stay in listening" << endl; + } + + StateDecision = PORT_SD_LIST; + } + else + { + if( EnableDebugOutput ) + { + EV << "... don't stay in listening" << endl; + } + + D0_ClockClass = pApp->D0.ClockQuality().GetClockClass(); + + if + ( + ( 1 <= D0_ClockClass ) && + ( 127 >= D0_ClockClass ) + ) + { + if( EnableDebugOutput ) + { + EV << "... good clock class" << endl; + EV << "... comparing D0, Erbest" << endl; + } + + CompReturn = cForeignClockDS::CompareClockDS( pApp->D0, GetErbest(), EnableDebugOutput ); + + if + ( + ( CompReturn.Result == ClockCompResult::A_BETTER_B ) || + ( CompReturn.Result == ClockCompResult::A_BETTER_B_BY_TOPO ) + ) + { + if( EnableDebugOutput ) + { + EV << "... A better B" << endl; + EV << "...... Details:" << endl; + EV << "...... Result: " << CompReturn.Result << endl; + EV << "...... Reason: " << CompReturn.Reason << endl; + EV << "--> state decision: M1" << endl; + } + + StateDecision = PORT_SD_M1; + } + else + { + if( EnableDebugOutput ) + { + EV << "... A not better B" << endl; + EV << "...... Details:" << endl; + EV << "...... Result: " << CompReturn.Result << endl; + EV << "...... Reason: " << CompReturn.Reason << endl; + EV << "--> state decision: P1" << endl; + } + + StateDecision = PORT_SD_P1; + } + } + else + { + if( EnableDebugOutput ) + { + EV << "... bad clock class" << endl; + EV << "... comparing D0, Ebest" << endl; + } + + CompReturn = cForeignClockDS::CompareClockDS( pApp->D0, Ebest, EnableDebugOutput ); + + if + ( + ( CompReturn.Result == ClockCompResult::A_BETTER_B ) || + ( CompReturn.Result == ClockCompResult::A_BETTER_B_BY_TOPO ) + ) + { + if( EnableDebugOutput ) + { + EV << "... A better B" << endl; + EV << "...... Details:" << endl; + EV << "...... Result: " << CompReturn.Result << endl; + EV << "...... Reason: " << CompReturn.Reason << endl; + EV << "--> state decision: M2" << endl; + } + + StateDecision = PORT_SD_M2; + } + else + { + if( EnableDebugOutput ) + { + EV << "... A is not better" << endl; + EV << "...... Details:" << endl; + EV << "...... Result: " << CompReturn.Result << endl; + EV << "...... Reason: " << CompReturn.Reason << endl; + EV << "... checking if this the Ebest-port" << endl; + } + + if( portDS.PortIdentity().GetPortNumber() == EbestPortID ) + { + if( EnableDebugOutput ) + { + EV << "...... this is the Ebest port" << endl; + EV << "--> state decision: S1" << endl; + } + + StateDecision = PORT_SD_S1; + } + else + { + if( EnableDebugOutput ) + { + EV << "...... this is not the Ebest port" << endl; + EV << "... comparing Ebest, Erbest" << endl; + } + + CompReturn = cForeignClockDS::CompareClockDS( Ebest, GetErbest(), EnableDebugOutput ); + + assert( CompReturn.Result != ClockCompResult::B_BETTER_A ); + assert( CompReturn.Result != ClockCompResult::B_BETTER_A_BY_TOPO ); + + if( CompReturn.Result == ClockCompResult::A_BETTER_B_BY_TOPO ) + { + if( EnableDebugOutput ) + { + EV << "... A better B by topo" << endl; + EV << "...... Details:" << endl; + EV << "...... Result: " << CompReturn.Result << endl; + EV << "...... Reason: " << CompReturn.Reason << endl; + EV << "--> state decision: P2" << endl; + } + + StateDecision = PORT_SD_P2; + } + else + { + if( EnableDebugOutput ) + { + EV << "... A is not better B by topo" << endl; + EV << "...... Details:" << endl; + EV << "...... Result: " << CompReturn.Result << endl; + EV << "...... Reason: " << CompReturn.Reason << endl; + EV << "--> state decision: M3" << endl; + } + + StateDecision = PORT_SD_M3; + } + } + } + } + } + + pParentModule->emit( StateDecision_SigId, StateDecision ); + + return StateDecision; +} + +// ------------------------------------------------------ +// API for services +// ------------------------------------------------------ +void +cPTP_Port::IssueFrame( PTPv2_Frame *pPTP ) +{ + pApp->IssueFrame( pPTP, portDS.PortIdentity().GetPortNumber() ); +} + +simsignal_t +cPTP_Port::RegisterDynamicSignal( const char *pSigName ) +{ + return DynamicSignals::RegisterDynamicSignal( pParentModule, "Port", portNumber, pSigName, pSigName ); +} + +void +cPTP_Port::ConfigPortPathDelay( simtime_t meanPathDelay ) +{ + pApp->ConfigPortPathDelay( portDS.PortIdentity().GetPortNumber(), meanPathDelay ); +} + +void +cPTP_Port::ConfigPortAsymmetry( simtime_t Asymmetry ) +{ + pApp->ConfigPortAsymmetry( portDS.PortIdentity().GetPortNumber(), Asymmetry ); +} + +// ------------------------------------------------------ +// API for stack +// ------------------------------------------------------ +void +cPTP_Port::HandleTimejump( simtime_t Delta ) +{ + foreignMasterDS.AdjustTimestamps( Delta ); + + appSync.HandleTimeJump(); + appDelay.HandleTimeJump(); + appPDelay.HandleTimeJump(); +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Instance functions +// ------------------------------------------------------ + diff --git a/src/Software/PTP_Stack/Port/PTP_Port.h b/src/Software/PTP_Stack/Port/PTP_Port.h new file mode 100644 index 0000000..baeb77a --- /dev/null +++ b/src/Software/PTP_Stack/Port/PTP_Port.h @@ -0,0 +1,179 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PORT_H_ +#define LIBPTP_PTP_PORT_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTP.h" +#include "AppAnnounce.h" +#include "AppSync.h" +#include "AppDelay.h" +#include "AppPDelay.h" +#include "SubmoduleInitBase.h" + +#include "ScheduleClock.h" + +#include "PTPv2_m.h" +#include "PtpPortRequ_m.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_Stack; + +// ====================================================== +// Declarations +// ====================================================== + +class cPTP_Port : public cSubmoduleInitBase +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + PTP_Stack *pApp; + cScheduleClock *pClock; + IClockServo *pClockServo; + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + UInteger16 portNumber; + + // ------------------------------------------------------------ + // Debug config + // ------------------------------------------------------------ + bool StateChange_EnableDebugOutput; + + // ------------------------------------------------------------ + // Statistics + // ------------------------------------------------------------ + simsignal_t StateDecision_SigId; + + // ------------------------------------------------------------ + // Data sets + // ------------------------------------------------------------ + cPortDS portDS; + cForeignMasterDS foreignMasterDS; + + // ------------------------------------------------------------ + // Services + // ------------------------------------------------------------ + cAppAnnounce appAnnounce; + cAppSync appSync; + cAppDelay appDelay; + cAppPDelay appPDelay; + + // ------------------------------------------------------------ + // Internal functions + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseParameters(); + void InitHierarchy(); + void RegisterSignals(); + void InitInternalState(); + void InitSignals(); + void ForwardInit( int stage ); + + protected: + + public: + + // ------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------ + cPTP_Port(); + + // ------------------------------------------------------------ + // Destructor + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Initialization + // ------------------------------------------------------------ + void SetHierarchy( PTP_Stack *pApp, cScheduleClock *pClock, IClockServo *pClockServo, UInteger16 portNumber ); + + // ------------------------------------------------------------ + // Instance methods + // ------------------------------------------------------------ + cPortDS& PortDS(); + cForeignMasterDS& ForeignMasterDS(); + cAppAnnounce& AppAnnounce(); + cAppSync& AppSync(); + + // ------------------------------------------------------------ + // API for PTP stack + // ------------------------------------------------------------ + void ChangeState( portState_t NewState ); + void FinishInitPhase(); + void HandleEvent( portEvent_t Event ); + void HandleStateDecision( portStateDecision_t StateDecision, bool NewMaster ); + void HandlePtpFrame( PTPv2_Frame *pPtpFrame ); + void HandleSyncFURequ( PtpPortRequ_TrigSyncFU *pRequ ); + void HandlePDelayFURequ( PtpPortRequ_TrigPDelayFU *pRequ ); + + cForeignClockDS GetErbest(); + portStateDecision_t GetStateDecision( cForeignClockDS Ebest, UInteger16 EbestPortID, bool EnableDebugOutput ); + + // ------------------------------------------------------------ + // API for services and data sets + // ------------------------------------------------------------ + void IssueFrame( PTPv2_Frame *pPTP ); + void ConfigPortPathDelay( simtime_t meanPathDelay ); + void ConfigPortAsymmetry( simtime_t Asymmetry ); + simsignal_t RegisterDynamicSignal( const char *pSigName ); + + // ------------------------------------------------------------ + // PTP stack API + // ------------------------------------------------------------ + void HandleTimejump( simtime_t Delta ); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Instance functions + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Friends + // ------------------------------------------------------------ + friend class cPortService; +}; + +#endif diff --git a/src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.cc b/src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.cc new file mode 100644 index 0000000..d27f5a7 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.cc @@ -0,0 +1,99 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_CustomProfileChecker.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------------ +// Init API +// ------------------------------------------------------------ +void +PTP_CustomProfileChecker::ParseParameters() +{ + PTP_ProfileChecker::ParseParameters(); +} + +// ------------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------------ +PTP_CustomProfileChecker::PTP_CustomProfileChecker() + : PTP_ProfileChecker() +{ + PTP_Profile = PTP_PROFILE_CUSTOM; +} + +PTP_CustomProfileChecker::~PTP_CustomProfileChecker() +{ +} + +// ------------------------------------------------------------ +// Setters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// Getters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// PTP Profile handling +// ------------------------------------------------------------ +void +PTP_CustomProfileChecker::CheckParametersFixed( PTP_Stack *pApp ) +{ + // Nothing to do here +} + +void +PTP_CustomProfileChecker::CheckParametersInit( PTP_Stack *pApp ) +{ + // Nothing to do here +} + +void +PTP_CustomProfileChecker::CheckParametersRange( PTP_Stack *pApp ) +{ + // Nothing to do here +} diff --git a/src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.h b/src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.h new file mode 100644 index 0000000..18d817d --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/CustomProfile/PTP_CustomProfileChecker.h @@ -0,0 +1,91 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_CUSTOM_PROFILE_CHECKER_H_ +#define LIBPTP_PTP_CUSTOM_PROFILE_CHECKER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ProfileChecker.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_CustomProfileChecker: public PTP_ProfileChecker +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal state handling + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------ + + protected: + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseParameters(); + + public: + + // ------------------------------------------------------------ + // Constructors/Destructor + // ------------------------------------------------------------ + PTP_CustomProfileChecker(); + ~PTP_CustomProfileChecker(); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // PTP Profile handling + // ------------------------------------------------------------ + void CheckParametersFixed( PTP_Stack *pApp ); + void CheckParametersInit( PTP_Stack *pApp ); + void CheckParametersRange( PTP_Stack *pApp ); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif diff --git a/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.cc b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.cc new file mode 100644 index 0000000..6844f00 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.cc @@ -0,0 +1,128 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_DefaultE2EProfileChecker.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------------ +// Init API +// ------------------------------------------------------------ +void +PTP_DefaultE2EProfileChecker::ParseParameters() +{ + PTP_DefaultProfileChecker::ParseParameters(); +} + +// ------------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------------ +PTP_DefaultE2EProfileChecker::PTP_DefaultE2EProfileChecker() + : PTP_DefaultProfileChecker() +{ + PTP_Profile = PTP_PROFILE_DEFAULT_E2E; +} + +PTP_DefaultE2EProfileChecker::~PTP_DefaultE2EProfileChecker() +{ +} + +// ------------------------------------------------------------ +// Setters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// Getters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// PTP Profile handling +// ------------------------------------------------------------ +void +PTP_DefaultE2EProfileChecker::CheckParametersFixed( PTP_Stack *pApp ) +{ + PTP_DefaultProfileChecker::CheckParametersFixed( pApp ); +} + +void +PTP_DefaultE2EProfileChecker::CheckParametersInit( PTP_Stack *pApp ) +{ + PTP_DefaultProfileChecker::CheckParametersInit( pApp ); + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPortDS& portDS = pApp->pPorts[PortIdx].PortDS(); + + if( portDS.GetDelayMechanism() != DELAY_MECH_E2E ) + { + HandleWrongInitValue( "portDS.delayMechanism" ); + } + + if( portDS.GetLogMinDelayReqInterval() != 0 ) + { + HandleWrongInitValue( "portDS.logMinDelayReqInterval" ); + } + } +} + +void +PTP_DefaultE2EProfileChecker::CheckParametersRange( PTP_Stack *pApp ) +{ + PTP_DefaultProfileChecker::CheckParametersRange( pApp ); + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPortDS& portDS = pApp->pPorts[PortIdx].PortDS(); + + if + ( + ( portDS.GetLogMinDelayReqInterval() < 0 ) || + ( portDS.GetLogMinDelayReqInterval() > 5 ) + ) + { + HandleRangeError( "portDS.logMinDelayReqInterval" ); + } + } +} diff --git a/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.h b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.h new file mode 100644 index 0000000..480bf36 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultE2EProfileChecker.h @@ -0,0 +1,91 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_DEFAULT_E2E_PROFILE_CHECKER_H_ +#define LIBPTP_PTP_DEFAULT_E2E_PROFILE_CHECKER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_DefaultProfileChecker.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_DefaultE2EProfileChecker: public PTP_DefaultProfileChecker +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal state handling + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------ + + protected: + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseParameters(); + + public: + + // ------------------------------------------------------------ + // Constructors/Destructor + // ------------------------------------------------------------ + PTP_DefaultE2EProfileChecker(); + ~PTP_DefaultE2EProfileChecker(); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // PTP Profile handling + // ------------------------------------------------------------ + void CheckParametersFixed( PTP_Stack *pApp ); + void CheckParametersInit( PTP_Stack *pApp ); + void CheckParametersRange( PTP_Stack *pApp ); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif diff --git a/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.cc b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.cc new file mode 100644 index 0000000..c618886 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.cc @@ -0,0 +1,128 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_DefaultP2PProfileChecker.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------------ +// Init API +// ------------------------------------------------------------ +void +PTP_DefaultP2PProfileChecker::ParseParameters() +{ + PTP_DefaultProfileChecker::ParseParameters(); +} + +// ------------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------------ +PTP_DefaultP2PProfileChecker::PTP_DefaultP2PProfileChecker() + : PTP_DefaultProfileChecker() +{ + PTP_Profile = PTP_PROFILE_DEFAULT_P2P; +} + +PTP_DefaultP2PProfileChecker::~PTP_DefaultP2PProfileChecker() +{ +} + +// ------------------------------------------------------------ +// Setters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// Getters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// PTP Profile handling +// ------------------------------------------------------------ +void +PTP_DefaultP2PProfileChecker::CheckParametersFixed( PTP_Stack *pApp ) +{ + PTP_DefaultProfileChecker::CheckParametersFixed( pApp ); +} + +void +PTP_DefaultP2PProfileChecker::CheckParametersInit( PTP_Stack *pApp ) +{ + PTP_DefaultProfileChecker::CheckParametersInit( pApp ); + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPortDS& portDS = pApp->pPorts[PortIdx].PortDS(); + + if( portDS.GetDelayMechanism() != DELAY_MECH_E2E ) + { + HandleWrongInitValue( "portDS.delayMechanism" ); + } + + if( portDS.GetLogMinPdelayReqInterval() != 0 ) + { + HandleWrongInitValue( "portDS.logMinPdelayReqInterval" ); + } + } +} + +void +PTP_DefaultP2PProfileChecker::CheckParametersRange( PTP_Stack *pApp ) +{ + PTP_DefaultProfileChecker::CheckParametersRange( pApp ); + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPortDS& portDS = pApp->pPorts[PortIdx].PortDS(); + + if + ( + ( portDS.GetLogMinPdelayReqInterval() < 0 ) || + ( portDS.GetLogMinPdelayReqInterval() > 5 ) + ) + { + HandleRangeError( "portDS.logMinPdelayReqInterval" ); + } + } +} diff --git a/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.h b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.h new file mode 100644 index 0000000..ec3ab51 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultP2PProfileChecker.h @@ -0,0 +1,91 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_DEFAULT_P2P_PROFILE_CHECKER_H_ +#define LIBPTP_PTP_DEFAULT_P2P_PROFILE_CHECKER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_DefaultProfileChecker.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_DefaultP2PProfileChecker: public PTP_DefaultProfileChecker +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal state handling + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------ + + protected: + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseParameters(); + + public: + + // ------------------------------------------------------------ + // Constructors/Destructor + // ------------------------------------------------------------ + PTP_DefaultP2PProfileChecker(); + ~PTP_DefaultP2PProfileChecker(); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // PTP Profile handling + // ------------------------------------------------------------ + void CheckParametersFixed( PTP_Stack *pApp ); + void CheckParametersInit( PTP_Stack *pApp ); + void CheckParametersRange( PTP_Stack *pApp ); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif diff --git a/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.cc b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.cc new file mode 100644 index 0000000..552cb09 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.cc @@ -0,0 +1,176 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_DefaultProfileChecker.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------------ +// Init API +// ------------------------------------------------------------ +void +PTP_DefaultProfileChecker::ParseParameters() +{ + PTP_ProfileChecker::ParseParameters(); +} + +// ------------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------------ +PTP_DefaultProfileChecker::PTP_DefaultProfileChecker() + : PTP_ProfileChecker() +{ +} + +PTP_DefaultProfileChecker::~PTP_DefaultProfileChecker() +{ +} + +// ------------------------------------------------------------ +// Setters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// Getters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// PTP Profile handling +// ------------------------------------------------------------ +void +PTP_DefaultProfileChecker::CheckParametersFixed( PTP_Stack *pApp ) +{ + if( pApp->BMCA != BMCA_1588_2008_DEFAULT ) + { + HandleWrongInitValue( "Best Master Clock Algorithm" ); + } + + if( pApp->MgmtProtocol != MGMT_1588_2008_DEFAULT ) + { + HandleWrongInitValue( "Management protocol" ); + } +} + +void +PTP_DefaultProfileChecker::CheckParametersInit( PTP_Stack *pApp ) +{ + if( pApp->defaultDS.GetDomainNumber() != 0 ) + { + HandleWrongInitValue( "defaultDS.domainNumber" ); + } + + if( pApp->defaultDS.GetPriority1() != 128 ) + { + HandleWrongInitValue( "defaultDS.priority1" ); + } + + if( pApp->defaultDS.GetPriority2() != 128 ) + { + HandleWrongInitValue( "defaultDS.priority2" ); + } + + // Remark: The following parameters are not checked here on purpose, + // as these checks would not fit well with the current implementation + // + // SlaveOnly flag + // transparentClockDS.primaryDomain + // Tau + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPortDS& portDS = pApp->pPorts[PortIdx].PortDS(); + + if( portDS.GetLogAnnounceInterval() != 1 ) + { + HandleWrongInitValue( "portDS.logAnnounceInterval" ); + } + + if( portDS.GetLogSyncInterval() != 0 ) + { + HandleWrongInitValue( "portDS.logSyncInterval" ); + } + + if( portDS.GetAnnounceReceiptTimeout() != 3 ) + { + HandleWrongInitValue( "portDS.announceReceiptTimeout" ); + } + } +} + +void +PTP_DefaultProfileChecker::CheckParametersRange( PTP_Stack *pApp ) +{ + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPortDS& portDS = pApp->pPorts[PortIdx].PortDS(); + + if + ( + ( portDS.GetLogAnnounceInterval() < 0 ) || + ( portDS.GetLogAnnounceInterval() > 4 ) + ) + { + HandleRangeError( "portDS.logAnnounceInterval" ); + } + + if + ( + ( portDS.GetLogSyncInterval() < -1 ) || + ( portDS.GetLogSyncInterval() > 1 ) + ) + { + HandleRangeError( "portDS.logSyncInterval" ); + } + + if + ( + ( portDS.GetAnnounceReceiptTimeout() < 2 ) || + ( portDS.GetAnnounceReceiptTimeout() > 10 ) + ) + { + HandleRangeError( "portDS.announceReceiptTimeout" ); + } + } +} diff --git a/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.h b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.h new file mode 100644 index 0000000..6c5a55b --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/DefaultProfile/PTP_DefaultProfileChecker.h @@ -0,0 +1,91 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_DEFAULT_PROFILE_CHECKER_H_ +#define LIBPTP_PTP_DEFAULT_PROFILE_CHECKER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ProfileChecker.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_DefaultProfileChecker: public PTP_ProfileChecker +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal state handling + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------ + + protected: + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseParameters(); + + public: + + // ------------------------------------------------------------ + // Constructors/Destructor + // ------------------------------------------------------------ + PTP_DefaultProfileChecker(); + ~PTP_DefaultProfileChecker(); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // PTP Profile handling + // ------------------------------------------------------------ + void CheckParametersFixed( PTP_Stack *pApp ); + void CheckParametersInit( PTP_Stack *pApp ); + void CheckParametersRange( PTP_Stack *pApp ); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif diff --git a/src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.cc b/src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.cc new file mode 100644 index 0000000..153f061 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.cc @@ -0,0 +1,94 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ProfileChecker.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------------ +// Error handling +// ------------------------------------------------------------ +void +PTP_ProfileChecker::HandleWrongInitValue( std::string ParameterName ) +{ + std::stringstream ss; + + ss << "Wrong initialization value for PTP profile" << endl; + ss << "PTP Profile: " << PTP_Profile << endl; + ss << "Parameter: " << ParameterName << endl; + + throw cRuntimeError( ss.str().c_str() ); +} + +void +PTP_ProfileChecker::HandleRangeError( std::string ParameterName ) +{ + std::stringstream ss; + + ss << "Parameter not in valid range for PTP profile" << endl; + ss << "PTP Profile: " << PTP_Profile << endl; + ss << "Parameter: " << ParameterName << endl; + + throw cRuntimeError( ss.str().c_str() ); +} + +// ------------------------------------------------------------ +// Init API +// ------------------------------------------------------------ +void +PTP_ProfileChecker::ParseParameters() +{ +} + +// ------------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------------ +PTP_ProfileChecker::PTP_ProfileChecker() +{ + PTP_Profile = PTP_PROFILE_CUSTOM; +} + +PTP_ProfileChecker::~PTP_ProfileChecker() +{ +} diff --git a/src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.h b/src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.h new file mode 100644 index 0000000..4857fd6 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/PTP_ProfileChecker.h @@ -0,0 +1,101 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_PROFILE_CHECKER_H_ +#define LIBPTP_PTP_PROFILE_CHECKER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP.h" +#include "SubmoduleInitBase.h" + +// ====================================================== +// Types +// ====================================================== + +class PTP_Stack; + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_ProfileChecker: public cSubmoduleInitBase +{ + private: + + protected: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal state handling + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + PTP_Profile_t PTP_Profile; + + // ------------------------------------------------------------ + // Error handling + // ------------------------------------------------------------ + void HandleWrongInitValue( std::string ParameterName ); + void HandleRangeError( std::string ParameterName ); + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseParameters(); + + public: + + // ------------------------------------------------------------ + // Constructors/Destructor + // ------------------------------------------------------------ + PTP_ProfileChecker(); + virtual ~PTP_ProfileChecker(); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // PTP Profile handling + // ------------------------------------------------------------ + virtual void CheckParametersFixed( PTP_Stack *pApp ) = 0; + virtual void CheckParametersInit( PTP_Stack *pApp ) = 0; + virtual void CheckParametersRange( PTP_Stack *pApp ) = 0; +}; + +#endif diff --git a/src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.cc b/src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.cc new file mode 100644 index 0000000..83f36b9 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.cc @@ -0,0 +1,160 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PowerProfileChecker.h" +#include "PTP_Stack.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------------ +// Init API +// ------------------------------------------------------------ +void +PTP_PowerProfileChecker::ParseParameters() +{ + PTP_ProfileChecker::ParseParameters(); + + GrandmasterCapable = pParentModule->par( "PowerProfile_GrandmasterCapable" ); + PreferredGrandmaster = pParentModule->par( "PowerProfile_PreferredGrandmaster" ); +} + +void +PTP_PowerProfileChecker::InitInternalState() +{ + AnnounceReceiptTimeout = PreferredGrandmaster ? 3 : 2; + + Priority1 = GrandmasterCapable ? 128 : 255; + Priority2 = GrandmasterCapable ? 128 : 255; + SlaveOnly = GrandmasterCapable ? true : false; +} + +// ------------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------------ +PTP_PowerProfileChecker::PTP_PowerProfileChecker() + : PTP_ProfileChecker() +{ + PTP_Profile = PTP_PROFILE_POWER; + GrandmasterCapable = false; + PreferredGrandmaster = false; +} + +PTP_PowerProfileChecker::~PTP_PowerProfileChecker() +{ +} + +// ------------------------------------------------------------ +// Setters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// Getters +// ------------------------------------------------------------ + +// ------------------------------------------------------------ +// PTP Profile handling +// ------------------------------------------------------------ +void +PTP_PowerProfileChecker::CheckParametersFixed( PTP_Stack *pApp ) +{ + if( pApp->defaultDS.GetDomainNumber() != 0 ) + { + HandleWrongInitValue( "defaultDS.domainNumber" ); + } + + for( int PortIdx = 0; PortIdx < pApp->PtpGateSize; PortIdx ++ ) + { + cPortDS& portDS = pApp->pPorts[PortIdx].PortDS(); + + if( portDS.GetDelayMechanism() != DELAY_MECH_P2P ) + { + HandleWrongInitValue( "portDS.delayMechanism" ); + } + + if( portDS.GetLogAnnounceInterval() != 0 ) + { + HandleWrongInitValue( "portDS.logAnnounceInterval" ); + } + + if( portDS.GetLogSyncInterval() != 0 ) + { + HandleWrongInitValue( "portDS.logSyncInterval" ); + } + + if( portDS.GetLogMinPdelayReqInterval() != 0 ) + { + HandleWrongInitValue( "portDS.logMinPdelayReqInterval" ); + } + + if( portDS.GetAnnounceReceiptTimeout() != AnnounceReceiptTimeout ) + { + HandleWrongInitValue( "portDS.announceReceiptTimeout" ); + } + } + + if( pApp->defaultDS.GetPriority1() != Priority1 ) + { + HandleWrongInitValue( "defaultDS.priority1" ); + } + + if( pApp->defaultDS.GetPriority2() != Priority2 ) + { + HandleWrongInitValue( "defaultDS.priority2" ); + } + + if( pApp->defaultDS.GetSlaveOnly() != SlaveOnly ) + { + HandleWrongInitValue( "defaultDS.priority2" ); + } +} + +void +PTP_PowerProfileChecker::CheckParametersInit( PTP_Stack *pApp ) +{ +} + +void +PTP_PowerProfileChecker::CheckParametersRange( PTP_Stack *pApp ) +{ +} diff --git a/src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.h b/src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.h new file mode 100644 index 0000000..bb8cbe0 --- /dev/null +++ b/src/Software/PTP_Stack/Profiles/PowerProfile/PTP_PowerProfileChecker.h @@ -0,0 +1,106 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_POWER_PROFILE_CHECKER_H_ +#define LIBPTP_PTP_POWER_PROFILE_CHECKER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_ProfileChecker.h" + +// ====================================================== +// Types +// ====================================================== + +/* +struct PTP_PowerProfileConfig_t +{ + uint16_t grandmasterID; + uint32_t grandmasterTimeInaccuracy; +}; +*/ + +class PTP_PowerProfileChecker: public PTP_ProfileChecker +{ + private: + + // ------------------------------------------------------------ + // Resources + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Configuration + // ------------------------------------------------------------ + bool GrandmasterCapable; + bool PreferredGrandmaster; + uint8_t AnnounceReceiptTimeout; + uint8_t Priority1; + uint8_t Priority2; + bool SlaveOnly; + + // ------------------------------------------------------------ + // Internal state handling + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------ + + protected: + + // ------------------------------------------------------------ + // Init API + // ------------------------------------------------------------ + void ParseParameters(); + void InitInternalState(); + + public: + + // ------------------------------------------------------------ + // Constructors/Destructor + // ------------------------------------------------------------ + PTP_PowerProfileChecker(); + ~PTP_PowerProfileChecker(); + + // ------------------------------------------------------------ + // Setters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // Getters + // ------------------------------------------------------------ + + // ------------------------------------------------------------ + // PTP Profile handling + // ------------------------------------------------------------ + void CheckParametersFixed( PTP_Stack *pApp ); + void CheckParametersInit( PTP_Stack *pApp ); + void CheckParametersRange( PTP_Stack *pApp ); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif diff --git a/src/Software/SimTimeFilter/ISimTimeFilter.cc b/src/Software/SimTimeFilter/ISimTimeFilter.cc new file mode 100644 index 0000000..739c062 --- /dev/null +++ b/src/Software/SimTimeFilter/ISimTimeFilter.cc @@ -0,0 +1,92 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "ISimTimeFilter.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ +ISimTimeFilter::ISimTimeFilter() +{ +} + +ISimTimeFilter::ISimTimeFilter( const ISimTimeFilter& other ) +{ +} + +ISimTimeFilter::~ISimTimeFilter() +{ +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +ISimTimeFilter& +ISimTimeFilter::operator= (const ISimTimeFilter& other) +{ + // By convention, always return *this + return *this; +} diff --git a/src/Software/SimTimeFilter/ISimTimeFilter.h b/src/Software/SimTimeFilter/ISimTimeFilter.h new file mode 100644 index 0000000..cbd5017 --- /dev/null +++ b/src/Software/SimTimeFilter/ISimTimeFilter.h @@ -0,0 +1,68 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_ISIMTIME_FILTER_H_ +#define LIBPTP_ISIMTIME_FILTER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class ISimTimeFilter +{ + private: + + protected: + + public: + + // Constructors/Destructor + ISimTimeFilter(); + ISimTimeFilter( const ISimTimeFilter& other ); + virtual ~ISimTimeFilter(); + + virtual ISimTimeFilter* Clone() const = 0; // Virtual constructor (copying) + + // Setters + + // Getters + + // API Functions + virtual void reset() = 0; + virtual void push( simtime_t v ) = 0; + virtual simtime_t pop() = 0; + + // Operators + ISimTimeFilter& operator= (const ISimTimeFilter& other); +}; + +#endif diff --git a/src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.cc b/src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.cc new file mode 100644 index 0000000..e00e792 --- /dev/null +++ b/src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.cc @@ -0,0 +1,121 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "IdentitySimTimeFilter.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ +IdentitySimTimeFilter::IdentitySimTimeFilter() + : ISimTimeFilter() +{ + reset(); +} + +IdentitySimTimeFilter::IdentitySimTimeFilter( const IdentitySimTimeFilter& other ) + : ISimTimeFilter( other ) +{ + buf = other.buf; +} + +IdentitySimTimeFilter::~IdentitySimTimeFilter() +{ +} + +IdentitySimTimeFilter* +IdentitySimTimeFilter::Clone() const +{ + return new IdentitySimTimeFilter(*this); +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +IdentitySimTimeFilter::reset() +{ + buf = SIMTIME_ZERO; +} + +void +IdentitySimTimeFilter::push( simtime_t v ) +{ + buf = v; +} + +simtime_t +IdentitySimTimeFilter::pop() +{ + return buf; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +IdentitySimTimeFilter& +IdentitySimTimeFilter::operator= (const IdentitySimTimeFilter& other) +{ + this->buf = other.buf; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.h b/src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.h new file mode 100644 index 0000000..fd34e43 --- /dev/null +++ b/src/Software/SimTimeFilter/Identity/IdentitySimTimeFilter.h @@ -0,0 +1,71 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_IDENTITY_SIMTIME_FILTER_H_ +#define LIBPTP_IDENTITY_SIMTIME_FILTER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class IdentitySimTimeFilter : public ISimTimeFilter +{ + private: + + // Resources + simtime_t buf; + + protected: + + public: + + // Constructors/Destructor + IdentitySimTimeFilter(); + IdentitySimTimeFilter( const IdentitySimTimeFilter& other ); + ~IdentitySimTimeFilter(); + + IdentitySimTimeFilter* Clone() const; + + // Setters + + // Getters + + // API Functions + virtual void reset(); + virtual void push( simtime_t v ); + virtual simtime_t pop(); + + // Operators + IdentitySimTimeFilter& operator= (const IdentitySimTimeFilter& other); +}; + +#endif diff --git a/src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.cc b/src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.cc new file mode 100644 index 0000000..c03dc57 --- /dev/null +++ b/src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.cc @@ -0,0 +1,168 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "MovingAvgSimTimeFilter.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +class cSimTimeCircBuf : public boost::circular_buffer +{ +}; + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Constructors/Destructor +// ------------------------------------------------------ +MovingAvgSimTimeFilter::MovingAvgSimTimeFilter( size_t size, bool DiscardMinMax ) + : ISimTimeFilter() +{ + pBuf = new cSimTimeCircBuf; + + pBuf->set_capacity( size ); + + this->DiscardMinMax = DiscardMinMax; +} + +MovingAvgSimTimeFilter::MovingAvgSimTimeFilter( const MovingAvgSimTimeFilter& other ) + : ISimTimeFilter( other ) +{ + pBuf = new cSimTimeCircBuf( *other.pBuf ); + + this->DiscardMinMax = other.DiscardMinMax; +} + +MovingAvgSimTimeFilter::~MovingAvgSimTimeFilter() +{ + delete pBuf; +} + +MovingAvgSimTimeFilter* +MovingAvgSimTimeFilter::Clone() const +{ + return new MovingAvgSimTimeFilter(*this); +} + +// ------------------------------------------------------ +// Instance methods +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ + +// ------------------------------------------------------ +// API functions +// ------------------------------------------------------ +void +MovingAvgSimTimeFilter::reset() +{ + pBuf->clear(); +} + +void +MovingAvgSimTimeFilter::push( simtime_t v ) +{ + pBuf->push_back( v ); +} + +simtime_t +MovingAvgSimTimeFilter::pop() +{ + if( pBuf->empty() ) + { + throw cRuntimeError( "Calling pop() of an empty buffer." ); + } + + simtime_t sum; + simtime_t avg; + + if( DiscardMinMax ) + { + cSimTimeCircBuf::iterator itMin = std::min_element( pBuf->begin(), pBuf->end() ); + cSimTimeCircBuf::iterator itMax = std::max_element( pBuf->begin(), pBuf->end() ); + + if( pBuf->size() > 3 ) + { + sum = std::accumulate( pBuf->begin(), pBuf->end(), SIMTIME_ZERO ); + sum -= *itMin; + sum -= *itMax; + + avg = sum / (pBuf->size()-2); + } + else + { + avg = SIMTIME_ZERO; + } + } + else + { + sum = std::accumulate( pBuf->begin(), pBuf->end(), SIMTIME_ZERO ); + avg = sum / pBuf->size(); + } + + return avg; +} + +// ------------------------------------------------------ +// Setters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Getters +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Operators +// ------------------------------------------------------ +MovingAvgSimTimeFilter& +MovingAvgSimTimeFilter::operator= (const MovingAvgSimTimeFilter& other) +{ + this->pBuf = new cSimTimeCircBuf( *other.pBuf ); + + this->DiscardMinMax = other.DiscardMinMax; + + // By convention, always return *this + return *this; +} diff --git a/src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.h b/src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.h new file mode 100644 index 0000000..dc05507 --- /dev/null +++ b/src/Software/SimTimeFilter/MovingAvg/MovingAvgSimTimeFilter.h @@ -0,0 +1,76 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_MOVING_AVG_SIMTIME_FILTER_H_ +#define LIBPTP_MOVING_AVG_SIMTIME_FILTER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +class cSimTimeCircBuf; + +// ====================================================== +// Declarations +// ====================================================== + +class MovingAvgSimTimeFilter : public ISimTimeFilter +{ + private: + + // Configuration + bool DiscardMinMax; + + // Resources + cSimTimeCircBuf *pBuf; + + protected: + + public: + + // Constructors/Destructor + MovingAvgSimTimeFilter( size_t size, bool DiscardMinMax ); + MovingAvgSimTimeFilter( const MovingAvgSimTimeFilter& other ); + ~MovingAvgSimTimeFilter(); + + MovingAvgSimTimeFilter* Clone() const; + + // Setters + + // Getters + + // API Functions + virtual void reset(); + virtual void push( simtime_t v ); + virtual simtime_t pop(); + + // Operators + MovingAvgSimTimeFilter& operator= (const MovingAvgSimTimeFilter& other); +}; + +#endif diff --git a/src/Software/SimTimeFilter/SimTimeFilterTypes.h b/src/Software/SimTimeFilter/SimTimeFilterTypes.h new file mode 100644 index 0000000..a6d1928 --- /dev/null +++ b/src/Software/SimTimeFilter/SimTimeFilterTypes.h @@ -0,0 +1,45 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_SIMTIME_FILTER_TYPES_H_ +#define LIBPTP_SIMTIME_FILTER_TYPES_H_ + +// ====================================================== +// Includes +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +typedef enum +{ + NO_FILTER, + MOVING_AVG_FILTER, +} +SimTimeFilter_t; + +// ====================================================== +// Declarations +// ====================================================== + +#endif diff --git a/src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.cc b/src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.cc new file mode 100644 index 0000000..10265db --- /dev/null +++ b/src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.cc @@ -0,0 +1,60 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "SimTimeFilter_ParameterParser.h" +#include "ParameterParser.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +ParseType SimTimeFilterTypeParse[] = +{ + { NO_FILTER, "NO_FILTER" }, + { MOVING_AVG_FILTER, "MOVING_AVG_FILTER" }, +}; + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// TdGen type +// ------------------------------------------------------ + +SimTimeFilter_t +cSimTimeFilter_ParameterParser::ParseSimTimeFilterType(const char *Str) +{ + return Parse( SimTimeFilterTypeParse, ArrayLen(SimTimeFilterTypeParse), Str ); +} diff --git a/src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.h b/src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.h new file mode 100644 index 0000000..6998e77 --- /dev/null +++ b/src/Software/SimTimeFilter/SimTimeFilter_ParameterParser.h @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_SIMTIME_FILTER_PARAMETER_PARSER_H_ +#define LIBPTP_SIMTIME_FILTER_PARAMETER_PARSER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "SimTimeFilterTypes.h" + +// ====================================================== +// Types +// ====================================================== + +class cSimTimeFilter_ParameterParser +{ + public: + + static SimTimeFilter_t ParseSimTimeFilterType(const char *Str); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif + diff --git a/src/Testbenches/BabblingIP/BabblingIP.cc b/src/Testbenches/BabblingIP/BabblingIP.cc new file mode 100644 index 0000000..4b12cf8 --- /dev/null +++ b/src/Testbenches/BabblingIP/BabblingIP.cc @@ -0,0 +1,104 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "BabblingIP.h" + +#include "distrib.h" + +#include "IPProtocolId_m.h" +#include "IPv4Datagram.h" +#include "IPv4Address.h" +#include "Ieee802Ctrl_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(BabblingIP); + +// ====================================================== +// Definitions +// ====================================================== + +void +BabblingIP::SendIpMsg() +{ + Ieee802Ctrl *pCtrl = new Ieee802Ctrl; + IPv4Datagram *pIP = new IPv4Datagram; + + pIP->setSrcAddress( IPv4Address(192,168,0,1) ); + pIP->setDestAddress( IPv4Address(192,168,0,5) ); + pIP->setTransportProtocol( IP_PROT_TCP ); + + pCtrl->setSrc( SrcMAC ); + pCtrl->setDest(MACAddress::BROADCAST_ADDRESS); + pCtrl->setEtherType(ETHERTYPE_IPv4); + + pIP->setControlInfo(pCtrl); + + send( pIP, "IpOut" ); +} + +void +BabblingIP::ScheduleNext() +{ + simtime_t NextInterval; + + NextInterval = simtime_t( intervalPar->doubleValue() ); + + scheduleAt( simTime() + NextInterval, pScheduleTrigger ); +} + +void +BabblingIP::initialize() +{ + SrcMAC = MACAddress::generateAutoAddress(); + + pScheduleTrigger = new cMessage("Schedule Trigger" ); + intervalPar = &par( "Interval" ); + + ScheduleNext(); +} + +void BabblingIP::handleMessage(cMessage *msg) +{ + SendIpMsg(); + ScheduleNext(); +} diff --git a/src/Testbenches/BabblingIP/BabblingIP.h b/src/Testbenches/BabblingIP/BabblingIP.h new file mode 100644 index 0000000..ac8ed85 --- /dev/null +++ b/src/Testbenches/BabblingIP/BabblingIP.h @@ -0,0 +1,61 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TESTBENCH_BABBLING_IP_H_ +#define LIBPTP_PTP_TESTBENCH_BABBLING_IP_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "MACAddress.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class BabblingIP : public cSimpleModule +{ + private: + + cMessage *pScheduleTrigger; + cPar *intervalPar; + + MACAddress SrcMAC; + + void SendIpMsg(); + void ScheduleNext(); + + protected: + virtual void initialize(); + virtual void handleMessage(cMessage *msg); + + public: +}; + +#endif diff --git a/src/Testbenches/BabblingIP/BabblingIP.ned b/src/Testbenches/BabblingIP/BabblingIP.ned new file mode 100644 index 0000000..634043b --- /dev/null +++ b/src/Testbenches/BabblingIP/BabblingIP.ned @@ -0,0 +1,34 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.BabblingIP; + +simple BabblingIP +{ + parameters: + + @display("i=block/user"); + volatile double Interval @unit(s) = default( uniform(1s,5s) ); + + gates: + output IpOut; +} diff --git a/src/Testbenches/ClockScaleTest/ClockScaleTest.cc b/src/Testbenches/ClockScaleTest/ClockScaleTest.cc new file mode 100644 index 0000000..4b58331 --- /dev/null +++ b/src/Testbenches/ClockScaleTest/ClockScaleTest.cc @@ -0,0 +1,83 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#include "ClockScaleTest.h" + +Define_Module(ClockScaleTest); + +void ClockScaleTest::initialize() +{ + bool Enabled = par("Enabled").boolValue(); + + if( Enabled ) + { + std::string ClockPath; + + ClockPath = par("ClockPath").stringValue(); + + pClock = check_and_cast(getModuleByPath( ClockPath.c_str() )); + + pTestMsg1 = new cMessage( "Clock Scale Test 1: SetScaleFactor_ppb( 500000 )" ); + pTestMsg2 = new cMessage( "Clock Scale Test 2: SetScaleFactor_ppb( -500000 )" ); + pTestMsg3 = new cMessage( "Clock Scale Test 3: IncScaledTime( 3.5 )" ); + pTestMsg4 = new cMessage( "Clock Scale Test 4: SetScaleFactor_ppb( 20000 )" ); + pTestMsg5 = new cMessage( "Clock Scale Test 5: IncScaledTime( -1.0 )" ); + pTestMsg6 = new cMessage( "Clock Scale Test 6: Long distance query" ); + + scheduleAt( 0.2, pTestMsg1 ); + scheduleAt( 0.6, pTestMsg2 ); + scheduleAt( 1.0, pTestMsg3 ); + scheduleAt( 1.3, pTestMsg4 ); + scheduleAt( 1.7, pTestMsg5 ); + scheduleAt( 1000000.0, pTestMsg6 ); + } +} + +void ClockScaleTest::handleMessage(cMessage *pMsg) +{ + if( pMsg == pTestMsg1 ) + { + pClock->SetScaleFactor_ppb( 500000 ); + } + else if( pMsg == pTestMsg2 ) + { + pClock->SetScaleFactor_ppb( -500000 ); + } + else if( pMsg == pTestMsg3 ) + { + pClock->IncScaledTime( 3.5 ); + } + else if( pMsg == pTestMsg4 ) + { + pClock->SetScaleFactor_ppb( 200000 ); + } + else if( pMsg == pTestMsg5 ) + { + pClock->IncScaledTime( -1.0 ); + } + else if( pMsg == pTestMsg6 ) + { + EV << "ScaledTime at " << simTime() << ": " << pClock->GetTimeStamp(); + } + + delete pMsg; +} diff --git a/src/Testbenches/ClockScaleTest/ClockScaleTest.h b/src/Testbenches/ClockScaleTest/ClockScaleTest.h new file mode 100644 index 0000000..6487a72 --- /dev/null +++ b/src/Testbenches/ClockScaleTest/ClockScaleTest.h @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_CLOCK_SCALE_TEST_H_ +#define LIBPTP_CLOCK_SCALE_TEST_H_ + +#include + +#include "AdjustableClock.h" + +class ClockScaleTest : public cSimpleModule +{ +private: + + cAdjustableClock *pClock; + + cMessage *pTestMsg1; + cMessage *pTestMsg2; + cMessage *pTestMsg3; + cMessage *pTestMsg4; + cMessage *pTestMsg5; + cMessage *pTestMsg6; + + protected: + virtual void initialize(); + virtual void handleMessage(cMessage *pMsg); +}; + +#endif diff --git a/src/Testbenches/ClockScaleTest/ClockScaleTest.ned b/src/Testbenches/ClockScaleTest/ClockScaleTest.ned new file mode 100644 index 0000000..fbb9176 --- /dev/null +++ b/src/Testbenches/ClockScaleTest/ClockScaleTest.ned @@ -0,0 +1,32 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.ClockScaleTest; + +simple ClockScaleTest +{ + @display("i=block/telnet"); + + string ClockPath = default("^.Clock"); + + bool Enabled = default(true); +} diff --git a/src/Testbenches/ClockScheduleTest/ClockScheduleTest.cc b/src/Testbenches/ClockScheduleTest/ClockScheduleTest.cc new file mode 100644 index 0000000..11c8b0c --- /dev/null +++ b/src/Testbenches/ClockScheduleTest/ClockScheduleTest.cc @@ -0,0 +1,90 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#include "ClockScheduleTest.h" + +Define_Module(ClockScheduleTest); + +void ClockScheduleTest::initialize() +{ + Enabled = par( "Enabled" ); + + if( Enabled ) + { + // Get resources + pClock = check_and_cast( getModuleByPath( par("ClockPath").stringValue() ) ); + + pTestMsg1 = new cMessage( "Schedule Test 1: ScheduleRelativeEvent(1.0)" ); + pTestMsg2 = new cMessage( "Schedule Test 2: ScheduleAbsoluteEvent(15.0)" ); + pTestMsg3 = new cMessage( "Schedule Test 3: ScheduleAbsoluteEvent(16.0)" ); + pTestMsg4 = new cMessage( "Schedule Test 4: CancelEvent( ScheduledEvent2 )" ); + pTestMsg5 = new cMessage( "Schedule Test 5: ScheduleRelativeEvent(1.0)" ); + + scheduleAt( 1.0, pTestMsg1 ); + scheduleAt( 3.0, pTestMsg2 ); + scheduleAt( 4.0, pTestMsg3 ); + scheduleAt( 5.0, pTestMsg4 ); + scheduleAt( 6.0, pTestMsg5 ); + } +} + +void ClockScheduleTest::handleMessage(cMessage *pMsg) +{ + if( pMsg == pTestMsg1 ) + { + ScheduledEvent1 = pClock->ScheduleRelativeEvent( 1.0, this, Event1 ); + } + else if( pMsg == pTestMsg2 ) + { + ScheduledEvent2 = pClock->ScheduleAbsoluteEvent( 15.0, this, Event2 ); + } + else if( pMsg == pTestMsg3 ) + { + ScheduledEvent3 = pClock->ScheduleAbsoluteEvent( 16.000000051, this, Event3 ); + } + else if( pMsg == pTestMsg4 ) + { + pClock->CancelEvent( ScheduledEvent2 ); + } + else if( pMsg == pTestMsg5 ) + { + ScheduledEvent4 = pClock->ScheduleRelativeEvent( 1.0, this, Event4 ); + } + + delete pMsg; +} + +ClockScheduleTest::ClockScheduleTest() +{ + Event1 = cClockEvent( 1, 0, 0, 0, nullptr ); + Event2 = cClockEvent( 2, 0, 0, 0, nullptr ); + Event3 = cClockEvent( 3, 0, 0, 0, nullptr ); + Event4 = cClockEvent( 2, 0, 0, 0, nullptr ); +} + +void +ClockScheduleTest::HandleClockEvent( cClockEvent& ClockEvent ) +{ + EV << "ClockTest (" << getName() << "): received values " << ClockEvent.GetID1() << " " << ClockEvent.GetID2() << " " << ClockEvent.GetID3() << " " << ClockEvent.GetID4() << endl; + EV << "ScaledTime: " << pClock->GetScaledTime() << endl; + EV << "RealTime: " << simTime() << endl; +} diff --git a/src/Testbenches/ClockScheduleTest/ClockScheduleTest.h b/src/Testbenches/ClockScheduleTest/ClockScheduleTest.h new file mode 100644 index 0000000..0dbbf89 --- /dev/null +++ b/src/Testbenches/ClockScheduleTest/ClockScheduleTest.h @@ -0,0 +1,68 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_CLOCK_SCHEDULE_TEST_H_ +#define LIBPTP_CLOCK_SCHEDULE_TEST_H_ + +#include + +#include "CallableModule.h" +#include "IClockEventSink.h" +#include "ScheduleClock.h" + +class ClockScheduleTest : public cCallableModule, public IClockEventSink +{ + private: + + // Resources + cScheduleClock *pClock; + + cMessage *pTestMsg1; + cMessage *pTestMsg2; + cMessage *pTestMsg3; + cMessage *pTestMsg4; + cMessage *pTestMsg5; + + cClockEvent Event1; + cClockEvent Event2; + cClockEvent Event3; + cClockEvent Event4; + + cScheduledClockEvent ScheduledEvent1; + cScheduledClockEvent ScheduledEvent2; + cScheduledClockEvent ScheduledEvent3; + cScheduledClockEvent ScheduledEvent4; + + // Configuration + bool Enabled; + + protected: + virtual void initialize(); + virtual void handleMessage(cMessage *pMsg); + + public: + ClockScheduleTest(); + + void HandleClockEvent( cClockEvent& ClockEvent ); +}; + +#endif diff --git a/src/Testbenches/ClockScheduleTest/ClockScheduleTest.ned b/src/Testbenches/ClockScheduleTest/ClockScheduleTest.ned new file mode 100644 index 0000000..f79e0a4 --- /dev/null +++ b/src/Testbenches/ClockScheduleTest/ClockScheduleTest.ned @@ -0,0 +1,32 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.ClockScheduleTest; + +simple ClockScheduleTest +{ + @display("i=block/layer"); + + string ClockPath = default("^.Clock"); + + bool Enabled = default( true ); +} diff --git a/src/Testbenches/ClockServoTest/ClockServoTest.cc b/src/Testbenches/ClockServoTest/ClockServoTest.cc new file mode 100644 index 0000000..963d2d6 --- /dev/null +++ b/src/Testbenches/ClockServoTest/ClockServoTest.cc @@ -0,0 +1,91 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#include "ClockServoTest.h" + +Define_Module(ClockServoTest); + +void +ClockServoTest::ParseParameters() +{ + pClockServo = check_and_cast ( getModuleByPath( par("ClockServoPath").stringValue() ) ); + pMasterClock = check_and_cast( getModuleByPath( par("MasterClockPath").stringValue() ) ); + pSlaveClock = check_and_cast( getModuleByPath( par("SlaveClockPath").stringValue() ) ); + + Interval = simtime_t( par("SampleInterval").doubleValue() ); +} + +ClockServoTest::ClockServoTest() +{ + pClockServo = nullptr; + pMasterClock = nullptr; + pSlaveClock = nullptr; +} + +void +ClockServoTest::initialize() +{ + ParseParameters(); + + pTimerMsg = new cMessage( "ClockServoTimer" ); + + TestState = INIT; + + scheduleAt( simTime() + Interval, pTimerMsg ); +} + +void +ClockServoTest::handleMessage(cMessage *msg) +{ + simtime_t MasterTime; + simtime_t SlaveTime; + simtime_t Offset; + + switch( TestState ) + { + case INIT: pClockServo->Disable(); + TestState = CONFIG; + break; + + case CONFIG: pClockServo->SetSyncInterval( Interval ); + TestState = ENABLE; + break; + + case ENABLE: pClockServo->Enable(); + TestState = RUN; + break; + + case RUN: MasterTime = pMasterClock->GetScaledTime(); + SlaveTime = pSlaveClock->GetScaledTime(); + Offset = SlaveTime - MasterTime; + + EV << "Offset: " << Offset << endl; + + pClockServo->Sample( Offset, pSlaveClock->GetScaledTime() ); + break; + + case INVALID: // Do nothing + break; + } + + scheduleAt( simTime() + Interval, pTimerMsg ); +} diff --git a/src/Testbenches/ClockServoTest/ClockServoTest.h b/src/Testbenches/ClockServoTest/ClockServoTest.h new file mode 100644 index 0000000..2dfd43e --- /dev/null +++ b/src/Testbenches/ClockServoTest/ClockServoTest.h @@ -0,0 +1,71 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_CLOCKSERVO_TEST_H_ +#define LIBPTP_CLOCKSERVO_TEST_H_ + +#include + +#include "IClockServo.h" + +#include "ScheduleClock.h" + +class ClockServoTest : public cSimpleModule +{ + private: + + // Types + typedef enum + { + INIT, + CONFIG, + ENABLE, + RUN, + INVALID, + } + TestState_t; + + // Resources + IClockServo *pClockServo; + cScheduleClock *pMasterClock; + cScheduleClock *pSlaveClock; + cMessage *pTimerMsg; + + // Configuration + simtime_t Interval; + + // House keeping + TestState_t TestState; + + void ParseParameters(); + + protected: + + virtual void initialize(); + virtual void handleMessage(cMessage *msg); + + public: + + ClockServoTest(); +}; + +#endif diff --git a/src/Testbenches/ClockServoTest/ClockServoTest.ned b/src/Testbenches/ClockServoTest/ClockServoTest.ned new file mode 100644 index 0000000..5781a6b --- /dev/null +++ b/src/Testbenches/ClockServoTest/ClockServoTest.ned @@ -0,0 +1,36 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.ClockServoTest; + +simple ClockServoTest +{ + parameters: + + @display("i=block/mac"); + + string ClockServoPath = default("^.ClockServo"); + string MasterClockPath = default("^.MasterClock"); + string SlaveClockPath = default("^.SlaveClock"); + + double SampleInterval @unit(s) = default(1s); +} diff --git a/src/Testbenches/ClockTest/ClockTest.cc b/src/Testbenches/ClockTest/ClockTest.cc new file mode 100644 index 0000000..e1446fa --- /dev/null +++ b/src/Testbenches/ClockTest/ClockTest.cc @@ -0,0 +1,169 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "ClockTest.h" + +// ====================================================== +// Types +// ====================================================== + +typedef enum +{ + TEST_SCHEDULE_EVENT, + TEST_SET_TIME, + TEST_INC_TIME, + TEST_SET_FACTOR_1, + TEST_SET_FACTOR_2, + TEST_DEC_TIME, +} +tTestID; + +struct tTestSchedule +{ + double TestTime; + tTestID TestID; +}; + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +static tTestSchedule TestSchedule[] = +{ +/* + { 0.1, TEST_SET_TIME }, + { 0.1, TEST_SCHEDULE_EVENT }, + { 0.2, TEST_SCHEDULE_EVENT }, + { 0.3, TEST_SCHEDULE_EVENT }, + { 0.4, TEST_SCHEDULE_EVENT }, + { 1.0, TEST_INC_TIME }, + { 1.2, TEST_DEC_TIME }, + { 1.3, TEST_SET_FACTOR_1 }, + { 1.4, TEST_SET_FACTOR_2 }, +*/ + { 1.5, TEST_SET_FACTOR_1 }, + { 2.5, TEST_SET_FACTOR_2 }, +}; + + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(ClockTest); + +// ====================================================== +// Definitions +// ====================================================== + +ClockTest::ClockTest() +{ +} + +void ClockTest::initialize() +{ + // Get resources + pClock = check_and_cast( getModuleByPath( par("ClockPath").stringValue() ) ); + + // Set up schedule + for( size_t i = 0; i < sizeof(TestSchedule)/sizeof(TestSchedule[0]); i++ ) + { + cMessage *pMsg = new cMessage; + simtime_t Time = simtime_t(TestSchedule[i].TestTime); + int ID = TestSchedule[i].TestID; + + EV << "Scheduling test " << ID << " at time " << Time << endl; + + pMsg->setKind( ID ); + + scheduleAt( Time, pMsg ); + } + + pClock->ScheduleRelativeEvent( simtime_t(0.6), this, cClockEvent( 1, 1, 1, 1, nullptr ) ); + pClock->ScheduleRelativeEvent( simtime_t(0.4), this, cClockEvent( 2, 1, 1, 1, nullptr ) ); + pClock->ScheduleRelativeEvent( simtime_t(0.2), this, cClockEvent( 3, 1, 1, 1, nullptr ) ); + pClock->ScheduleRelativeEvent( simtime_t(0.1), this, cClockEvent( 4, 1, 1, 1, nullptr ) ); + pClock->ScheduleRelativeEvent( simtime_t(0.3), this, cClockEvent( 5, 1, 1, 1, nullptr ) ); + pClock->ScheduleRelativeEvent( simtime_t(0.5), this, cClockEvent( 6, 1, 1, 1, nullptr ) ); +} + +void +ClockTest::handleMessage(cMessage *msg) +{ + if( msg->isSelfMessage() ) + { + EV << "Starting test " << msg->getKind() << endl; + + switch( msg->getKind() ) + { + case TEST_SCHEDULE_EVENT: + { + pClock->ScheduleRelativeEvent( simtime_t(1.0), this, cClockEvent( 5, 4, 3, 2, nullptr ) ); + break; + } + + case TEST_SET_TIME: + { + pClock->SetScaledTime( SIMTIME_ZERO ); + break; + } + + case TEST_INC_TIME: + { + pClock->IncScaledTime( 1.0 ); + break; + } + + case TEST_SET_FACTOR_1: + { + pClock->SetScaleFactor_ppb( -500000); + break; + } + case TEST_SET_FACTOR_2: + { + pClock->SetScaleFactor_ppb( 500000 ); + break; + } + case TEST_DEC_TIME: + { + pClock->IncScaledTime( -0.5 ); + break; + } + } + } +} + +void +ClockTest::HandleClockEvent( cClockEvent& ClockEvent ) +{ + EV << "ClockTest (" << getName() << "): received values " << ClockEvent.GetID1() << " " << ClockEvent.GetID2() << " " << ClockEvent.GetID3() << " " << ClockEvent.GetID4() << endl; +} diff --git a/src/Testbenches/ClockTest/ClockTest.h b/src/Testbenches/ClockTest/ClockTest.h new file mode 100644 index 0000000..88af5d3 --- /dev/null +++ b/src/Testbenches/ClockTest/ClockTest.h @@ -0,0 +1,60 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TESTBENCH_CLOCK_TEST_H_ +#define LIBPTP_PTP_TESTBENCH_CLOCK_TEST_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "CallableModule.h" +#include "ScheduleClock.h" +#include "IClockEventSink.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class ClockTest : public cCallableModule, public IClockEventSink +{ + private: + cScheduleClock *pClock; + + protected: + virtual void initialize(); + virtual void handleMessage(cMessage *msg); + + public: + + ClockTest(); + + void HandleClockEvent( cClockEvent& ClockEvent ); +}; + +#endif diff --git a/src/Testbenches/ClockTest/ClockTest.ned b/src/Testbenches/ClockTest/ClockTest.ned new file mode 100644 index 0000000..9022af9 --- /dev/null +++ b/src/Testbenches/ClockTest/ClockTest.ned @@ -0,0 +1,31 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.ClockTest; + +simple ClockTest +{ + parameters: + string ClockPath = default("^.Clock"); + + gates: +} diff --git a/src/Testbenches/EncapTest/EncapTest.cc b/src/Testbenches/EncapTest/EncapTest.cc new file mode 100644 index 0000000..f38e055 --- /dev/null +++ b/src/Testbenches/EncapTest/EncapTest.cc @@ -0,0 +1,220 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "EncapTest.h" + +#include "PTP_Ethernet.h" + +#include "IPProtocolId_m.h" +#include "IPv4Datagram.h" +#include "IPv4Address.h" +#include "Ieee802Ctrl_m.h" + +// ====================================================== +// Types +// ====================================================== + +typedef enum +{ + TEST_SEND_PTP_ANNOUNCE, + TEST_SEND_PTP_SYNC, + TEST_SEND_PTP_FOLLOW_UP, + TEST_SEND_PTP_DELAY_REQ, + TEST_SEND_PTP_DELAY_RESP, + TEST_SEND_PTP_PDELAY_REQ, + TEST_SEND_PTP_PDELAY_RESP, + TEST_SEND_PTP_PDELAY_RESP_FU, + TEST_SEND_PTP_SIGNALING, + TEST_SEND_PTP_MANAGEMENT, + TEST_SEND_IP, +} +tTestID; + +struct tTestSchedule +{ + double TestTime; + tTestID TestID; +}; + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +static tTestSchedule TestSchedule[] = +{ + { 1, TEST_SEND_PTP_ANNOUNCE }, + { 2, TEST_SEND_PTP_SYNC }, + { 3, TEST_SEND_PTP_FOLLOW_UP }, + { 4, TEST_SEND_PTP_DELAY_REQ }, + { 5, TEST_SEND_PTP_DELAY_RESP }, + { 6, TEST_SEND_PTP_PDELAY_REQ }, + { 7, TEST_SEND_PTP_PDELAY_RESP }, + { 8, TEST_SEND_PTP_PDELAY_RESP_FU }, + { 9, TEST_SEND_PTP_SIGNALING }, + { 10, TEST_SEND_PTP_MANAGEMENT }, + { 11, TEST_SEND_IP }, +}; + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(EncapTest); + +// ====================================================== +// Definitions +// ====================================================== + +void +EncapTest::SendPtpMsg( tPtpMessageType MsgType ) +{ + Ieee802Ctrl *pCtrl = new Ieee802Ctrl; + PTPv2_Frame *pPtp; + + switch( MsgType ) + { + case PTP_TYPE_ANNOUNCE: pPtp = new PTPv2_AnnounceFrame; break; + case PTP_TYPE_SYNC: pPtp = new PTPv2_SyncFrame; break; + case PTP_TYPE_FOLLOW_UP: pPtp = new PTPv2_Follow_UpFrame; break; + case PTP_TYPE_DELAY_REQ: pPtp = new PTPv2_Delay_ReqFrame; break; + case PTP_TYPE_DELAY_RESP: pPtp = new PTPv2_Delay_RespFrame; break; + case PTP_TYPE_PDELAY_REQ: pPtp = new PTPv2_PDelay_ReqFrame; break; + case PTP_TYPE_PDELAY_RESP: pPtp = new PTPv2_PDelay_RespFrame; break; + case PTP_TYPE_PDELAY_RESP_FU: pPtp = new PTPv2_PDelay_Resp_FU_Frame; break; + case PTP_TYPE_SIGNALING: pPtp = new PTPv2_SignalingFrame; break; + case PTP_TYPE_MANAGEMENT: pPtp = new PTPv2_ManagementFrame; break; + + default: + case PTP_TYPE_INVALID: + { + return; + } + } + + pPtp->setMessageType( MsgType ); + + pCtrl->setSrc( SrcMAC ); + pCtrl->setEtherType( PTP_ETH_TYPE ); + + switch( MsgType ) + { + case PTP_TYPE_PDELAY_REQ: + case PTP_TYPE_PDELAY_RESP: + case PTP_TYPE_PDELAY_RESP_FU: + { + pCtrl->setDest( PtpMcPDelayMAC ); + break; + } + default: + { + pCtrl->setDest( PtpMcMAC ); + break; + } + } + + pPtp->setControlInfo( pCtrl ); + + send( pPtp, "upperLayerOut" ); +} + +void +EncapTest::SendIpMsg() +{ + Ieee802Ctrl *pCtrl = new Ieee802Ctrl; + IPv4Datagram *pIP = new IPv4Datagram; + + pIP->setSrcAddress( IPv4Address(192,168,0,1) ); + pIP->setDestAddress( IPv4Address(192,168,0,5) ); + pIP->setTransportProtocol( IP_PROT_TCP ); + + pCtrl->setSrc( SrcMAC ); + pCtrl->setDest(MACAddress::BROADCAST_ADDRESS); + pCtrl->setEtherType(ETHERTYPE_IPv4); + + pIP->setControlInfo(pCtrl); + + send( pIP, "upperLayerOut" ); +} + +void +EncapTest::initialize() +{ + // Get resources + + // Set up schedule + for( size_t i = 0; i < sizeof(TestSchedule)/sizeof(TestSchedule[0]); i++ ) + { + cMessage *pMsg = new cMessage; + simtime_t Time = simtime_t(TestSchedule[i].TestTime); + int ID = TestSchedule[i].TestID; + + EV << "Scheduling test " << ID << " at time " << Time << endl; + + pMsg->setKind( ID ); + + scheduleAt( Time, pMsg ); + } +} + +void EncapTest::handleMessage(cMessage *msg) +{ + if( msg->isSelfMessage() ) + { + EV << "Starting test " << msg->getKind() << endl; + + switch( msg->getKind() ) + { + case TEST_SEND_PTP_SYNC: SendPtpMsg( PTP_TYPE_SYNC ); break; + case TEST_SEND_PTP_DELAY_REQ: SendPtpMsg( PTP_TYPE_DELAY_REQ ); break; + case TEST_SEND_PTP_PDELAY_REQ: SendPtpMsg( PTP_TYPE_PDELAY_REQ ); break; + case TEST_SEND_PTP_PDELAY_RESP: SendPtpMsg( PTP_TYPE_PDELAY_RESP ); break; + case TEST_SEND_PTP_FOLLOW_UP: SendPtpMsg( PTP_TYPE_FOLLOW_UP ); break; + case TEST_SEND_PTP_DELAY_RESP: SendPtpMsg( PTP_TYPE_DELAY_RESP ); break; + case TEST_SEND_PTP_PDELAY_RESP_FU: SendPtpMsg( PTP_TYPE_PDELAY_RESP_FU ); break; + case TEST_SEND_PTP_ANNOUNCE: SendPtpMsg( PTP_TYPE_ANNOUNCE ); break; + case TEST_SEND_PTP_SIGNALING: SendPtpMsg( PTP_TYPE_SIGNALING ); break; + case TEST_SEND_PTP_MANAGEMENT: SendPtpMsg( PTP_TYPE_MANAGEMENT ); break; + case TEST_SEND_IP: SendIpMsg(); break; + } + + delete msg; + } + else + { + delete msg; + } +} + +EncapTest::EncapTest() + : SrcMAC( "C0:FF:EE:C0:FF:EE" ), PtpMcMAC(PTP_ETH_MC_DEFAULT_MAC), PtpMcPDelayMAC(PTP_ETH_MC_PDELAY_MAC) +{ +} diff --git a/src/Testbenches/EncapTest/EncapTest.h b/src/Testbenches/EncapTest/EncapTest.h new file mode 100644 index 0000000..a6cfcb6 --- /dev/null +++ b/src/Testbenches/EncapTest/EncapTest.h @@ -0,0 +1,61 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TESTBENCH_ENCAP_TEST_H_ +#define LIBPTP_PTP_TESTBENCH_ENCAP_TEST_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTPv2_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class EncapTest : public cSimpleModule +{ + private: + + const MACAddress SrcMAC; + const MACAddress PtpMcMAC; + const MACAddress PtpMcPDelayMAC; + + void SendPtpMsg( tPtpMessageType MsgType ); + void SendIpMsg(); + + protected: + virtual void initialize(); + virtual void handleMessage(cMessage *msg); + + public: + EncapTest(); +}; + +#endif diff --git a/src/Testbenches/EncapTest/EncapTest.ned b/src/Testbenches/EncapTest/EncapTest.ned new file mode 100644 index 0000000..6288417 --- /dev/null +++ b/src/Testbenches/EncapTest/EncapTest.ned @@ -0,0 +1,30 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.EncapTest; + +simple EncapTest +{ + gates: + input upperLayerIn @labels(Ieee802Ctrl/up); + output upperLayerOut @labels(Ieee802Ctrl/down); +} diff --git a/src/Testbenches/EtherPhyTester/EtherPhyTester.cc b/src/Testbenches/EtherPhyTester/EtherPhyTester.cc new file mode 100755 index 0000000..6ffc852 --- /dev/null +++ b/src/Testbenches/EtherPhyTester/EtherPhyTester.cc @@ -0,0 +1,100 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "EtherPhyTester.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(EtherPhyTester); + +// ====================================================== +// Definitions +// ====================================================== + +void +EtherPhyTester::SendMsg() +{ + T_tx = simTime(); + + EthernetIIFrame *pEthFrame; + + pEthFrame = new EthernetIIFrame; + + send( pEthFrame, "TestGate$o" ); +} + +void +EtherPhyTester::HandleRx( EthernetIIFrame *pEthFrame ) +{ + simtime_t T_rx = simTime(); + simtime_t T_diff = T_rx - T_tx; + + EV << "T_diff: " << T_diff << endl; + + emit( Delay_SigId, T_diff ); + + delete pEthFrame; +} + +void +EtherPhyTester::initialize() +{ + TestInterval = simtime_t( 1, SIMTIME_S ); + IntervalTimer = new cMessage( "Next Test" ); + + Delay_SigId = registerSignal( "Delay" ); + + scheduleAt( simTime() + TestInterval, IntervalTimer ); +} + +void +EtherPhyTester::handleMessage(cMessage *pMsg) +{ + if( pMsg->isSelfMessage() ) + { + SendMsg(); + + scheduleAt( simTime() + TestInterval, IntervalTimer ); + } + else + { + HandleRx( check_and_cast(pMsg) ); + } +} diff --git a/src/Testbenches/EtherPhyTester/EtherPhyTester.h b/src/Testbenches/EtherPhyTester/EtherPhyTester.h new file mode 100755 index 0000000..2961242 --- /dev/null +++ b/src/Testbenches/EtherPhyTester/EtherPhyTester.h @@ -0,0 +1,54 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_ETHER_PHY_TESTER_H_ +#define LIBPTP_ETHER_PHY_TESTER_H_ + +#include + +#include "EtherFrame_m.h" + +class EtherPhyTester : public cSimpleModule +{ + private: + + // Config + simtime_t TestInterval; + + // Resources + cMessage *IntervalTimer; + simtime_t T_tx; + + // Signals + simsignal_t Delay_SigId; + + // Internal functions + void SendMsg(); + void HandleRx( EthernetIIFrame *pEthFrame ); + + protected: + + virtual void initialize(); + virtual void handleMessage(cMessage *pMsg); +}; + +#endif diff --git a/src/Testbenches/EtherPhyTester/EtherPhyTester.ned b/src/Testbenches/EtherPhyTester/EtherPhyTester.ned new file mode 100755 index 0000000..d79365d --- /dev/null +++ b/src/Testbenches/EtherPhyTester/EtherPhyTester.ned @@ -0,0 +1,38 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.EtherPhyTester; + +simple EtherPhyTester +{ + parameters: + + @display("i=block/mac"); + + @signal[Delay](type=simtime_t); + @statistic[Delay](record=stats,vector,histogram); + + gates: + + inout TestGate @labels(EtherFrame); + +} diff --git a/src/Testbenches/PTP_EthSink/PTP_EthSink.cc b/src/Testbenches/PTP_EthSink/PTP_EthSink.cc new file mode 100644 index 0000000..e77ecc1 --- /dev/null +++ b/src/Testbenches/PTP_EthSink/PTP_EthSink.cc @@ -0,0 +1,230 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_EthSink.h" + +#include "PTP_Ethernet.h" + +#include "EtherFrame_m.h" +#include "PTPv2_m.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Constants +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(PTP_EthSink); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +PTP_EthSink::initialize() +{ +} + +// ------------------------------------------------------ +// Handle message +// ------------------------------------------------------ +void +PTP_EthSink::handleMessage(cMessage *msg) +{ + EthernetIIFrame *pEth = check_and_cast(msg); + + if( pEth->getEtherType() != PTP_ETH_TYPE) + { + throw cRuntimeError("PTP EthSink: Received message with wrong EthType: %d", pEth->getEtherType() ); + } + + PTPv2_Frame *pPtp = check_and_cast(pEth->decapsulate()); + + switch( pPtp->getMessageType() ) + { + case PTP_TYPE_ANNOUNCE: EV << "Announce" << endl; break; + case PTP_TYPE_SYNC: EV << "Sync" << endl; break; + case PTP_TYPE_FOLLOW_UP: EV << "Follow Up" << endl; break; + case PTP_TYPE_DELAY_REQ: EV << "Delay Requ" << endl; break; + case PTP_TYPE_DELAY_RESP: EV << "Delay Resp" << endl; break; + case PTP_TYPE_PDELAY_REQ: EV << "PDelay Req" << endl; break; + case PTP_TYPE_PDELAY_RESP: EV << "PDelay Resp" << endl; break; + case PTP_TYPE_PDELAY_RESP_FU: EV << "PDelay Resp FU" << endl; break; + case PTP_TYPE_SIGNALING: EV << "Signaling" << endl; break; + case PTP_TYPE_MANAGEMENT: EV << "Management" << endl; break; + case PTP_TYPE_INVALID: EV << "Invalid" << endl; break; + } + + EV << "" << endl; + EV << "General PTP information" << endl; + EV << "Domain Number: " << (int) pPtp->getDomainNumber() << endl; + EV << "Ingress: " << pPtp->getIngressTimeStamp() << endl; + + switch( pPtp->getMessageType() ) + { + case PTP_TYPE_ANNOUNCE: + { + PTPv2_AnnounceFrame *pAnn = check_and_cast(pPtp); + + EV << "" << endl; + EV << "Announce specific information" << endl; + EV << "GrandmasterIdentity: " << pAnn->getGrandmasterIdentity().GetString() << endl; + EV << "GrandmasterPriority1: " << (int) pAnn->getGrandmasterPriority1() << endl; + EV << "GrandmasterPriority2: " << (int) pAnn->getGrandmasterPriority2() << endl; + EV << "StepsRemoved: " << pAnn->getStepsRemoved() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_SYNC: + { + PTPv2_SyncFrame *pSync = check_and_cast(pPtp); + + EV << "" << endl; + EV << "Sync specific information" << endl; + EV << "SequID: " << pSync->getSequenceId() << endl; + EV << "OriginTimestamp: " << pSync->getOriginTimestamp().GetSimTime() << endl; + EV << "CorrectionField: " << pSync->getCorrectionField().GetSimTime() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_FOLLOW_UP: + { + PTPv2_Follow_UpFrame *pFU = check_and_cast(pPtp); + + EV << "" << endl; + EV << "Follow Up specific information" << endl; + EV << "SequID: " << pFU->getSequenceId() << endl; + EV << "preciseOriginTimestamp: " << pFU->getPreciseOriginTimestamp().GetSimTime() << endl; + EV << "CorrectionField: " << pFU->getCorrectionField().GetSimTime() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_DELAY_REQ: + { + PTPv2_Delay_ReqFrame *pDelReq = check_and_cast(pPtp); + + EV << "" << endl; + EV << "Delay Request specific information" << endl; + EV << "SequID: " << pDelReq->getSequenceId() << endl; + EV << "originTimestamp: " << pDelReq->getOriginTimestamp().GetSimTime() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_DELAY_RESP: + { + PTPv2_Delay_RespFrame *pDelResp = check_and_cast(pPtp); + + EV << "" << endl; + EV << "Delay Response specific information" << endl; + EV << "SequID: " << pDelResp->getSequenceId() << endl; + EV << "ReceiveTimestamp: " << pDelResp->getReceiveTimestamp().GetSimTime() << endl; + EV << "RequestingPortIdentity: " << pDelResp->getRequestingPortIdentity().GetString() << endl; + EV << "DelayReq EgressTime: " << pDelResp->getReqEgressTime() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_PDELAY_REQ: + { + PTPv2_PDelay_ReqFrame *pPDelReq = check_and_cast(pPtp); + + EV << "" << endl; + EV << "PDelay Request specific information" << endl; + EV << "SequID: " << pPDelReq->getSequenceId() << endl; + EV << "originTimestamp: " << pPDelReq->getOriginTimestamp().GetSimTime() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_PDELAY_RESP: + { + PTPv2_PDelay_RespFrame *pPDelResp = check_and_cast(pPtp); + + EV << "" << endl; + EV << "PDelay Response specific information" << endl; + EV << "SequID: " << pPDelResp->getSequenceId() << endl; + EV << "RequestReceiptTimestamp: " << pPDelResp->getRequestReceiptTimestamp().GetSimTime() << endl; + EV << "RequestingPortIdentity: " << pPDelResp->getRequestingPortIdentity().GetString() << endl; + EV << "PDelayReq EgressTime: " << pPDelResp->getReqEgressTime() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_PDELAY_RESP_FU: + { + PTPv2_PDelay_Resp_FU_Frame *pPDelRespFU = check_and_cast(pPtp); + + EV << "" << endl; + EV << "PDelay Response Follow Up specific information" << endl; + EV << "SequID: " << pPDelRespFU->getSequenceId() << endl; + EV << "ResponseOriginTimestamp: " << pPDelRespFU->getResponseOriginTimestamp().GetSimTime() << endl; + EV << "RequestingPortIdentity: " << pPDelRespFU->getRequestingPortIdentity().GetString() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_SIGNALING: + { + PTPv2_SignalingFrame *pSig = check_and_cast(pPtp); + + EV << "" << endl; + EV << "Signaling specific information" << endl; + EV << "TargetPortIdentity: " << pSig->getTargetPortIdentity().GetString() << endl; + EV << "" << endl; + + break; + } + case PTP_TYPE_MANAGEMENT: + case PTP_TYPE_INVALID: + { + break; + } + } + + delete pEth; + delete pPtp; +} diff --git a/src/Testbenches/PTP_EthSink/PTP_EthSink.h b/src/Testbenches/PTP_EthSink/PTP_EthSink.h new file mode 100644 index 0000000..6b7004a --- /dev/null +++ b/src/Testbenches/PTP_EthSink/PTP_EthSink.h @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TESTBENCH_PTP_ETH_SINK_H_ +#define LIBPTP_PTP_TESTBENCH_PTP_ETH_SINK_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class PTP_EthSink : public cSimpleModule +{ + protected: + + virtual void initialize(); + virtual void handleMessage(cMessage *msg); +}; + +#endif diff --git a/src/Testbenches/PTP_EthSink/PTP_EthSink.ned b/src/Testbenches/PTP_EthSink/PTP_EthSink.ned new file mode 100644 index 0000000..489ca88 --- /dev/null +++ b/src/Testbenches/PTP_EthSink/PTP_EthSink.ned @@ -0,0 +1,30 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.PTP_EthSink; + +simple PTP_EthSink +{ + @display("i=block/filter"); + gates: + input lowerLayerIn @labels(EtherFrame); +} diff --git a/src/Testbenches/PtpFrameGen/PtpFrameGen.cc b/src/Testbenches/PtpFrameGen/PtpFrameGen.cc new file mode 100644 index 0000000..19837e1 --- /dev/null +++ b/src/Testbenches/PtpFrameGen/PtpFrameGen.cc @@ -0,0 +1,433 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PtpFrameGen.h" + +#include "PTP_Parser.h" + +// ====================================================== +// Definitions +// ====================================================== + +#define MAX_PARSE_STRING_LEN 50 + +#define ArrayLen(A) (sizeof(A)/sizeof(A[0])) + +// ====================================================== +// Types +// ====================================================== + +typedef enum +{ + TEST_SEND_PTP_ANNOUNCE, + TEST_SEND_PTP_SYNC, + TEST_SEND_PTP_FOLLOW_UP, + TEST_SEND_PTP_DELAY_REQ, + TEST_SEND_PTP_PDELAY_REQ, + TEST_SEND_ETH_TINY, + TEST_SEND_ETH_SMALL, + TEST_SEND_ETH_LARGE, + TEST_SEND_ETH_HUGE, +} +tTestID; + +struct tTestIdParse +{ + tTestID Value; + char String[MAX_PARSE_STRING_LEN]; +}; + +// ====================================================== +// Constants +// ====================================================== + +const tTestIdParse TestIdPareArray[] = +{ + { TEST_SEND_PTP_ANNOUNCE, "TEST_SEND_PTP_ANNOUNCE" }, + { TEST_SEND_PTP_SYNC, "TEST_SEND_PTP_SYNC" }, + { TEST_SEND_PTP_FOLLOW_UP, "TEST_SEND_PTP_FOLLOW_UP" }, + { TEST_SEND_PTP_DELAY_REQ, "TEST_SEND_PTP_DELAY_REQ" }, + { TEST_SEND_PTP_PDELAY_REQ, "TEST_SEND_PTP_PDELAY_REQ" }, + { TEST_SEND_ETH_TINY, "TEST_SEND_ETH_TINY" }, + { TEST_SEND_ETH_SMALL, "TEST_SEND_ETH_SMALL" }, + { TEST_SEND_ETH_LARGE, "TEST_SEND_ETH_LARGE" }, + { TEST_SEND_ETH_HUGE, "TEST_SEND_ETH_HUGE" }, +}; + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(PtpFrameGen); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Template search function +// ------------------------------------------------------ + +tTestID ParseTestID( const tTestIdParse *pParse, const size_t size, const char *Str) +{ + for(size_t i = 0; i < size; i++) + { + if( 0 == strcmp(pParse[i].String, Str)) + { + return pParse[i].Value; + } + } + + // No match could be found --> Throw exception + throw cRuntimeError("Parsing exception: Failed to parse value '%s'", Str ); +} + +// ------------------------------------------------------ +// Fill in basic information +// ------------------------------------------------------ +void +PtpFrameGen::FillInBasicInfo( PTPv2_Frame *pPtp ) +{ + pPtp->setTransportSpecific(0); + pPtp->setReserved_0(0); + pPtp->setVersionPTP( PTP_VERSION_IEEE_1588_2008 ); + pPtp->setDomainNumber( PrimaryDomain ); + pPtp->setReserved_1(0); + + cPtpHeaderFlags& FlagField = pPtp->getFlagField(); + + FlagField.alternateMasterFlag = false; + FlagField.unicastFlag = false; + FlagField.ptpProfileSpecific_1 = false; + FlagField.ptpProfileSpecific_2 = false; + FlagField.reserved = false; + FlagField.leap59 = false; + FlagField.leap61 = false; + FlagField.currentUtcOffsetValid = false; + FlagField.ptpTimescale = false; + FlagField.timeTraceable = true; + FlagField.frequencyTraceable = true; + + pPtp->setCorrectionField( cTimeInterval() ); // Table 21 + + for( unsigned int i = 0; i < pPtp->getReserved_2ArraySize(); i++ ) + { + pPtp->setReserved_2( i, 0); + } + + pPtp->setSourcePortIdentity( PortIdentity ); +} + +// ------------------------------------------------------ +// Fill in Announce specific information +// ------------------------------------------------------ +void +PtpFrameGen::FillInAnnounceFrame( PTPv2_AnnounceFrame *pPtp ) +{ + pPtp->setOriginTimestamp( cTimeStamp(0,0) ); + + pPtp->setCurrentUtcOffset( 0 ); + pPtp->setGrandmasterPriority1( Priority1 ); + pPtp->setGrandmasterClockQuality( ClockQuality ); + pPtp->setGrandmasterPriority2( Priority2 ); + pPtp->setGrandmasterIdentity( PortIdentity.ClockIdentity() ); + pPtp->setStepsRemoved( 0 ); + pPtp->setTimeSource( TIME_SRC_ATOMIC_CLOCK ); + + // Generic PTP header, 13.3.2 + pPtp->setMessageType(PTP_TYPE_ANNOUNCE); + pPtp->setMessageLength(PTP_MSG_ANNOUNCE_LEN); + pPtp->getFlagField().twoStepFlag = false; + pPtp->setSequenceId( AnnSequId++ ); + pPtp->setControlField( PTP_MSG_CTRL_OTHERS ); + pPtp->setLogMessageInterval( 0 ); + pPtp->setByteLength( PTP_MSG_ANNOUNCE_LEN ); +} + +// ------------------------------------------------------ +// Fill in Sync specific information +// ------------------------------------------------------ +void +PtpFrameGen::FillInSyncFrame( PTPv2_SyncFrame *pPtp ) +{ + if(PTP_TwoStepFlag) + { + pPtp->setOriginTimestamp( cTimeStamp(0,0) ); + } + else + { + pPtp->setOriginTimestamp( simTime() ); + } + + pPtp->setMessageType(PTP_TYPE_SYNC); + pPtp->setMessageLength(PTP_MSG_SYNC_LEN); + pPtp->getFlagField().twoStepFlag = PTP_TwoStepFlag; + pPtp->setSequenceId( SyncSequId++ ); + pPtp->setControlField( PTP_MSG_CTRL_SYNC ); + pPtp->setLogMessageInterval( 0 ); + pPtp->setByteLength( PTP_MSG_SYNC_LEN ); +} + +void +PtpFrameGen::FillInSyncFUFrame( PTPv2_Follow_UpFrame *pPtp ) +{ + pPtp->setPreciseOriginTimestamp( cTimeStamp(0,0) ); + + // Generic PTP header, 13.3.2 + pPtp->setMessageType(PTP_TYPE_FOLLOW_UP); + pPtp->setMessageLength(PTP_MSG_FOLLOW_UP_LEN); + + pPtp->setFlagField( pSyncDup->getFlagField() ); + pPtp->getFlagField().twoStepFlag = false; + + pPtp->setSequenceId( pSyncDup->getSequenceId() ); + pPtp->setControlField( PTP_MSG_CTRL_FOLLOW_UP ); + pPtp->setLogMessageInterval( pSyncDup->getLogMessageInterval() ); + + pPtp->setByteLength( PTP_MSG_FOLLOW_UP_LEN ); +} + +// ------------------------------------------------------ +// Fill in DelayReq specific information +// ------------------------------------------------------ +void +PtpFrameGen::FillInDelayReqFrame( PTPv2_Delay_ReqFrame *pPtp) +{ + pPtp->setOriginTimestamp( cTimeStamp(0,0) ); + + pPtp->setMessageType(PTP_TYPE_DELAY_REQ); + pPtp->setMessageLength(PTP_MSG_DEL_REQ_LEN); + pPtp->getFlagField().twoStepFlag = PTP_TwoStepFlag; + pPtp->setSequenceId( DelaySequId++ ); + pPtp->setControlField( PTP_MSG_CTRL_DEL_REQ ); + pPtp->setLogMessageInterval( 0x7F ); + pPtp->setByteLength( PTP_MSG_DEL_REQ_LEN ); +} + +void +PtpFrameGen::FillInPDelayReqFrame( PTPv2_PDelay_ReqFrame *pPtp) +{ + pPtp->setOriginTimestamp( cTimeStamp(0,0) ); + + pPtp->setMessageType(PTP_TYPE_PDELAY_REQ); + pPtp->setMessageLength(PTP_MSG_PDEL_REQ_LEN); + + pPtp->getFlagField().twoStepFlag = false; + + pPtp->setSequenceId( PDelaySequId++ ); + pPtp->setControlField( PTP_MSG_CTRL_OTHERS ); + pPtp->setLogMessageInterval( 0x7F ); + pPtp->setByteLength( PTP_MSG_PDEL_REQ_LEN ); +} + +// ------------------------------------------------------ +// Create frames +// ------------------------------------------------------ + +PTPv2_AnnounceFrame * +PtpFrameGen::CreateAnnounceFrame() +{ + PTPv2_AnnounceFrame *pPtp = new PTPv2_AnnounceFrame; + + FillInBasicInfo( pPtp ); + FillInAnnounceFrame( pPtp ); + + return pPtp; +} + +PTPv2_SyncFrame * +PtpFrameGen::CreateSyncFrame() +{ + PTPv2_SyncFrame *pPtp = new PTPv2_SyncFrame; + + FillInBasicInfo( pPtp ); + FillInSyncFrame( pPtp ); + + if( pSyncDup != nullptr ) + { + delete pSyncDup; + } + pSyncDup = pPtp->dup(); + + return pPtp; +} + +PTPv2_Follow_UpFrame * +PtpFrameGen::CreateFollowUpFrame() +{ + if( pSyncDup == nullptr ) + { + throw cRuntimeError( "Can't create FollowUp frame before Sync frame" ); + } + if( PTP_TwoStepFlag == false ) + { + throw cRuntimeError( "Can't create FollowUp if TwoStepFlag is set to false" ); + } + + PTPv2_Follow_UpFrame *pPtp = new PTPv2_Follow_UpFrame; + + FillInBasicInfo( pPtp ); + FillInSyncFUFrame( pPtp ); + + return pPtp; +} + +PTPv2_Delay_ReqFrame * +PtpFrameGen::CreateDelayReqFrame() +{ + PTPv2_Delay_ReqFrame *pPtp = new PTPv2_Delay_ReqFrame; + + FillInBasicInfo( pPtp ); + FillInDelayReqFrame( pPtp ); + + return pPtp; +} + +PTPv2_PDelay_ReqFrame * +PtpFrameGen::CreatePDelayReqFrame() +{ + PTPv2_PDelay_ReqFrame *pPtp = new PTPv2_PDelay_ReqFrame; + + FillInBasicInfo( pPtp ); + FillInPDelayReqFrame( pPtp ); + + return pPtp; +} + +PTPv2_SyncFrame * +PtpFrameGen::CreateEthFrame( size_t ByteLength ) +{ + PTPv2_SyncFrame *pPtp = new PTPv2_SyncFrame; + + FillInBasicInfo( pPtp ); + FillInSyncFrame( pPtp ); + + pPtp->setByteLength( ByteLength ); + + return pPtp; +} + +// ------------------------------------------------------ +// Parse parameters +// ------------------------------------------------------ +void +PtpFrameGen::ParseParameters() +{ + PortIdentity.ClockIdentity() = par("ClockID").stringValue(); + PortIdentity.SetPortNumber( par( "PortNumber" ).longValue() ); + + PTP_TwoStepFlag = par( "PTP_TwoStepFlag" ).boolValue(); + Priority1 = par( "Priority1" ).longValue(); + Priority2 = par( "Priority2" ).longValue(); + PrimaryDomain = cPTP_Parser::ParseDomainNumber ( par( "DomainNumber" ).stringValue() ); + + ClockQuality.SetOffsetScaledLogVar( par( "OffsetScaledLogVariance" ).longValue() ); + ClockQuality.SetClockAccuracy( cPTP_Parser::ParseClockAccuracy ( par( "ClockAccuracy" ).stringValue() ) ); + ClockQuality.SetClockClass( cPTP_Parser::ParseClockClass( par( "ClockClass" ).stringValue() ) ); + + const char *vstr = par("TestSchedule").stringValue(); // e.g. "aa bb cc"; + std::vector v = cStringTokenizer(vstr, " ,\t" ).asVector(); + + for( std::vector::const_iterator i = v.begin(); i != v.end(); ++i) + { + simtime_t t; + tTestID TestID; + + t = simtime_t(::strtod(i->c_str(), nullptr ) ); + + i++; + + TestID = ParseTestID( TestIdPareArray, ArrayLen(TestIdPareArray), i->c_str() ); + + EV << "Scheduling Test with ID " << TestID << " at time " << t << endl; + + cMessage *pMsg = new cMessage; + + pMsg->setKind( TestID ); + + scheduleAt( t, pMsg ); + } +} + +// ------------------------------------------------------ +// Initialize +// ------------------------------------------------------ +void +PtpFrameGen::initialize() +{ + ParseParameters(); + + // Get resources + AnnSequId = 0; + SyncSequId = 0; + DelaySequId = 0; + PDelaySequId = 0; + + pSyncDup = nullptr; +} + +// ------------------------------------------------------ +// Handle message +// ------------------------------------------------------ +void +PtpFrameGen::handleMessage(cMessage *msg) +{ + if( msg->isSelfMessage() ) + { + EV << "Starting test of type " << msg->getKind() << endl; + + switch( msg->getKind() ) + { + case TEST_SEND_PTP_ANNOUNCE: send( CreateAnnounceFrame(), "PtpOut" ); break; + case TEST_SEND_PTP_SYNC: send( CreateSyncFrame(), "PtpOut" ); break; + case TEST_SEND_PTP_FOLLOW_UP: send( CreateFollowUpFrame(), "PtpOut" ); break; + case TEST_SEND_PTP_DELAY_REQ: send( CreateDelayReqFrame(), "PtpOut" ); break; + case TEST_SEND_PTP_PDELAY_REQ: send( CreatePDelayReqFrame(), "PtpOut" ); break; + + case TEST_SEND_ETH_TINY: send( CreateEthFrame( 40 ), "PtpOut" ); break; + case TEST_SEND_ETH_SMALL: send( CreateEthFrame( 64 ), "PtpOut" ); break; + case TEST_SEND_ETH_LARGE: send( CreateEthFrame( 1200 ), "PtpOut" ); break; + case TEST_SEND_ETH_HUGE: send( CreateEthFrame( 2000 ), "PtpOut" ); break; + } + } + + delete msg; +} + +// ------------------------------------------------------ +// Constructor/Destructor +// ------------------------------------------------------ +PtpFrameGen::~PtpFrameGen() +{ + if( pSyncDup != nullptr ) + { + delete pSyncDup; + } +} diff --git a/src/Testbenches/PtpFrameGen/PtpFrameGen.h b/src/Testbenches/PtpFrameGen/PtpFrameGen.h new file mode 100644 index 0000000..9d4c148 --- /dev/null +++ b/src/Testbenches/PtpFrameGen/PtpFrameGen.h @@ -0,0 +1,88 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_TESTBENCH_PTP_FRAME_GEN_TEST_H_ +#define LIBPTP_PTP_TESTBENCH_PTP_FRAME_GEN_TEST_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +#include "PTPv2_m.h" + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +class PtpFrameGen : public cSimpleModule +{ + private: + + void FillInBasicInfo( PTPv2_Frame *pPtp ); + void FillInAnnounceFrame( PTPv2_AnnounceFrame *pPtp ); + void FillInSyncFrame( PTPv2_SyncFrame *pPtp ); + void FillInSyncFUFrame( PTPv2_Follow_UpFrame *pPtp ); + void FillInDelayReqFrame( PTPv2_Delay_ReqFrame *pPtp); + void FillInPDelayReqFrame( PTPv2_PDelay_ReqFrame *pPtp); + + PTPv2_AnnounceFrame *CreateAnnounceFrame(); + PTPv2_SyncFrame *CreateSyncFrame(); + PTPv2_Follow_UpFrame *CreateFollowUpFrame(); + PTPv2_Delay_ReqFrame *CreateDelayReqFrame(); + PTPv2_PDelay_ReqFrame *CreatePDelayReqFrame(); + + PTPv2_SyncFrame *CreateEthFrame( size_t ByteLength ); + + void ParseParameters(); + + protected: + + // Resources + UInteger16 AnnSequId; + UInteger16 SyncSequId; + UInteger16 DelaySequId; + UInteger16 PDelaySequId; + PTPv2_SyncFrame *pSyncDup; + + // Configuration + cPortIdentity PortIdentity; + bool PTP_TwoStepFlag; + uint8_t Priority1; + uint8_t Priority2; + + cClockQuality ClockQuality; + domainNumber_t PrimaryDomain; + + virtual void initialize(); + virtual void handleMessage(cMessage *msg); + + public: + ~PtpFrameGen(); +}; + +#endif diff --git a/src/Testbenches/PtpFrameGen/PtpFrameGen.ned b/src/Testbenches/PtpFrameGen/PtpFrameGen.ned new file mode 100644 index 0000000..827bb03 --- /dev/null +++ b/src/Testbenches/PtpFrameGen/PtpFrameGen.ned @@ -0,0 +1,55 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.PtpFrameGen; + +simple PtpFrameGen +{ + parameters: + + int PortNumber = default(1); + + string ClockID = default("C0 FF EE FF FE 00 00 01"); + bool PTP_TwoStepFlag = default(true); + int Priority1 = default(5); + int Priority2 = default(10); + int OffsetScaledLogVariance = default(57); + string ClockClass = default("CLOCK_CLASS_DEFAULT"); + string ClockAccuracy = default("CLOCK_ACCURACY_1_MS"); + string DomainNumber = default("DOMAIN_DEFAULT"); + + string TestSchedule = default(" 10.0, TEST_SEND_PTP_ANNOUNCE, \ + 11.0, TEST_SEND_PTP_ANNOUNCE, \ + 20.0, TEST_SEND_PTP_SYNC, \ + 21.0, TEST_SEND_PTP_SYNC, \ + 30.0, TEST_SEND_PTP_FOLLOW_UP, \ + 31.0, TEST_SEND_PTP_FOLLOW_UP, \ + 40.0, TEST_SEND_PTP_DELAY_REQ, \ + 41.0, TEST_SEND_PTP_DELAY_REQ, \ + 50.0, TEST_SEND_PTP_PDELAY_REQ, \ + 51.0, TEST_SEND_PTP_PDELAY_REQ, \ + "); + + @display("i=block/star"); + gates: + output PtpOut; +} diff --git a/src/Testbenches/PtpMacSink/PtpMacSink.ned b/src/Testbenches/PtpMacSink/PtpMacSink.ned new file mode 100644 index 0000000..56dd65a --- /dev/null +++ b/src/Testbenches/PtpMacSink/PtpMacSink.ned @@ -0,0 +1,89 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.PtpMacSink; + +import libptp.Testbenches.PTP_EthSink.PTP_EthSink; +import inet.networklayer.common.InterfaceTable; +import inet.base.NotificationBoard; + +import libptp.Hardware.*.*; +import libptp.Hardware.HwClock.ScheduleClock.IScheduleClock; + +module PtpMacSink +{ + parameters: + + string ClockType = default("ConstantDriftScheduleClock"); + string MAC_Address = default( "C0:FF:EE:BA:D0:1D" ); + + string PTP_ClockType = default("PTP_CLOCK_TYPE_ORDINARY"); + bool PTP_TwoStepFlag = default(false); + string PTP_DelayMechanism = default("DELAY_MECH_E2E"); + + @display("i=block/sink"); + @node; + + gates: + + inout ethg[] @labels(EtherFrame-conn); + + submodules: + + interfaceTable: InterfaceTable { + @display("p=175,312;is=s"); + } + + notificationBoard: NotificationBoard { + parameters: + @display("p=284,312;is=s"); + } + + Clock: like IScheduleClock { + parameters: + @display("p=175,235"); + } + + PTP_NIC_Ctrl: PTP_NIC_Ctrl { + @display("p=175,133"); + } + + MAC: PTP_MAC { + + parameters: + @display("p=84,235"); + + address = MAC_Address; + PTP_ClockType = PTP_ClockType; + PTP_TwoStepFlag = PTP_TwoStepFlag; + PTP_DelayMechanism = PTP_DelayMechanism; + } + + Sink: PTP_EthSink { + @display("p=84,133"); + } + + connections allowunconnected: + + ethg[0] <--> MAC.phys; + MAC.upperLayerOut --> Sink.lowerLayerIn; +} diff --git a/src/Testbenches/PtpMacSource/PtpMacSource.ned b/src/Testbenches/PtpMacSource/PtpMacSource.ned new file mode 100644 index 0000000..4fcb9d8 --- /dev/null +++ b/src/Testbenches/PtpMacSource/PtpMacSource.ned @@ -0,0 +1,114 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.PtpMacSource; + +import libptp.Testbenches.PtpFrameGen.PtpFrameGen; +import inet.networklayer.common.InterfaceTable; +import inet.base.NotificationBoard; + +import libptp.Hardware.*.*; +import libptp.Software.*.*; + +import libptp.Hardware.HwClock.ScheduleClock.IScheduleClock; + +module PtpMacSource +{ + parameters: + + string ClockType = default("ConstantDriftScheduleClock"); + string MAC_Address = default("C0:FF:EE:BA:D0:1D"); + + string PTP_ClockType = default("PTP_CLOCK_TYPE_ORDINARY"); + bool PTP_TwoStepFlag = default(false); + string PTP_DelayMechanism = default("DELAY_MECH_E2E"); + + @display("i=block/source"); + @node; + + gates: + + inout ethg[] @labels(EtherFrame-conn); + + submodules: + + interfaceTable: InterfaceTable { + @display("p=191,410;is=s"); + } + + notificationBoard: NotificationBoard { + parameters: + @display("p=298,410;is=s"); + } + + Clock: like IScheduleClock { + parameters: + @display("p=191,333"); + } + + PTP_NIC_Ctrl: PTP_NIC_Ctrl { + @display("p=191,231"); + } + + FrameSource: PtpFrameGen { + + parameters: + + @display("p=84,36"); + + PTP_TwoStepFlag = PTP_TwoStepFlag; + } + + EthMap: PTP_EthernetMapping { + + parameters: + + @display("p=84,127"); + + gates: + upperLayerIn[1]; + upperLayerOut[1]; + } + + Encap: PTP_EtherEncap { + @display("p=84,231"); + } + + MAC: PTP_MAC { + + parameters: + @display("p=84,333"); + + address = MAC_Address; + PTP_ClockType = PTP_ClockType; + PTP_TwoStepFlag = PTP_TwoStepFlag; + PTP_DelayMechanism = PTP_DelayMechanism; + } + + connections allowunconnected: + + FrameSource.PtpOut --> EthMap.upperLayerIn[0]; + EthMap.lowerLayerOut --> Encap.upperLayerIn; + Encap.lowerLayerOut --> MAC.upperLayerIn; + ethg[0] <--> MAC.phys; + +} diff --git a/src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.cc b/src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.cc new file mode 100644 index 0000000..86763db --- /dev/null +++ b/src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.cc @@ -0,0 +1,66 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PtpStackTest_Parser.h" +#include "ParameterParser.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +ParseType PtpStackTestTypeParse[] = +{ + { NONE, "NONE" }, + { CLOCK_IDENTITY, "CLOCK_IDENTITY" }, + { PORT_IDENTITY, "PORT_IDENTITY" }, + { FOREIGN_CLOCK_DS, "FOREIGN_CLOCK_DS" }, + { ALL, "ALL" }, +}; + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// PTP stack test type +// ------------------------------------------------------ +PtpStackTestType_t +cPtpStackTest_Parser::ParseTestType(const char *Str) +{ + return Parse( PtpStackTestTypeParse, ArrayLen(PtpStackTestTypeParse), Str ); +} diff --git a/src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.h b/src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.h new file mode 100644 index 0000000..2605a23 --- /dev/null +++ b/src/Testbenches/PtpStackTest/ParameterParser/PtpStackTest_Parser.h @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_STACK_TEST_PARSER_H_ +#define LIBPTP_PTP_STACK_TEST_PARSER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PtpStackTestType.h" + +// ====================================================== +// Types +// ====================================================== + +class cPtpStackTest_Parser +{ + public: + + static PtpStackTestType_t ParseTestType (const char *Str); +}; + +// ====================================================== +// Declarations +// ====================================================== + +#endif + diff --git a/src/Testbenches/PtpStackTest/PtpStackTest.cc b/src/Testbenches/PtpStackTest/PtpStackTest.cc new file mode 100644 index 0000000..98cfec3 --- /dev/null +++ b/src/Testbenches/PtpStackTest/PtpStackTest.cc @@ -0,0 +1,94 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PtpStackTest.h" + +#include "PtpStackTest_Parser.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +Define_Module(PtpStackTest); + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +PtpStackTest::CarryOutTest() +{ + switch( TestType ) + { + case CLOCK_IDENTITY: TestClockIdentity(); + break; + + case PORT_IDENTITY: TestPortIdentity(); + break; + + case FOREIGN_CLOCK_DS: TestForeignClockDS(); + break; + + case ALL: TestClockIdentity(); + TestPortIdentity(); + TestForeignClockDS(); + break; + + default: + case NONE: // Nothing to do + break; + } +} + +// ------------------------------------------------------ +// Init API +// ------------------------------------------------------ +void +PtpStackTest::ParseParameters() +{ + TestType = cPtpStackTest_Parser::ParseTestType( par("TestType").stringValue() ); +} + +void +PtpStackTest::FinishInit() +{ + CarryOutTest(); +} diff --git a/src/Testbenches/PtpStackTest/PtpStackTest.h b/src/Testbenches/PtpStackTest/PtpStackTest.h new file mode 100644 index 0000000..5326bc9 --- /dev/null +++ b/src/Testbenches/PtpStackTest/PtpStackTest.h @@ -0,0 +1,50 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_STACK_TEST_H_ +#define LIBPTP_PTP_STACK_TEST_H_ + +#include + +#include "ModuleInitBase.h" +#include "PtpStackTestType.h" + +class PtpStackTest: public cModuleInitBase +{ + private: + + // Configuration + PtpStackTestType_t TestType; + + // Internal functions + void CarryOutTest(); + void TestClockIdentity(); + void TestPortIdentity(); + void TestForeignClockDS(); + + protected: + // Init API + void ParseParameters(); + void FinishInit(); +}; + +#endif diff --git a/src/Testbenches/PtpStackTest/PtpStackTest.ned b/src/Testbenches/PtpStackTest/PtpStackTest.ned new file mode 100644 index 0000000..68ac939 --- /dev/null +++ b/src/Testbenches/PtpStackTest/PtpStackTest.ned @@ -0,0 +1,31 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp.Testbenches.PtpStackTest; + +simple PtpStackTest +{ + parameters: + + string TestType = default("NONE"); + @display("i=msg/req"); +} diff --git a/src/Testbenches/PtpStackTest/PtpStackTestType.h b/src/Testbenches/PtpStackTest/PtpStackTestType.h new file mode 100644 index 0000000..4169cdd --- /dev/null +++ b/src/Testbenches/PtpStackTest/PtpStackTestType.h @@ -0,0 +1,48 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PTP_STACK_TEST_TYPE_H_ +#define LIBPTP_PTP_STACK_TEST_TYPE_H_ + +// ====================================================== +// Includes +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +typedef enum +{ + NONE, + CLOCK_IDENTITY, + PORT_IDENTITY, + FOREIGN_CLOCK_DS, + ALL, +} +PtpStackTestType_t; + +// ====================================================== +// Declarations +// ====================================================== + +#endif diff --git a/src/Testbenches/PtpStackTest/Tests/ClockIdentity.cc b/src/Testbenches/PtpStackTest/Tests/ClockIdentity.cc new file mode 100644 index 0000000..d36f002 --- /dev/null +++ b/src/Testbenches/PtpStackTest/Tests/ClockIdentity.cc @@ -0,0 +1,88 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PtpStackTest.h" + +#include "PTP_ClockIdentity.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +PtpStackTest::TestClockIdentity() +{ + EV << __func__ << endl; + + cClockIdentity A( "C0-FF-EE-FE-EE-00-00-02" ); + cClockIdentity B( "C0-FF-EE-FE-EE-00-00-05" ); + cClockIdentity C( A ); + cClockIdentity D; + + D = B; + + // Equal + assert( (A == A) == true ); + assert( (A == B) == false ); + assert( (A == C) == true ); + assert( (A == D) == false ); + assert( (B == D) == true ); + + // Not equal + assert( (A != A) == false ); + assert( (A != B) == true ); + + // Smaller + assert( (A < B) == true ); + assert( (A < A) == false ); + assert( (B < A) == false ); + + // Greater + assert( (B > A) == true ); + assert( (B > B) == false ); + assert( (A > B) == false ); +} diff --git a/src/Testbenches/PtpStackTest/Tests/ForeignClockDS.cc b/src/Testbenches/PtpStackTest/Tests/ForeignClockDS.cc new file mode 100644 index 0000000..3ec5ec5 --- /dev/null +++ b/src/Testbenches/PtpStackTest/Tests/ForeignClockDS.cc @@ -0,0 +1,199 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PtpStackTest.h" + +#include "PTP_ForeignClockDS.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +static void CompareClockDS( cForeignClockDS &A, cForeignClockDS &B, ClockCompResult Result1, ClockCompResult Result2, ClockCompReason Reason ) +{ + ClockCompReturn_t Res; + + Res = cForeignClockDS::CompareClockDS( A, B, false ); + assert( Res.Result == Result1 ); + assert( Res.Reason == Reason ); + + Res = cForeignClockDS::CompareClockDS( B, A, false ); + assert( Res.Result == Result2 ); + assert( Res.Reason == Reason ); +} + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +PtpStackTest::TestForeignClockDS() +{ + EV << __func__ << endl; + + cForeignClockDS A; + cForeignClockDS B; + cForeignClockDS A2; + + + // Initialize A + A.ClockIdentity() = "AA-AA-AA-FF-FE-00-00-01"; + A.ClockQuality().SetClockAccuracy( CLOCK_ACCURACY_1_S ); + A.ClockQuality().SetClockClass( CLOCK_CLASS_PRIMARY ); + A.ClockQuality().SetOffsetScaledLogVar( 100 ); + + A.SenderPortIdentity() = cPortIdentity( "BB-BB-BB-FF-FE-00-00-01", 5 ); + A.ReceiverPortIdentity()= cPortIdentity( "CC-CC-CC-FF-FE-00-00-01", 4 ); + A.FlagsField() = cPtpHeaderFlags(); + + A.SetPriority1( 100 ); + A.SetPriority2( 100 ); + A.SetStepsRemoved( 5 ); + + A.Activate(); + + A2 = A; + + A2.ClockIdentity() = "DD-DD-DD-FF-FE-00-00-01"; + + // ------------------------------------------------------------------ + // Equal + // ------------------------------------------------------------------ + B = A; + CompareClockDS( A, B, ClockCompResult::ERROR_2, ClockCompResult::ERROR_2, ClockCompReason::NONE ); + + // ------------------------------------------------------------------ + // One side empty + // ------------------------------------------------------------------ + B.Clear(); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::OTHER_EMPTY ); + + // ------------------------------------------------------------------ + // Priority 1 + // ------------------------------------------------------------------ + B = A2; + B.SetPriority1(200); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::PRIORITY1 ); + + // ------------------------------------------------------------------ + // GM Class + // ------------------------------------------------------------------ + B = A2; + B.ClockQuality().SetClockClass(CLOCK_CLASS_SLAVE_ONLY); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::CLOCK_CLASS ); + + // ------------------------------------------------------------------ + // GM Accuracy + // ------------------------------------------------------------------ + B = A2; + B.ClockQuality().SetClockAccuracy(CLOCK_ACCURACY_OVER_10_S); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::CLOCK_ACCURACY ); + + // ------------------------------------------------------------------ + // GM offsetScaledLogVariance + // ------------------------------------------------------------------ + B = A2; + B.ClockQuality().SetOffsetScaledLogVar(200); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::OFFSET_LOG_VAR ); + + // ------------------------------------------------------------------ + // Priority 2 + // ------------------------------------------------------------------ + B = A2; + B.SetPriority2(200); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::PRIORITY2 ); + + // ------------------------------------------------------------------ + // GM Identity + // ------------------------------------------------------------------ + B = A2; + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::CLOCK_ID ); + + // ------------------------------------------------------------------ + // StepsRemoved (not within 1 step) + // ------------------------------------------------------------------ + B = A; + B.SetStepsRemoved( A.GetStepsRemoved() + 2 ); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::STEPS_REMOVED ); + + // ------------------------------------------------------------------ + // StepsRemoved 2a (within 1 step, receiver smaller) + // ------------------------------------------------------------------ + B = A; + B.SetStepsRemoved( A.GetStepsRemoved() + 1 ); + B.SenderPortIdentity() = cPortIdentity( "BB-BB-BB-FF-FE-00-00-01", 5 ); + B.ReceiverPortIdentity() = cPortIdentity( "BB-BB-BB-FF-FE-00-00-01", 4 ); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B, ClockCompResult::B_BETTER_A, ClockCompReason::RCV_PORT_ID_SMALLER ); + + // ------------------------------------------------------------------ + // StepsRemoved 2b (within 1 step, receiver greater) + // ------------------------------------------------------------------ + B = A; + B.SetStepsRemoved( A.GetStepsRemoved() + 1 ); + B.SenderPortIdentity() = cPortIdentity( "BB-BB-BB-FF-FE-00-00-01", 5 ); + B.ReceiverPortIdentity() = cPortIdentity( "BB-BB-BB-FF-FE-00-00-01", 6 ); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B_BY_TOPO, ClockCompResult::B_BETTER_A_BY_TOPO, ClockCompReason::RCV_PORT_ID_GREATER ); + + // ------------------------------------------------------------------ + // StepsRemoved 2c (within 1 step, receiver equals sender) + // ------------------------------------------------------------------ + B = A; + B.SetStepsRemoved( A.GetStepsRemoved() + 1 ); + B.SenderPortIdentity() = B.ReceiverPortIdentity(); + CompareClockDS( A, B, ClockCompResult::ERROR_1, ClockCompResult::ERROR_1, ClockCompReason::NONE ); + + // ------------------------------------------------------------------ + // Sender identity + // ------------------------------------------------------------------ + B = A; + B.SenderPortIdentity().SetPortNumber( A.SenderPortIdentity().GetPortNumber() + 1 ); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B_BY_TOPO, ClockCompResult::B_BETTER_A_BY_TOPO, ClockCompReason::SND_PORT_ID ); + + // ------------------------------------------------------------------ + // Port number + // ------------------------------------------------------------------ + B = A; + B.ReceiverPortIdentity().SetPortNumber( A.ReceiverPortIdentity().GetPortNumber() + 1 ); + CompareClockDS( A, B, ClockCompResult::A_BETTER_B_BY_TOPO, ClockCompResult::B_BETTER_A_BY_TOPO, ClockCompReason::RCV_PORT_NUMBER ); + +} diff --git a/src/Testbenches/PtpStackTest/Tests/PortIdentity.cc b/src/Testbenches/PtpStackTest/Tests/PortIdentity.cc new file mode 100644 index 0000000..a844a01 --- /dev/null +++ b/src/Testbenches/PtpStackTest/Tests/PortIdentity.cc @@ -0,0 +1,95 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PtpStackTest.h" + +#include "PTP_PortIdentity.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +// ------------------------------------------------------ +// Internal functions +// ------------------------------------------------------ +void +PtpStackTest::TestPortIdentity() +{ + EV << __func__ << endl; + + cPortIdentity A( "C0-FF-EE-FE-EE-00-00-02", 1 ); + cPortIdentity B( "C0-FF-EE-FE-EE-00-00-05", 3 ); + cPortIdentity C( A ); + cPortIdentity D; + cPortIdentity E( A ); + + E.SetPortNumber( 5 ); + + D = B; + + // Equal + assert( (A == A) == true ); + assert( (A == B) == false ); + assert( (A == C) == true ); + assert( (A == D) == false ); + assert( (B == D) == true ); + assert( (A == E) == false ); + + // Not equal + assert( (A != A) == false ); + assert( (A != B) == true ); + assert( (A != E) == true ); + + // Smaller + assert( (A < B) == true ); + assert( (A < A) == false ); + assert( (B < A) == false ); + assert( (A < E) == true ); + + // Greater + assert( (A > B) == false ); + assert( (A > A) == false ); + assert( (B > A) == true ); + assert( (A > E) == false ); +} diff --git a/src/Utils/ByteOrder/ByteOrder.cc b/src/Utils/ByteOrder/ByteOrder.cc new file mode 100644 index 0000000..c820e31 --- /dev/null +++ b/src/Utils/ByteOrder/ByteOrder.cc @@ -0,0 +1,165 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "ByteOrder.h" + +#include // Get htonl etc. for Windows and Linux + +#include + +// ====================================================== +// Remarks +// ====================================================== + +// Boost/Endianess +// +// The Boost library recently got an Endianess module +// (starting with v1.58). This might be a better option +// than the self developed code here. +// +// http://www.boost.org/doc/libs/1_58_0/libs/endian/doc/index.html + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== + +UInteger16 +NetToHostUI16( UInteger16 UInt16 ) +{ + return (UInteger16) ntohs( UInt16 ); +} + +UInteger32 +NetToHostUI32( UInteger32 UInt32 ) +{ + return (UInteger32) ntohl( UInt32 ); +} + +Integer16 +NetToHostI16( Integer16 Int16 ) +{ + return (Integer16) ntohs( Int16 ); +} + +Integer32 +NetToHostI32( Integer32 Int32 ) +{ + return (Integer32) ntohl( Int32 ); +} + +Integer64 +NetToHostI64( Integer64 Int64 ) +{ +#ifdef _WIN32 + // Code-snippet from http://www.codeproject.com/Articles/4804/Basic-concepts-on-Endianness + return (((Integer64)(ntohl((int)((Int64 << 32) >> 32))) << 32) | (unsigned int)ntohl(((int)(Int64 >> 32)))); +#else + return (Integer64) be64toh( Int64 ); +#endif +} + +UInteger48 +BufToHostUI48( Octet Buf[], size_t BufSize ) +{ + UInteger48 UInt48; + + if( BufSize != 6 ) + throw cRuntimeError( "Unexpected BufSize" ); + + UInt48 = ((((UInteger48) Buf[0]) & 0xFF) << 40) | + ((((UInteger48) Buf[1]) & 0xFF) << 32) | + ((((UInteger48) Buf[2]) & 0xFF) << 24) | + ((((UInteger48) Buf[3]) & 0xFF) << 16) | + ((((UInteger48) Buf[4]) & 0xFF) << 8) | + ((((UInteger48) Buf[5]) & 0xFF) << 0); + + return UInt48; +} + +UInteger16 +HostToNetUI16( UInteger16 UInt16 ) +{ + return (UInteger16) htons( UInt16 ); +} + +UInteger32 +HostToNetUI32( UInteger32 UInt32 ) +{ + return (UInteger32) htonl( UInt32 ); +} + +Integer16 +HostToNetI16( Integer16 Int16 ) +{ + return (Integer16) htons( Int16 ); +} + +Integer32 +HostToNetI32( Integer32 Int32 ) +{ + return (Integer32) htonl( Int32 ); +} + +Integer64 +HostToNetI64( Integer64 Int64 ) +{ +#ifdef _WIN32 + return NetToHostI64(Int64); +#else + return (Integer64) htobe64( Int64 ); +#endif +} + +void +HostToBufUI48( UInteger48 UInt48, Octet Buf[], size_t BufSize ) +{ + if( BufSize != 6 ) + throw cRuntimeError( "Unexpected BufSize" ); + + Buf[0] = (Octet) ((UInt48 >> 40) & 0xFF); + Buf[1] = (Octet) ((UInt48 >> 32) & 0xFF); + Buf[2] = (Octet) ((UInt48 >> 24) & 0xFF); + Buf[3] = (Octet) ((UInt48 >> 16) & 0xFF); + Buf[4] = (Octet) ((UInt48 >> 8) & 0xFF); + Buf[5] = (Octet) ((UInt48 >> 0) & 0xFF); +} + diff --git a/src/Utils/ByteOrder/ByteOrder.h b/src/Utils/ByteOrder/ByteOrder.h new file mode 100644 index 0000000..5ed243c --- /dev/null +++ b/src/Utils/ByteOrder/ByteOrder.h @@ -0,0 +1,62 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_BYTE_ORDER_H_ +#define LIBPTP_BYTE_ORDER_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include "PTP_PrimitiveDataTypes.h" + +#include + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +UInteger16 NetToHostUI16( UInteger16 UInt16 ); +UInteger32 NetToHostUI32( UInteger32 UInt32 ); +Integer16 NetToHostI16 ( Integer16 Int16 ); +Integer32 NetToHostI32 ( Integer32 Int32 ); +Integer64 NetToHostI64 ( Integer64 Int64 ); + +UInteger48 BufToHostUI48( Octet Buf[], size_t BufSize ); + +UInteger16 HostToNetUI16( UInteger16 UInt16 ); +UInteger32 HostToNetUI32( UInteger32 UInt32 ); +Integer16 HostToNetI16 ( Integer16 Int16 ); +Integer32 HostToNetI32 ( Integer32 Int32 ); +Integer64 HostToNetI64 ( Integer64 Int64 ); + +void HostToBufUI48( UInteger48 UInt48, Octet Buf[], size_t BufSize ); + +#endif diff --git a/src/Utils/Constants/PhysicalConstants.cc b/src/Utils/Constants/PhysicalConstants.cc new file mode 100644 index 0000000..24ff0a7 --- /dev/null +++ b/src/Utils/Constants/PhysicalConstants.cc @@ -0,0 +1,49 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +// ====================================================== +// Includes +// ====================================================== + +#include "PhysicalConstants.h" + +// ====================================================== +// Definitions +// ====================================================== + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Variables +// ====================================================== + +const Integer64 NANOSECS_PER_S = 1E9; + +// ====================================================== +// Declarations +// ====================================================== + +// ====================================================== +// Definitions +// ====================================================== diff --git a/src/Utils/Constants/PhysicalConstants.h b/src/Utils/Constants/PhysicalConstants.h new file mode 100644 index 0000000..304dfc7 --- /dev/null +++ b/src/Utils/Constants/PhysicalConstants.h @@ -0,0 +1,42 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +#ifndef LIBPTP_PHYSICAL_CONSTANTS_H_ +#define LIBPTP_PHYSICAL_CONSTANTS_H_ + +// ====================================================== +// Includes +// ====================================================== + +#include + +// ====================================================== +// Types +// ====================================================== + +// ====================================================== +// Declarations +// ====================================================== + +extern const Integer64 NANOSECS_PER_S; + +#endif diff --git a/src/package.ned b/src/package.ned new file mode 100644 index 0000000..1a95af4 --- /dev/null +++ b/src/package.ned @@ -0,0 +1,25 @@ +// ============================================================================ +// +// Copyright 2013-2015 Wolfgang Wallner (wolfgang-wallner AT gmx.at) +// +// This file is part of the LibPTP project. +// +// The LibPTP project is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The LibPTP project is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License.com for more details. +// +// You should have received a copy of the GNU General Public License.com +// along with The LibPTP project. +// If not, see . +// +// ============================================================================ + +package libptp; + +@license(GPL);