某一层楼20层,有五部互联的电梯。基于线程思想,编写一个电梯调度程序。
│ myElevator.exe
│ README.md
│ 电梯调度_设计方案报告.md
│
├─Resources
│ ├─Button
│ │ doordown.png
│ │ doordown_hover.png
│ │ doordown_pressed.png
│ │ doorup.png
│ │ doorup_hover.png
│ │ doorup_pressed.png
│ │ down.png
│ │ down_hover.png
│ │ down_pressed.png
│ │ state.png
│ │ state_down.png
│ │ state_up.png
│ │ up.png
│ │ up_hover.png
│ │ up_pressed.png
│ │
│ ├─Figure
│ │ people.png
│ │
│ └─Icon
│ elevator.ico
│ icon.png
│
└─Src
dispatch.py
myElevator.py
myElevatorInterface.py
myElevator.exe, 进入电梯模拟系统如下图



外部事件:
预期响应:
筛选可以响应的电梯 (没禁用的电梯)
可响应电梯中:
向上顺路:
若某电梯正在向上运动并且用户选择楼层大于该电梯当前楼层 => "可调度性"定义为(用户楼层 - 该电梯当前楼层)
向下顺路:
若某电梯正在向下运动并且用户选择楼层小于该电梯当前楼层 => "可调度性"定义为(该电梯当前楼层 - 用户楼层)
某电梯静止:
若某电梯当前处于静止状态 => "可调度性"定义为(|该电梯当前楼层 - 用户楼层|)
选择可调度性最好的作为最佳电梯


墙模型: QtWidgets.QGraphicsView
电梯模型(包括电梯文字模型):
电梯楼层数码管模型: QtWidgets.QLCDNumber
电梯上下行标志 & 门口按钮: QtWidgets.QGraphicsView | QtWidgets.QPushButton
报警器模型: QtWidgets.QPushButton
楼层按键模型: QtWidgets.QGridLayout | QtWidgets.QWidget | QtWidgets.QPushButton
开关键模型: QtWidgets.QPushButton
下拉框模型: QtWidgets.QComboBox | QtWidgets.QLabel | QtWidgets.QPushButton
小人模型: QtWidgets.QGraphicsView | QPropertyAnimation
OPEN = 0 # 开门状态CLOSED = 1 # 关门状态STANDSTILL = 0 # 静止状态RUNNING_UP = 1 # 电梯上行状态RUNNING_DOWN = 2 # 电梯下行状态NOPE = 0 # 空动画READYSTART = 1 # 电梯即将运动GOUP = 1 # 用户要上行GODOWN = 2 # 用户要下行mywindow类: 界面的显示
class mywindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(mywindow, self).__init__()
self.setupUi(self)
self.setWindowTitle('myElevator')
self.setWindowIcon(QIcon('Resources/Icon/icon.png'))
Ui_MainWindow类: 界面控件设置及其逻辑
class Ui_MainWindow(object):
def __init__(self):
self.Ctrl = Controler(self) # 与调度文件建立连接
self.elevEnabled = [True] * 5 # 电梯状态(可使用/禁用)标志位
self.doorState = [CLOSED] * 5 # 电梯门状态(开门/关门)标志位
self.elevState = [STANDSTILL] * 5 # 电梯状态(运行向上/运行向下/静止)标志位
self.animState = [NOPE] * 5 # 动画播放状态(空/即将运动/即将停止)标志位
self.elevNow = [1] * 5 # 电梯楼层
self.wall = [] # 墙模型
self.elevator_back = [] # 电梯模型
self.elevator_front = []
self.elevator_Anim = []
self.label = []
self.lcdNumber = [] # 数码管模型
self.stateshow = [] # 上下行标志模型
self.updoorbtn = [] # 门口上下行按钮模型
self.downdoorbtn = []
self.warnbtn = [] # 报警器模型
self.gridLayoutWidget = [] # 楼层按键模型
self.gridLayout = []
self.openbtn = [] # 开关键模型
self.closebtn = []
self.figure = [] # 小人模型
self.figure_Anim = []
def setupUi(self, MainWindow):
def retranslateUi(self, MainWindow):
# 报警器槽函数
def warningClick(self):
# 楼层按键槽函数
def btnClick(self):
# 外命令选择槽函数
def chooseClick(self):
# 开关门槽函数
def doorClick(self):
Controler类: 用于电梯的控制及调度
class Controler(object):
def __init__(self, Elev):
# 与界面文件建立连接
self.elev = Elev
# 创建定时器, 1s中更新一次电梯状态
self.timer = QTimer()
self.timer.timeout.connect(self.updateElevState)
self.timer.start(1000)
# 5个电梯内部消息列表(用列表代替队列)
self.messQueue = []
for i in range(0, 5):
self.messQueue.append([])
# 5个电梯内部不顺路消息列表
self.messQueue_reverse = []
for i in range(0, 5):
self.messQueue_reverse.append([])
# 警报器槽函数
def warnCtrl(self, whichelev):
# 开关门槽函数
def doorCtrl(self, whichelev, whichcommand):
# 开门动画
def openDoor_Anim(self, whichelev):
# 关门动画
def closeDoor_Anim(self, whichelev):
# 小人进电梯动画
def figureIn_Anim(self, whichelev):
# 小人出电梯动画
def figureOut_Anim(self, whichelev):
# 将门至于顶层
def setDoorTop(self, whichelev):
# 将小人至于顶层
def setFigureTop(self, whichelev):
# 内命令电梯运动
def elevMove(self, whichelev, dest):
# 外命令电梯调度
def chooseCtrl(self, whichfloor, choice):
# 更新电梯状态
def updateElevState(self):
用户点击某报警器 => 更改其颜色(响应用户) => 弹出警告窗口"第i号电梯已损坏" => 调用控制器进行warnCtrl处理
该电梯状态设置为禁用 => 设置该电梯各部件禁用并停止所有动画
如果所有5部电梯都禁用 => 设置下拉框等外命令空间禁用 => 弹出窗口"所有电梯以损坏"
# 报警器槽函数
def warningClick(self):
which_warnbtn = int(self.sender().objectName()[-1])
print("点击了{0}号报警器".format(which_warnbtn))
self.warnbtn[which_warnbtn].setStyleSheet("background-color: rgb(255, 255, 255);")
self.MessBox = QtWidgets.QMessageBox.information(self.warnbtn[int(which_warnbtn)], "警告", # 弹出警告框
"第" + str(which_warnbtn) + "号电梯已损坏, 不能继续使用")
self.warnbtn[which_warnbtn].setStyleSheet("background-color: rgb(180, 0, 0);")
self.Ctrl.warnCtrl(which_warnbtn) # 调用控制器进行warnCtrl处理
# 警报器槽函数
def warnCtrl(self, whichelev):
self.elev.elevEnabled[whichelev] = False # 该电梯禁用
# 省略电梯各部件禁用(详见Src中dispatch.py)
# 五部电梯全部禁用
arr = np.array(self.elev.elevEnabled)
if ((arr == False).all()):
self.elev.comboBox.setEnabled(False) # 下拉框禁用
self.elev.chooselabel.setEnabled(False) # 文字禁用
self.elev.upbtn.setEnabled(False) # 上行按钮禁用
self.elev.downbtn.setEnabled(False) # 下行按钮禁用
time.sleep(0.5)
self.MessBox = QtWidgets.QMessageBox.information(self.elev, "警告", "所有电梯已损坏!")
用户点击某个楼层按键 => 获取电梯编号 => 获取楼层信息 => 改变按钮背景颜色(模拟点击状态) => 将该按钮设置为不可点击状态 => 调用控制器进行elevMove处理
如果按键楼层大于当前楼层:
STANDSTILL状态 => 将目标楼层加入 消息队列RUNNING_UP状态 => 将目标楼层加入 消息队列并排序RUNNING_DOWN状态 => 将目标楼层加入 不顺路消息队列并排序如果按键楼层小于当前楼层:
STANDSTILL状态 => 将目标楼层加入 消息队列RUNNING_DOWN状态 => 将目标楼层加入 消息队列并反向排序RUNNING_UP状态 => 将目标楼层加入 不顺路消息队列并反向排序如果按键就为当前楼层:
STANDSTILL状态 => 打开门(并等待用户自行关闭)# 楼层按键槽函数
def btnClick(self):
whichbtn = self.sender()
btn_name = whichbtn.objectName()
buf = [int(s) for s in btn_name.split() if s.isdigit()] # 提取字符串中的数字
whichelev = buf[0]
whichfloor = buf[1]
print("{0}号电梯, {1}按键被按".format(whichelev, whichfloor))
whichbtn.setStyleSheet("background-color: rgb(255, 150, 3);") # 改变按钮背景颜色(模拟点击状态)
whichbtn.setEnabled(False) # 将该按钮设置为不可点击状态
self.Ctrl.elevMove(whichelev, whichfloor) # 调用控制器进行elevMove处理
# 内命令电梯运动
def elevMove(self, whichelev, dest):
nowFloor = self.elev.elevNow[whichelev] # 获取当前电梯位置
if nowFloor < dest: # 如果按键大于当前楼层
if self.elev.elevState[whichelev] == STANDSTILL: # 电梯处于静止状态
self.messQueue[whichelev].append(dest) # 将目标楼层加入 消息队列
else:
if self.elev.elevState[whichelev] == RUNNING_UP: # 电梯正在向上运行
self.messQueue[whichelev].append(dest) # 将目标楼层加入 消息队列并排序
self.messQueue[whichelev].sort()
elif self.elev.elevState[whichelev] == RUNNING_DOWN: # 电梯正在向下运行
self.messQueue_reverse[whichelev].append(dest) # 将目标楼层加入 不顺路消息队列并排序
self.messQueue_reverse[whichelev].sort()
elif nowFloor > dest:
if self.elev.elevState[whichelev] == STANDSTILL:
self.messQueue[whichelev].append(dest) # 将目标楼层加入 消息队列
else:
if self.elev.elevState[whichelev] == RUNNING_DOWN:
self.messQueue[whichelev].append(dest) # 将目标楼层加入 消息队列并反向排序
self.messQueue[whichelev].sort()
self.messQueue[whichelev].reverse()
elif self.elev.elevState[whichelev] == RUNNING_UP:
self.messQueue_reverse[whichelev].append(dest) # 将目标楼层加入 不顺路消息队列并反向排序
self.messQueue_reverse[whichelev].sort()
self.messQueue_reverse[whichelev].reverse()
else: # 如果按键就为当前楼层
if self.elev.elevState[whichelev] == STANDSTILL: # 电梯静止 => 打开门(并等待用户自行关闭)
self.elev.doorState[whichelev] = OPEN
self.openDoor_Anim(whichelev)
button = self.elev.findChild(QtWidgets.QPushButton,
"button {0} {1}".format(whichelev, nowFloor)) # 恢复按键背景并重新允许点击
button.setStyleSheet("")
button.setEnabled(True)
用户点击某个开/关门按钮 => 获取电梯编号 => 获取按键信息 => 调用控制器进行doorCtrl处理
如果用户要开门:
CLOSED状态并且电梯处于STANDSTILL状态 => 门的状态改为OPEN => 电梯状态更新为禁用 => 播放开门动画如果用户要关门:
OPEN状态并且电梯处于STANDSTILL状态 => 门的状态改为CLOSED => 电梯状态更新为允许使用 => 将电梯门前的上下行开关恢复 => 播放关门动画# 开关门槽函数
def doorCtrl(self, whichelev, whichcommand):
if whichcommand == 0: # 如果用户要开门
if self.elev.doorState[whichelev] == CLOSED and self.elev.elevState[
whichelev] == STANDSTILL: # 如果当前门是关闭状态并且电梯是静止的
self.elev.doorState[whichelev] = OPEN # 先将门状态更新为打开
self.elev.elevEnabled[whichelev] = False
self.openDoor_Anim(whichelev)
else: # 如果用户要关门
if self.elev.doorState[whichelev] == OPEN and self.elev.elevState[
whichelev] == STANDSTILL: # 如果当前门是打开状态并且电梯是静止的
self.elev.doorState[whichelev] = CLOSED # 先将门状态更新为关闭
self.elev.elevEnabled[whichelev] = True
#将电梯门前的上下行按键熄灭
for i in range(0, 5):
if self.elev.elevEnabled[i]:
self.elev.updoorbtn[i].setStyleSheet("QPushButton{border-image: url(Resources/Button/doorup.png)}"
"QPushButton:hover{border-image: url(Resources/Button/doorup_hover.png)}"
"QPushButton:pressed{border-image: url(Resources/Button/doorup_pressed.png)}")
self.elev.downdoorbtn[i].setStyleSheet("QPushButton{border-image: url(Resources/Button/doordown.png)}"
"QPushButton:hover{border-image: url(Resources/Button/doordown_hover.png)}"
"QPushButton:pressed{border-image: url(Resources/Button/doordown_pressed.png)}")
self.elev.updoorbtn[i].setEnabled(True)
self.elev.downdoorbtn[i].setEnabled(True)
self.closeDoor_Anim(whichelev)
用户选择下拉框数字并点击上/下行开关 => 获取用户选择的楼层 => 获取上/下行并转换为用户选择状态 => 调用控制器进行chooseCtrl处理
# 外命令选择槽函数
def chooseClick(self):
whichfloor = int(self.comboBox.currentText())
whichbtn = self.sender().objectName()
if whichbtn[0] == 'd':
if whichbtn != "downbtn": # 如果是电梯门前的按钮
for i in range(0, len(self.downdoorbtn)):
if self.elevEnabled[i]:
self.downdoorbtn[i].setStyleSheet(
"QPushButton{border-image: url(Resources/Button/doordown_pressed.png)}")
self.downdoorbtn[i].setEnabled(False)
choice = GODOWN
else:
if whichbtn != "upbtn": # 如果是电梯门前的按钮
for i in range(0, len(self.downdoorbtn)):
if self.elevEnabled[i]:
self.updoorbtn[i].setStyleSheet(
"QPushButton{border-image: url(Resources/Button/doorup_pressed.png)}")
self.updoorbtn[i].setEnabled(False)
choice = GOUP
print("用户选择了 {0} {1}".format(whichfloor, choice))
self.Ctrl.chooseCtrl(whichfloor, choice) # 调用控制器进行chooseCtrl处理
初步筛选没损坏的电梯 => 计算每部可用电梯的"可调度性" => 选择可调度性最好的电梯作为最佳电梯
如果最佳电梯就在用户选择的楼层 => 打开门并等待用户自行关闭 => 播放开门动画 => 该电梯设置为禁用
否则 => 加入该最佳电梯的消息队列 => 将用户的目标楼层设定为特殊颜色
可调度性:
RUNNING_UP状态并且用户选择楼层大于该电梯当前楼层 => "可调度性"定义为(用户楼层 - 该电梯当前楼层)RUNNING_DOWN状态并且用户选择楼层小于该电梯当前楼层 => "可调度性"定义为(该电梯当前楼层 - 用户楼层)STANDSTILL状态 => "可调度性"定义为(|该电梯当前楼层 - 用户楼层|)# 外命令电梯调度
def chooseCtrl(self, whichfloor, choice):
# region 初步筛选没损坏的电梯
EnabledList = []
for i in range(0, 5):
if self.elev.elevEnabled[i]:
EnabledList.append(i)
print(EnabledList)
# endregion
# region 计算每部可用电梯的"可调度性"
dist = [INFINITE] * 5 # 可使用电梯距离用户的距离
for EnabledElev in EnabledList:
if self.elev.elevState[EnabledElev] == RUNNING_UP and choice == GOUP and whichfloor > self.elev.elevNow[
EnabledElev]: # 向上顺路
dist[EnabledElev] = whichfloor - self.elev.elevNow[EnabledElev]
elif self.elev.elevState[EnabledElev] == RUNNING_DOWN and choice == GODOWN and whichfloor < \
self.elev.elevNow[EnabledElev]: # 向下顺路
dist[EnabledElev] = self.elev.elevNow[EnabledElev] - whichfloor
elif self.elev.elevState[EnabledElev] == STANDSTILL: # 该电梯此时静止
dist[EnabledElev] = abs(self.elev.elevNow[EnabledElev] - whichfloor)
# endregion
BestElev = dist.index(min(dist)) # 选择可调度性最好的电梯作为最佳电梯
if dist[BestElev] == 0: # 如果最佳电梯就在用户选择的楼层
self.elev.doorState[BestElev] = OPEN # 打开门并等待用户自行关闭
self.openDoor_Anim(BestElev)
else:
self.messQueue[BestElev].append(whichfloor) # 加入该最佳电梯的消息队列
button = self.elev.findChild(QtWidgets.QPushButton,
"button {0} {1}".format(BestElev, whichfloor)) # 将用户的目标楼层设定为特殊颜色
button.setStyleSheet("background-color: rgb(11, 15, 255);")
button.setEnabled(False)
采用定时器(1s调用一次方法更新电梯状态)
# 创建定时器, 1s中更新一次电梯状态
self.timer = QTimer()
self.timer.timeout.connect(self.updateElevState)
self.timer.start(1000)
遍历五部电梯
某个电梯的消息队列不为空
STANDSTILL状态 => 播放开门动画 => 播放小人进门动画 => 根据即将运行的方向更新电梯状态 => 动画变为READYSTART状态READYSTART状态 => 播放关门动画 => 动画变为NOPE状态READYSTOP状态 => 结束该命令的处理 => 动画变为NOPE状态 => 电梯变为STANDSTILL状态RUNNING_UP => 显示向上运行图标 => 将当前楼层加一并设置数码管显示RUNNING_DOWN => 显示向下运行图标 => 将当前楼层减一并设置数码管显示READYSTOP状态 => 将该楼层按钮恢复到原始状态某个电梯的消息队列为空 & 不顺路消息队列不为空 => 交换两个队列
遍历五部电梯, 在运行过程中禁止点击报警键:
STANDSTILL状态 => 允许使用报警键# 更新电梯状态
def updateElevState(self):
# print('timer clock......')
for i in range(0, len(self.messQueue)): # 遍历五部电梯
if len(self.messQueue[i]): # 某个电梯的消息队列不为空
if self.elev.doorState[i] == OPEN: # 如果电梯门是打开的 => 等待电梯关门
continue
elif self.elev.elevState[i] == STANDSTILL: # 电梯处于静止状态
self.openDoor_Anim(i)
self.figureIn_Anim(i)
if self.elev.elevNow[i] < self.messQueue[i][0]: # 根据即将运行的方向更新电梯状态
self.elev.elevState[i] = RUNNING_UP
elif self.elev.elevNow[i] > self.messQueue[i][0]:
self.elev.elevState[i] = RUNNING_DOWN
self.elev.animState[i] = READYSTART # 动画变为就绪运行状态
elif self.elev.animState[i] == READYSTART: # 动画处于就绪运行状态
self.closeDoor_Anim(i)
self.elev.animState[i] = NOPE # 动画变为空状态
elif self.elev.animState[i] == READYSTOP: # 动画处于就绪停止状态
self.messQueue[i].pop(0) # 结束该命令的处理
self.closeDoor_Anim(i)
self.elev.animState[i] = NOPE # 动画变为空状态
self.elev.elevState[i] = STANDSTILL # 电梯变为静止状态
self.elev.stateshow[i].setStyleSheet("QGraphicsView{border-image: url(Resources/Button/state.png)}")
else:
destFloor = self.messQueue[i][0] # 获取第一个目标楼层
if self.elev.elevNow[i] < destFloor: # 向上运动
self.elev.elevState[i] = RUNNING_UP
self.elev.stateshow[i].setStyleSheet(
"QGraphicsView{border-image: url(Resources/Button/state_up.png)}")
self.elev.elevNow[i] = self.elev.elevNow[i] + 1 # 将当前楼层加一并设置数码管显示
self.elev.lcdNumber[i].setProperty("value", self.elev.elevNow[i])
elif self.elev.elevNow[i] > destFloor: # 向下运动
self.elev.elevState[i] = RUNNING_DOWN
self.elev.stateshow[i].setStyleSheet(
"QGraphicsView{border-image: url(Resources/Button/state_down.png)}")
self.elev.elevNow[i] = self.elev.elevNow[i] - 1 # 将当前楼层减一并设置数码管显示
self.elev.lcdNumber[i].setProperty("value", self.elev.elevNow[i])
else: # 电梯到达目的地
self.openDoor_Anim(i)
self.figureOut_Anim(i)
self.elev.animState[i] = READYSTOP # 到达目的地 => 动画变为就绪停止状态
button = self.elev.findChild(QtWidgets.QPushButton,
"button {0} {1}".format(i, self.elev.elevNow[i])) # 恢复该按钮的状态
button.setStyleSheet("")
button.setEnabled(True)
elif len(self.messQueue_reverse[i]): # 如果消息队列为空 & 不顺路消息队列不为空
self.messQueue[i] = self.messQueue_reverse[i].copy() # 交替两个队列
self.messQueue_reverse[i].clear()
# 电梯在运行过程中禁止点击报警键
for i in range(0, 5):
if self.elev.gridLayoutWidget[i].isEnabled(): # 如果这个电梯没被禁用
if self.elev.elevState[i] == STANDSTILL: # 如果电梯是静止的
self.elev.warnbtn[i].setEnabled(True)
else:
self.elev.warnbtn[i].setEnabled(False)
# 开门动画
def openDoor_Anim(self, whichelev):
self.elev.elevator_Anim[2 * whichelev].setDirection(QAbstractAnimation.Forward) # 正向设定动画
self.elev.elevator_Anim[2 * whichelev + 1].setDirection(QAbstractAnimation.Forward)
self.elev.elevator_Anim[2 * whichelev].start() # 开始播放
self.elev.elevator_Anim[2 * whichelev + 1].start()
# 关门动画
def closeDoor_Anim(self, whichelev):
self.elev.elevator_Anim[2 * whichelev].setDirection(QAbstractAnimation.Backward) # 反向设定动画
self.elev.elevator_Anim[2 * whichelev + 1].setDirection(QAbstractAnimation.Backward)
self.elev.elevator_Anim[2 * whichelev].start() # 开始播放
self.elev.elevator_Anim[2 * whichelev + 1].start()
# 小人进电梯动画
def figureIn_Anim(self, whichelev):
self.elev.figure[whichelev].setVisible(True)
self.elev.figure_Anim[whichelev].setDirection(QAbstractAnimation.Forward)
self.elev.figure_Anim[whichelev].start()
s = threading.Timer(1.5, self.setDoorTop, (whichelev,)) # 1.5秒之后把门至于顶层
s.start()
# 小人出电梯动画
def figureOut_Anim(self, whichelev):
self.elev.figure[whichelev].setVisible(True)
self.elev.figure_Anim[whichelev].setDirection(QAbstractAnimation.Backward)
self.elev.figure_Anim[whichelev].start()
s = threading.Timer(1, self.setFigureTop, (whichelev,)) # 1s之后将人至于顶层
s.start()
# 将门至于顶层
def setDoorTop(self, whichelev):
self.elev.elevator_front[2 * whichelev].raise_()
self.elev.elevator_front[2 * whichelev + 1].raise_()
# 将小人至于顶层
def setFigureTop(self, whichelev):
self.elev.figure[whichelev].raise_()
self.elev.figure[whichelev].setVisible(False)





