Skip to content

Commit

Permalink
Added the train controller's execute .bat file, config file template.
Browse files Browse the repository at this point in the history
  • Loading branch information
LiuYuancheng committed Jul 26, 2023
1 parent 070a565 commit eb975e8
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 87 deletions.
Binary file added doc/img/trainPlc/2023-07-26_152445.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/trainPlc/2023-07-26_152554.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/trainPlc/2023-07-26_152608.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/trainPlc/operatingLogic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/trainPlc/readme0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/trainPlc/workflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/plcCtrl/trainPlcEmu/plcSimGlobalTrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
ALLOW_R_L = CONFIG_DICT['ALLOW_R_L']
ALLOW_W_L = CONFIG_DICT['ALLOW_W_L']


#-------<GLOBAL VARIABLES (start with "g")>------------------------------------
# VARIABLES are the built in data type.
gRealWordIP = (CONFIG_DICT['RW_IP'], int(CONFIG_DICT['RW_PORT']))
Expand Down
3 changes: 3 additions & 0 deletions src/runTrainsCtrlHMI.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off
python plcCtrl\trainCtrlUI\trainCtrlRun.py
pause
61 changes: 38 additions & 23 deletions src/trainCtrlUI/trainCtrlGlobal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
# Author: Yuancheng Liu
#
# Created: 2010/08/26
# Copyright:
# License:
# Version: v0.1.2
# Copyright: Copyright (c) 2023 LiuYuancheng
# License: MIT License
#-----------------------------------------------------------------------------
"""
For good coding practice, follow the following naming convention:
Expand All @@ -27,6 +28,7 @@

TOPDIR = 'src'
LIBDIR = 'lib'
CONFIG_FILE_NAME = 'trainHMIConfig.txt'

#-----------------------------------------------------------------------------
# Init the logger:
Expand All @@ -39,41 +41,54 @@
import Log
Log.initLogger(gTopDir, 'Logs', APP_NAME[0], APP_NAME[1], historyCnt=100, fPutLogsUnderDate=True)

#------<IMAGES PATH>-------------------------------------------------------------
IMG_FD = os.path.join(dirpath, 'img')
ICO_PATH = os.path.join(IMG_FD, "metro.ico")

TEST_MD = False
PLC_NUM = 2

PERIODIC = 300 # update the main in every 300ms
MBTCP_PORT = 502

# Init the log type parameters.
DEBUG_FLG = False
LOG_INFO = 0
LOG_WARN = 1
LOG_ERR = 2
LOG_EXCEPT = 3

#-----------------------------------------------------------------------------
# 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()

#------<IMAGES PATH>-------------------------------------------------------------
IMG_FD = os.path.join(dirpath, 'img')
ICO_PATH = os.path.join(IMG_FD, "metro.ico")

TEST_MD = CONFIG_DICT['TEST_MD'] # test mode flag, True: the simulator will operate with control logic itself.
PERIODIC = 300 # update the main in every 300ms
PLC_ID = CONFIG_DICT['PLC_ID']
PLC_IP = CONFIG_DICT['PLC_IP']
PLC_PORT = int(CONFIG_DICT['PLC_PORT'])

#-------<GLOBAL VARIABLES (start with "g")>------------------------------------
# VARIABLES are the built in data type.

gTrackConfig = OrderedDict()

# The PLC data fetching and set information
gPlcInfo = OrderedDict()
gPlcInfo['PLC-06'] = {'id': 'PLC-06', 'ipaddress': '127.0.0.1',
'port': 504, 'hRegsInfo': (0, 10), 'coilsInfo': (0, 10)}
gPlcInfo['PLC-06'] = {'id': PLC_ID, 'ipaddress': PLC_IP, 'port': PLC_PORT,
'hRegsInfo': (0, 10), 'coilsInfo': (0, 10)}

# Init the plc display panel data fetching mapping information.
gPlcPnlInfo = OrderedDict()
gPlcPnlInfo['PLC-06'] = {'id': 'PLC-06', 'label': 'PLC-05[Master:slot-0]', 'ipaddress': '127.0.0.1',
'port': 504, 'tgt': 'PLC-06', 'hRegsInfo': (0, 8), 'coilsInfo': (0, 8)}

gPlcPnlInfo['PLC-07'] = {'id': 'PLC-07', 'label': 'PLC-06[Slave:slot-1]', 'ipaddress': '127.0.0.1',
'port': 504, 'tgt': 'PLC-06', 'hRegsInfo': (8, 10), 'coilsInfo': (8, 10)}
gPlcPnlInfo['PLC-06'] = {'id': 'PLC-06', 'label': 'PLC-06[Master:slot-0]',
'ipaddress': PLC_IP, 'port': PLC_PORT, 'tgt': PLC_ID,
'hRegsInfo': (0, 8), 'coilsInfo': (0, 8)}

gTranspPct = 70 # Windows transparent percentage.
gUpdateRate = 2 # main frame update rate 1 sec.
gPlcPnlInfo['PLC-07'] = {'id': 'PLC-07', 'label': 'PLC-07[Slave:slot-1]',
'ipaddress': PLC_IP, 'port': PLC_PORT, 'tgt': PLC_ID,
'hRegsInfo': (8, 10), 'coilsInfo': (8, 10)}

#-------<GLOBAL VARIABLES (start with "g")>------------------------------------
# VARIABLES are the built in data type.
gUpdateRate = float(CONFIG_DICT['CLK_INT']) # main frame update rate every 2 sec.

def gDebugPrint(msg, prt=True, logType=None):
if prt: print(msg)
Expand Down
101 changes: 38 additions & 63 deletions src/trainCtrlUI/trainCtrlPanel.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,54 @@
#!/usr/bin/python
#-----------------------------------------------------------------------------
# Name: uiPanel.py
# Name: trainCtrl.py
#
# Purpose: This module is used to create different function panels for the
# train control HMI.
#
# Purpose: This module is used to create different function panels.
# Author: Yuancheng Liu
#
# Created: 2020/01/10
# Copyright: YC @ Singtel Cyber Security Research & Development Laboratory
# License: YC
# Created: 2023/07/12
# Version: v0.1.2
# Copyright: Copyright (c) 2023 LiuYuancheng
# License: MIT License
#-----------------------------------------------------------------------------

import os
import wx
import wx.grid

from datetime import datetime
import trainCtrlGlobal as gv

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
class PanelTrain(wx.Panel):
""" Mutli-information display panel used to show all sensors' detection
situation on the office top-view map, sensor connection status and a
wx.Grid to show all the sensors' basic detection data.
""" Mutli-information display panel used to show all the trains' name, speed
current voltage and power state.
"""
def __init__(self, parent):
""" Init the panel."""
wx.Panel.__init__(self, parent, size=(650, 300))
self.SetBackgroundColour(wx.Colour(39, 40, 62))
self.SetSizer(self._buidUISizer())

#--PanelMultInfo---------------------------------------------------------------
#------------------------------------------------------------------------------
def _buidUISizer(self):
""" Build the UI with 2 columns.left: Map, right: sensor data Grid."""
sizer = wx.BoxSizer(wx.HORIZONTAL)
""" Build the UI sizer with the information grid"""
sizer = wx.BoxSizer(wx.VERTICAL)
flagsL = wx.LEFT
sizer.AddSpacer(5)

vbox0 = wx.BoxSizer(wx.VERTICAL)

# Row 0: Set the panel label
font = wx.Font(12, wx.DECORATIVE, wx.BOLD, wx.BOLD)
label = wx.StaticText(self, label="Train Information")
label = wx.StaticText(self, label="Trains Information")
label.SetFont(font)
label.SetForegroundColour(wx.Colour("WHITE"))
vbox0.Add(label, flag=flagsL, border=2)
vbox0.AddSpacer(10)

sizer.Add(label, flag=flagsL, border=2)
sizer.AddSpacer(10)
# Row 1: set the information display grid
self.grid = wx.grid.Grid(self, -1)
self.grid.CreateGrid(12, 6)
# Set the Grid size.
self.grid.SetRowLabelSize(40)
#self.grid.SetColSize(0, 50)
#self.grid.SetColSize(1, 65)
#self.grid.SetColSize(2, 65)
# Set the Grid's labels.
self.grid.SetColLabelValue(0, 'Train-ID')
self.grid.SetColLabelValue(1, 'Railway-ID')
Expand All @@ -63,18 +60,17 @@ def _buidUISizer(self):
self.grid.SetColSize(4, 120)
self.grid.SetColLabelValue(5, 'Power-State')
self.grid.SetColSize(5, 120)
vbox0.Add(self.grid, flag=flagsL, border=2)
sizer.Add(vbox0, flag=flagsL, border=2)
sizer.Add(self.grid, flag=flagsL, border=2)
return sizer


#--PanelMultInfo---------------------------------------------------------------
#------------------------------------------------------------------------------
def updateTrainInfoGrid(self):
""" update the trains information grid.
"""
lineIdx = 0
for key in gv.gTrackConfig.keys():
trainsInfo = gv.iMapMgr.getTrainsInfo(key)
colorVal = gv.gTrackConfig[key]['color']
#gv.gDebugPrint(str(trainsInfo), logType=gv.LOG_INFO)
for data in trainsInfo:
self.grid.SetCellValue(lineIdx, 0, ' '+ str(data['id']))
self.grid.SetCellValue(lineIdx, 1, key)
Expand All @@ -87,7 +83,7 @@ def updateTrainInfoGrid(self):
self.grid.SetCellValue(lineIdx, 5, ' '+ str(pwdFlg))
self.grid.SetCellBackgroundColour(lineIdx, 5, bgColor)
lineIdx += 1
self.grid.ForceRefresh() # refresh all the grid's cell at one time ?
self.grid.ForceRefresh() # refresh all the grid's cell at one time

#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
Expand All @@ -104,7 +100,7 @@ def __init__(self, parent, name, ipAddr):
self.ipAddr = ipAddr
self.regsNum = 16
self.coilsNum = 8
self.connected = {'0': 'Unconnected', '1': 'Connected'}
self.connectedFlg = False
self.gpioInList = [0]*self.regsNum # PLC GPIO input stuation list.
self.gpioInLbList = [] # GPIO input device <id> label list.
self.gpioOuList = [0]*self.coilsNum # PLC GPIO output situation list.
Expand All @@ -120,16 +116,14 @@ def buidUISizer(self):
flagsR = wx.LEFT
mSizer.AddSpacer(5)
# Row idx = 0 : set the basic PLC informaiton.
self.nameLb = wx.StaticText(
self, label=" PLC Name: ".ljust(15)+self.plcName)
self.nameLb = wx.StaticText(self, label=" PLC Name: ".ljust(15)+self.plcName)
mSizer.Add(self.nameLb, flag=flagsR, border=5)
self.ipaddrLb = wx.StaticText(
self, label=" PLC IPaddr: ".ljust(15)+self.ipAddr)
self.ipaddrLb = wx.StaticText( self, label=" PLC IPaddr: ".ljust(15)+self.ipAddr)
mSizer.Add(self.ipaddrLb, flag=flagsR, border=5)
hbox0 = wx.BoxSizer(wx.HORIZONTAL)
hbox0.Add(wx.StaticText(self, label="Connection:".ljust(15)),
flag=flagsR, border=5)
self.connLb = wx.StaticText(self, label=self.connected['0'])
hbox0.Add(wx.StaticText(self, label="Connection:".ljust(15)), flag=flagsR, border=5)
self.connLb = wx.StaticText(self, label='Connected' if self.connectedFlg else 'Unconnected')
self.connLb.SetBackgroundColour( wx.Colour('GREEN') if self.connectedFlg else wx.Colour(120, 120, 120))
hbox0.Add(self.connLb, flag=flagsR, border=5)
mSizer.Add(hbox0, flag=flagsR, border=5)
mSizer.AddSpacer(10)
Expand All @@ -140,7 +134,6 @@ def buidUISizer(self):
# - row line structure: Input indicator | output label | output button with current status.
for i in range(self.regsNum):
hsizer = wx.BoxSizer(wx.HORIZONTAL)
# M221 doc: IO 0:3 are regular input, IO 4:7 are fast input.
# Col idx = 0: PLC input indicators.
lbtext = " R_%H 0."+str(i)
inputLb = wx.StaticText(self, label=lbtext.ljust(12))
Expand All @@ -155,50 +148,33 @@ def buidUISizer(self):
# Col idx =2: PLC output ON/OFF contorl buttons.
hsizer.AddSpacer(5)
outputBt = wx.Button(self, label='OFF', size=(50, 17), name=self.plcName+':'+str(i))
#outputBt.Bind(wx.EVT_BUTTON, self.relayOn)
self.gpioOuLbList.append(outputBt)
hsizer.Add(outputBt, flag=flagsR, border=5)
mSizer.Add(hsizer, flag=flagsR, border=5)
mSizer.AddSpacer(3)
return mSizer

#--PanelPLC--------------------------------------------------------------------
def relayOn(self, event):
""" Turn on the related ralay based on the user's action and update the
button's display situation.
"""
obj = event.GetEventObject()
print("PLC panel: Button idx %s" % str(obj.GetName()))
plcIdx = int(obj.GetName().split('[')[0][-1])
outIdx = int(obj.GetName().split(':')[-1])
# toggle output state.
self.gpioOuList[outIdx] = 1 - self.gpioOuList[outIdx]
self.updateOutput(outIdx, self.gpioOuList[outIdx])
# Update the element on the map.
tag = str((plcIdx+1)*100+outIdx)
for element in gv.iPowCtrlPanel.powerLabel:
if tag in element:
gv.iMapMgr.setSignalPwr(element, self.gpioOuList[outIdx])
break

#--PanelPLC--------------------------------------------------------------------
def setConnection(self, state):
""" Update the connection state on the UI. 0 - disconnect 1- connected
"""
self.connLb.SetLabel(self.connected[str(state)])
""" Update the connection state on the UI."""
self.connectedFlg = state
self.connLb.SetLabel('Connected' if self.connectedFlg else 'Unconnected')
self.connLb.SetBackgroundColour(
wx.Colour('GREEN') if state else wx.Colour(120, 120, 120))
wx.Colour('GREEN') if self.connectedFlg else wx.Colour(120, 120, 120))
self.Refresh(False)

#--PanelPLC--------------------------------------------------------------------
def updateHoldingRegs(self, regList):
""" Update the holding register's data and UI indicator's state if there
is new register chagne.
"""
if regList is None or self.gpioInList == regList: return # no new update
for idx in range(min(self.regsNum, len(regList))):
status = regList[idx]
if self.gpioInList[idx] != status:
self.gpioInList[idx] = status
self.gpioInLbList[idx].SetBackgroundColour(
wx.Colour('GREEN') if status else wx.Colour(120, 120, 120))
#self.Refresh(False)

def updateCoils(self, coilsList):
if coilsList is None or self.gpioOuList == coilsList: return
Expand All @@ -209,7 +185,6 @@ def updateCoils(self, coilsList):
self.gpioOuLbList[idx].SetLabel('ON' if status else 'OFF')
self.gpioOuLbList[idx].SetBackgroundColour(
wx.Colour('GREEN') if status else wx.Colour(253, 253, 253))
#self.Refresh(False)

def updataPLCdata(self):
if gv.idataMgr:
Expand Down
20 changes: 20 additions & 0 deletions src/trainCtrlUI/trainHMIConfig_tamplate.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This is the config file template for the module <monitorApp.py>
# Setup the paramter with below format (every line follow <key>:<val> format, the
# key can not be changed):

# Test mode:
# True: use the real word internal logic to simulator the control logic.
# False: connect to PLC let plc control the signals
TEST_MD:False

# Init the PLC information need to connect
PLC_ID:PLC-06

# Target PLC IP address
PLC_IP:127.0.0.1

# Target PLC port
PLC_PORT:504

# Define update clock interval
CLK_INT:2

0 comments on commit eb975e8

Please sign in to comment.