diff --git a/doc/designDoc.pptx b/doc/designDoc.pptx index a4ff2ca..ca8533e 100644 Binary files a/doc/designDoc.pptx and b/doc/designDoc.pptx differ diff --git a/doc/img/networkCommDesign.png b/doc/img/networkCommDesign.png index e58c0e0..5409405 100644 Binary files a/doc/img/networkCommDesign.png and b/doc/img/networkCommDesign.png differ diff --git a/src/plcCtrl/signalPlcEmu/plcConfig_tamplate.txt b/src/plcCtrl/signalPlcEmu/plcConfig_tamplate.txt new file mode 100644 index 0000000..9fdbd88 --- /dev/null +++ b/src/plcCtrl/signalPlcEmu/plcConfig_tamplate.txt @@ -0,0 +1,29 @@ +# This is the config file template for the module +# Setup the paramter with below format (every line follow : format, the +# key can not be changed): + +# Set the master PLC's name +PLC_NAME:PLC-01 + +# Define the ip addresses allowed to read PLC state: +# json list fomat: ["masterIP", "slave1IP", ...] +ALLOW_R_L:["127.0.0.1", "192.168.0.10"] + +# Define the ip addresses allowed to change PLC state: +# json list fomat: ["masterIP", "slave1IP", ...] +ALLOW_W_L:["127.0.0.1"] + +# Define Realworld emulator ip +RW_IP:127.0.0.1 + +# Define Realworld emulator connection port +RW_PORT:3001 + +# Define PLC clock interval +CLK_INT:0.6 + +# Define modbus TCP host IP, use 0.0.0.0 or localhost +MD_BUS_IP:localhost + +# Define modbus TCP host Port, normally use 502 +MD_BUS_PORT:502 \ No newline at end of file diff --git a/src/plcCtrl/signalPlcEmu/plcSimGlobalSignal.py b/src/plcCtrl/signalPlcEmu/plcSimGlobalSignal.py index ced259b..f1140ee 100644 --- a/src/plcCtrl/signalPlcEmu/plcSimGlobalSignal.py +++ b/src/plcCtrl/signalPlcEmu/plcSimGlobalSignal.py @@ -7,8 +7,9 @@ # Author: Yuancheng Liu # # Created: 2010/08/26 -# Copyright: -# License: +# Version: v0.1.2 +# Copyright: Copyright (c) 2023 Singapore National Cybersecurity R&D Lab LiuYuancheng +# License: MIT License #----------------------------------------------------------------------------- """ For good coding practice, follow the following naming convention: @@ -26,7 +27,10 @@ TOPDIR = 'src' LIBDIR = 'lib' +CONFIG_FILE_NAME = 'plcConfig.txt' +#----------------------------------------------------------------------------- +# Init the logger: idx = dirpath.find(TOPDIR) gTopDir = dirpath[:idx + len(TOPDIR)] if idx != -1 else dirpath # found it - truncate right after TOPDIR # Config the lib folder @@ -43,16 +47,26 @@ LOG_ERR = 2 LOG_EXCEPT = 3 -PCL_NAME = 'PLC-01' -ALLOW_R_L = ['127.0.0.1', '192.168.0.10'] -ALLOW_W_L = ['127.0.0.1'] +#----------------------------------------------------------------------------- +# Init the configure file loader. +import ConfigLoader +gGonfigPath = os.path.join(dirpath, CONFIG_FILE_NAME) +iConfigLoader = ConfigLoader.ConfigLoader(gGonfigPath, mode='r') +if iConfigLoader is None: + print("Error: The config file %s is not exist.Program exit!" %str(gGonfigPath)) + exit() +CONFIG_DICT = iConfigLoader.getJson() +PLC_NAME = CONFIG_DICT['PLC_NAME'] +ALLOW_R_L = CONFIG_DICT['ALLOW_R_L'] +ALLOW_W_L = CONFIG_DICT['ALLOW_W_L'] #------------------------------------------- # VARIABLES are the built in data type. -gRealWordIP = ('127.0.0.1', 3001) -gInterval = 0.6 +gRealWordIP = (CONFIG_DICT['RW_IP'], int(CONFIG_DICT['RW_PORT'])) +gInterval = float(CONFIG_DICT['CLK_INT']) +gModBusIP = (CONFIG_DICT['MD_BUS_IP'], int(CONFIG_DICT['MD_BUS_PORT'])) def gDebugPrint(msg, prt=True, logType=None): if prt: print(msg) if logType == LOG_WARN: @@ -64,7 +78,6 @@ def gDebugPrint(msg, prt=True, logType=None): elif logType == LOG_INFO or DEBUG_FLG: Log.info(msg) - #------------------------------------------------------------ iMBhandler = None # modbus TCP data handler. iMBservice = None # modbus TCP service diff --git a/src/plcCtrl/signalPlcEmu/plcSimulatorSignal.py b/src/plcCtrl/signalPlcEmu/plcSimulatorSignal.py index 9dce9fb..2144cd3 100644 --- a/src/plcCtrl/signalPlcEmu/plcSimulatorSignal.py +++ b/src/plcCtrl/signalPlcEmu/plcSimulatorSignal.py @@ -5,13 +5,14 @@ # Purpose: A simple plc simulation module to connect and control the real-world # emulator via UDP (to simulate the eletrical signals change) and handle # SCADA system Modbus TCP request. +# This module will simulate the sensors-signals-auto-control system. # # Author: Yuancheng Liu # -# Version: v0.2 # Created: 2023/05/29 -# Copyright: -# License: +# Version: v0.1.2 +# Copyright: Copyright (c) 2023 Singapore National Cybersecurity R&D Lab LiuYuancheng +# License: MIT License #----------------------------------------------------------------------------- """ Program design: @@ -29,7 +30,7 @@ #----------------------------------------------------------------------------- class tFlipFlopLadderLogic(modbusTcpCom.ladderLogic): """ A T-flip-flop latching relay ladder logic set with 19 ladders. The ladder logic - need to be init and passed in the data handler (with handler auto-update flag + needs to be init and passed in the data handler (with handler auto-update flag set to True). """ @@ -49,6 +50,7 @@ def initLadderInfo(self): # address: 18 - 24: nsline sensors # address: 25 - 38: ccline sensors. weIdxOffSet, nsIdxOffset, ccIdxOffset = 0, 17, 25 + # init all the flip flop maping table. self.ffConfig = [ # Init all the weline signals flipflop: {'coilIdx': 0, 'onRegIdx':(ccIdxOffset+12,), 'offRegIdx':(ccIdxOffset+13,)}, @@ -77,7 +79,8 @@ def initLadderInfo(self): #----------------------------------------------------------------------------- def _tfligFlogRun(self, coilState, toggleOnList, toggleOffList): """ Function to simulate a normal t-flip-flop latching replay to take input - and calculate the output based on the current state. + and calculate the output based on the current state. If there is N register + state changed, this ladder logic will execute N times. Args: coilState (int/bool): Current output State. toggleOnList (list(int/bool)): input registers's state list which can toggle @@ -98,8 +101,8 @@ def runLadderLogic(self, regsList, coilList=None): coilsRsl = [] if len(regsList) != 39 or coilList is None or len(coilList)!=19: gv.gDebugPrint('runLadderLogic(): input not valid', logType=gv.LOG_WARN) - gv.gDebugPrint("Input registers list: %s" %str(regsList)) - gv.gDebugPrint("Input coils list: %s" %str(coilList)) + gv.gDebugPrint("Input registers list: %s" %str(regsList), logType=gv.LOG_INFO) + gv.gDebugPrint("Input coils list: %s" %str(coilList), logType=gv.LOG_INFO) else: for item in self.ffConfig: coilState = coilList[item['coilIdx']] @@ -113,8 +116,8 @@ def runLadderLogic(self, regsList, coilList=None): #----------------------------------------------------------------------------- class signalPlcSet(plcSimulator.plcSimuInterface): - def __init__(self, parent, plcID, addressInfoDict, ladderObj): - super().__init__(parent, plcID, addressInfoDict, ladderObj) + def __init__(self, parent, plcID, addressInfoDict, ladderObj, updateInt=0.6): + super().__init__(parent, plcID, addressInfoDict, ladderObj, updateInt=updateInt) def initInputState(self): self.regsAddrs = (0, 39) @@ -143,7 +146,7 @@ def initCoilState(self): #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- def main(): - #gv.gDebugPrint("Start Init the PLC: %s" %str(gv.PCL_NAME), logType=gv.LOG_INFO) + #gv.gDebugPrint("Start Init the PLC: %s" %str(gv.PLC_NAME), logType=gv.LOG_INFO) gv.iLadderLogic = tFlipFlopLadderLogic(None, ladderName='T_flipflop_logic_set') addressInfoDict = { 'hostaddress': ('localhost', 502), @@ -151,7 +154,8 @@ def main(): 'allowread':gv.ALLOW_R_L, 'allowwrite': gv.ALLOW_W_L } - plc = signalPlcSet(None, gv.PCL_NAME, addressInfoDict, gv.iLadderLogic) + plc = signalPlcSet(None, gv.PLC_NAME, addressInfoDict, + gv.iLadderLogic, updateInt=gv.gInterval) plc.run() if __name__ == "__main__": diff --git a/src/plcCtrl/stationPlcEmu/plcSimGlobalStation.py b/src/plcCtrl/stationPlcEmu/plcSimGlobalStation.py index ed64a49..c330d3a 100644 --- a/src/plcCtrl/stationPlcEmu/plcSimGlobalStation.py +++ b/src/plcCtrl/stationPlcEmu/plcSimGlobalStation.py @@ -44,7 +44,7 @@ LOG_EXCEPT = 3 # Init the PLC info. -PCL_NAME = 'PLC-03' +PLC_NAME = 'PLC-03' ALLOW_R_L = ['127.0.0.1', '192.168.0.10'] ALLOW_W_L = ['127.0.0.1'] diff --git a/src/plcCtrl/stationPlcEmu/plcSimulatorStation.py b/src/plcCtrl/stationPlcEmu/plcSimulatorStation.py index e2cd478..70569d4 100644 --- a/src/plcCtrl/stationPlcEmu/plcSimulatorStation.py +++ b/src/plcCtrl/stationPlcEmu/plcSimulatorStation.py @@ -101,7 +101,7 @@ def initCoilState(self): #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- def main(): - gv.gDebugPrint("Start Init the PLC: %s" %str(gv.PCL_NAME), logType=gv.LOG_INFO) + gv.gDebugPrint("Start Init the PLC: %s" %str(gv.PLC_NAME), logType=gv.LOG_INFO) gv.iLadderLogic = directConnLadderLogic(None, ladderName='Direct_connection') addressInfoDict = { 'hostaddress': ('localhost', 503), @@ -109,7 +109,7 @@ def main(): 'allowread':gv.ALLOW_R_L, 'allowwrite': gv.ALLOW_W_L } - plc = stationPlcSet(None, gv.PCL_NAME, addressInfoDict, gv.iLadderLogic) + plc = stationPlcSet(None, gv.PLC_NAME, addressInfoDict, gv.iLadderLogic) plc.run() if __name__ == "__main__": diff --git a/src/plcCtrl/trainPlcEmu/plcSimGlobalTrain.py b/src/plcCtrl/trainPlcEmu/plcSimGlobalTrain.py index 3712c7d..6e19691 100644 --- a/src/plcCtrl/trainPlcEmu/plcSimGlobalTrain.py +++ b/src/plcCtrl/trainPlcEmu/plcSimGlobalTrain.py @@ -44,7 +44,7 @@ LOG_EXCEPT = 3 # Init the PLC info. -PCL_NAME = 'PLC-06' +PLC_NAME = 'PLC-06' ALLOW_R_L = ['127.0.0.1', '192.168.0.10'] ALLOW_W_L = ['127.0.0.1'] diff --git a/src/plcCtrl/trainPlcEmu/plcSimulatorTrain.py b/src/plcCtrl/trainPlcEmu/plcSimulatorTrain.py index 384e545..72f300b 100644 --- a/src/plcCtrl/trainPlcEmu/plcSimulatorTrain.py +++ b/src/plcCtrl/trainPlcEmu/plcSimulatorTrain.py @@ -89,7 +89,7 @@ def initCoilState(self): #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- def main(): - gv.gDebugPrint("Start Init the PLC: %s" %str(gv.PCL_NAME), logType=gv.LOG_INFO) + gv.gDebugPrint("Start Init the PLC: %s" %str(gv.PLC_NAME), logType=gv.LOG_INFO) gv.iLadderLogic = onlyCoilLadderLogic(None, ladderName='only_coil_control') addressInfoDict = { 'hostaddress': ('localhost', 504), @@ -97,7 +97,7 @@ def main(): 'allowread':gv.ALLOW_R_L, 'allowwrite': gv.ALLOW_W_L } - plc = trainPowerPlcSet(None, gv.PCL_NAME, addressInfoDict, gv.iLadderLogic) + plc = trainPowerPlcSet(None, gv.PLC_NAME, addressInfoDict, gv.iLadderLogic) plc.run() if __name__ == "__main__":