11 sys.path.append(os.path.dirname(os.path.realpath(__file__)))
14 from PySide2.QtWidgets
import QApplication, QMainWindow, QLineEdit, QVBoxLayout, QMenu, QListWidgetItem,\
15 QDialog, QDialogButtonBox, QLabel, QPushButton, QSlider, QListWidget, QMessageBox
16 from PySide2.QtGui
import QIcon, QBrush
17 from PySide2.QtCore
import QRect, Qt, QMutex, QThread, Signal
18 from PySide2.QtUiTools
import QUiLoader
19 from Communication
import MessageReception, MessageTransmission, initSerialConnection, scanAvailablePorts
20 from Drawer
import Drawer
21 from collections
import deque
34 warnings.filterwarnings(
"ignore",
"an integer is required", DeprecationWarning)
37 os.chdir(os.path.dirname(os.path.realpath(__file__)))
44 Tread to send the different positions to the motors during a sequence 46 def __init__(self,moves,motors, listOfSequencesHandler, mainWindow):
48 Initialization of the play sequence thread 49 :param moves: the moves of the sequence containing the different positions to reach 50 :param motors: the motors of the robot 51 :param listOfSequencesHandler: the handler of the different sequences 52 :param mainWindow: the main window of the UI 65 self.__mainWindow.app.aboutToQuit.connect(self.
close)
68 self.finished.connect(self.
stop)
73 self.
__motors[motor].setGoalPosition(move.getMotorPosition(motor))
75 while self.
__motors[motor].getCurrentPosition() < self.
__motors[motor].getGoalPosition() - 10 \
76 or self.
__motors[motor].getCurrentPosition() > self.
__motors[motor].getGoalPosition() + 10:
77 if not self.__mainWindow.shouldStop:
80 self.
__motors[motor].setGoalPosition(move.getMotorPosition(motor))
88 Handles the different events when the thread is closed 91 self.__listOfSequencesHandler.robotInMotionMessage.accept()
92 self.__listOfSequencesHandler.enableUi()
93 self.__mainWindow.shouldStop =
False 97 To get out of the while loop 100 self.__mainWindow.shouldStop =
True 105 To load the saved sequence in the SaveSequence.json file and put them in the list of sequence 106 :param listOfSequenceHandler: To add the sequences to the list 107 :param motors: the motors of the robotic arm 111 with open(
'SaveSequence.json')
as save:
112 savedListOfSequences = json.load(save)
113 for sequence
in savedListOfSequences:
114 for sequenceName
in sequence:
115 savedSequence =
Sequence(motors,sequenceName)
116 for move
in sequence[sequenceName]:
117 savedMove =
Move(motors)
119 savedMove.setMotorPosition(motor, move[motor])
120 savedSequence.addMove(savedMove)
121 listOfSequenceHandler.addItem(savedSequence)
122 except FileNotFoundError:
123 print(
"Save file not found")
127 Handler for the list of sequences 131 Initializtion of the handler for the list of sequences 132 :param ui: The ui in which the list of sequence is in 133 :param motors: The dictionary of all the motors 139 self.__listOfSequences.itemDoubleClicked.connect(self.
playSequence)
153 self.robotInMotionMessage.setIcon(QMessageBox.Information)
154 self.robotInMotionMessage.setWindowIcon(QIcon(icon))
155 self.robotInMotionMessage.setText(
"Robot is in motion")
156 self.robotInMotionMessage.addButton(QMessageBox.Abort)
157 self.robotInMotionMessage.rejected.connect(self.__mainWindow.stopMotors)
158 self.robotInMotionMessage.rejected.connect(self.
enableUi)
161 self.__createSequenceButton.clicked.connect(self.
createWindow)
164 self.__listOfSequences.customContextMenuRequested.connect(self.
showMenu)
168 Add an item to the list of sequence 169 :param item: The QListWidgetItem sequence to add 172 self.__listOfSequences.addItem(item)
176 Removes the selected item in the list 179 listItems = self.__listOfSequences.selectedItems()
180 if not listItems:
return 181 for item
in listItems:
182 self.__listOfSequences.takeItem(self.__listOfSequences.row(item))
184 with open(
'SaveSequence.json',
'r') as save_file: 185 savedListOfSequences = json.load(save_file) 188 for sequence
in range(len(savedListOfSequences)):
189 for sequenceName
in savedListOfSequences[sequence]:
190 if sequenceName == item.getName():
192 indexSequence = sequence
193 if indexSequence != -1:
194 savedListOfSequences.pop(indexSequence)
197 with open(
'SaveSequence.json',
'w')
as save_file:
198 json.dump(savedListOfSequences, save_file)
202 Create the window to create a new sequence 203 :param modifySequence: bool, if true there's a selected sequence that needs to be modified if false 211 self.__ui.setEnabled(
False)
213 self.__window.setGeometry(QRect(150, 150, 600, 400))
218 The right click menu of the sequence of the list 219 :param event: The event (here right click) that makes the menu come up 223 menu.addAction(
"Modify Sequence",
lambda: self.
createWindow(
True))
226 menu.exec_(self.__listOfSequences.mapToGlobal(event))
233 self.__ui.setEnabled(
True)
237 Accessor to the selected items of the list 238 :return: the selected items 241 return self.__listOfSequences.selectedItems()
245 self.__ui.setEnabled(
False)
247 self.playSequenceThread.start()
248 self.robotInMotionMessage.exec_()
252 Window for creating a new sequence 254 def __init__(self, motors={}, listOfSequenceHandler = None, sequence = None, modifySequence = False):
256 Initializtion of the window for creating a new sequence 257 :param motors: The dictionary of all the motors 258 :param listOfSequenceHandler: The handler of the list of sequence 260 QDialog.__init__(self)
262 appIcon = QIcon(icon)
263 self.setWindowIcon(appIcon)
279 self.nameEntry.setText(self.__sequence.getName())
285 for move
in self.__sequence.getMoves():
287 labelText =
"move " + str(moveNumber) +
": " 290 labelText += self.
__motors[motor].getName() +
" " + \
291 str(move.getMotorPosition(self.
__motors[motor].getName())) +
", " 295 self.__listOfMoveLabels.insertItem(0, label)
300 dictOfSlider = dict()
303 dictOfSlider[motor] = QSlider(Qt.Horizontal)
304 dictOfSlider[motor].setMaximum(4095)
305 dictOfSlider[motor].setValue(self.
__motors[motor].getCurrentPosition())
306 dictOfSlider[motor].sliderMoved.connect(self.
__motors[motor].setGoalPosition)
307 self.listOfSliders.append(dictOfSlider[motor])
311 self.__noNameMessage.setIcon(QMessageBox.Warning)
312 self.__noNameMessage.setWindowIcon(appIcon)
313 self.__noNameMessage.setText(
"Please name your sequence before saving it")
314 self.__noNameMessage.setStandardButtons(QMessageBox.Ok)
316 self.__noNameMessage.accepted.connect(self.
enableWindow)
320 self.__warningMessage.setIcon(QMessageBox.Warning)
321 self.__warningMessage.setWindowIcon(appIcon)
322 self.__warningMessage.setText(
"Are you sure you want to close this window? Your sequence will not be saved")
323 self.__warningMessage.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
325 self.__warningMessage.accepted.connect(self.reject)
327 self.__warningMessage.rejected.connect(self.
enableWindow)
332 for motorNumber
in range(0,len(motors)):
333 self.__motorLabels.append(QLabel(
"Motor " + str(motorNumber+1) +
" position"))
340 self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
344 self.buttonBox.rejected.connect(self.__warningMessage.exec)
347 self.rejected.connect(self.__listOfSequenceHandler.enableUi)
348 self.accepted.connect(self.__listOfSequenceHandler.enableUi)
358 for motorNumber
in range(len(self.
__motors)):
365 self.__listOfMoveLabels.setContextMenuPolicy(Qt.CustomContextMenu)
366 self.__listOfMoveLabels.customContextMenuRequested.connect(self.
rightClickMenu)
370 Sets the name of the sequence with the user input 371 :param name: The name of the sequence 374 self.__sequence.setName(name)
378 Accessor of the sequence (for tests) 379 :return: the sequence 385 Accessor of the list of move labels (for tests) 386 :return: the list of move labels 392 Add the sequence to the list of sequence 393 :param modifySequence: bool, if true there's a selected sequence that needs to be modified if false 399 self.
setName(self.nameEntry.text())
400 if self.__sequence.getName() !=
"":
403 with open(
'SaveSequence.json')
as save:
404 savedListOfSequences = json.load(save)
405 except FileNotFoundError:
406 savedListOfSequences = []
412 for sequence
in savedListOfSequences:
413 if self.__sequence.getName()
in sequence:
414 indexOfTheSequence = savedListOfSequences.index(sequence)
416 savedListOfSequences.remove(sequence)
417 self.__listOfSequenceHandler.addItem(self.
__sequence)
420 newSequence[self.__sequence.getName()] = []
421 for moveNumber
in range(len(self.__sequence.getMoves())):
422 newSequence[self.__sequence.getName()].append(
423 self.__sequence.getMoves()[moveNumber].getMovePositions())
426 savedListOfSequences.insert(indexOfTheSequence,newSequence)
429 with open(
'SaveSequence.json',
'w')
as outfile:
430 json.dump(savedListOfSequences, outfile)
434 self.__listOfSequenceHandler.addItem(self.
__sequence)
437 newSequence[self.__sequence.getName()] = []
438 for moveNumber
in range(len(self.__sequence.getMoves())):
439 newSequence[self.__sequence.getName()].append(
440 self.__sequence.getMoves()[moveNumber].getMovePositions())
443 savedListOfSequences.append(newSequence)
446 with open(
'SaveSequence.json',
'w')
as outfile:
447 json.dump(savedListOfSequences, outfile)
451 self.setEnabled(
False)
452 self.__noNameMessage.exec_()
456 Add the last move to the sequence 462 for row
in range(self.__listOfMoveLabels.count()):
463 if not self.__listOfMoveLabels.item(row).getMove().isNew:
464 move = self.__listOfMoveLabels.item(row).getMove()
465 labelToModify = self.__listOfMoveLabels.item(row)
473 move.setMotorPosition(motorName, self.
listOfSliders[i].value())
475 self.__sequence.addMove(move)
478 labelText =
"move " + str(self.__sequence.getNumberofMoves()) +
": " 481 labelText += self.
__motors[motor].getName() +
" " +\
487 self.__listOfMoveLabels.insertItem(0, label)
492 move.setMotorPosition(motorName, self.
listOfSliders[i].value())
496 textToEdit = labelToModify.text()
497 listOfTextToEdit = textToEdit.split(
' ')
498 labelText = listOfTextToEdit[0] +
" " + listOfTextToEdit[1] +
" " 501 labelText += self.
__motors[motor].getName() +
" " + \
504 labelToModify.setText(labelText)
505 labelToModify.setSelected(
False)
506 labelToModify.setBackground(Qt.white)
515 Called when a move in the sequence is double clicked 516 :param moveItem: the move that was double clicked 519 moveItem.goToMoveOfTheLabel()
530 Enable the create sequence window 533 self.setEnabled(
True)
537 The right click menu of the move list 538 :param event: The event (here right click) that makes the menu come up 543 menu.addAction(
"Modify Move",
lambda: self.
modifyMove(self.__listOfMoveLabels.selectedItems()[0]))
545 menu.addAction(
"Delete Move",
lambda: self.
deleteMove(self.__listOfMoveLabels.selectedItems()[0]))
546 menu.exec_(self.__listOfMoveLabels.mapToGlobal(event))
550 Delete a move and its label of the sequence 551 :param label: label of the move 555 self.__listOfMoveLabels.takeItem(self.__listOfMoveLabels.row(label))
557 self.__sequence.deleteMove(label.getMove())
560 for index
in range(self.__sequence.getNumberofMoves()-1,-1,-1):
561 labelToModify = self.__listOfMoveLabels.item(index)
562 textToEdit = labelToModify.text()
563 listOfTextToEdit = textToEdit.split(
' ')
564 listOfTextToEdit[1] = str(self.__sequence.getNumberofMoves()-index) +
':' 565 textToEdit =
' '.join(listOfTextToEdit)
566 self.__listOfMoveLabels.item(index).setText(textToEdit)
570 Put a move to a modified state 571 :param label: label of the move 575 for row
in range(self.__listOfMoveLabels.count()):
576 if not self.__listOfMoveLabels.item(row).getMove().isNew:
577 self.__listOfMoveLabels.item(row).getMove().isNew =
True 578 self.__listOfMoveLabels.item(row).setBackground(QBrush(Qt.white))
580 moveToModify = label.getMove()
581 moveToModify.isNew =
False 582 label.setBackground(QBrush(Qt.darkCyan))
583 label.goToMoveOfTheLabel()
588 Class for the custom labels that are stored in the move list in the sequence creator 590 def __init__(self, move = None, text = None, motors = {}, parent=None):
592 Initialization of the move label 593 :param move: The move 594 :param text: The text in the label 595 :param motors: Dictionary of all the motors 596 :param parent: The Qt parent 598 QListWidgetItem.__init__(self, parent)
607 Handles the event when the move lable is double clicked 610 self.__move.goToMove()
615 Accessor of the move of the label 616 :return: the move object 623 Sequence is a series of moves that will be executed in a specific order 626 QListWidgetItem.__init__(self)
638 Sets the name of the sequence 639 :param name: The name 647 Accesor for the sequence name 648 :return: The name of the sequence 654 Adds a move to the list in the sequence 655 :param newMove: The move to add to the list 658 self.__moves.append(newMove)
662 :return: The number of moves in the sequence 668 Acessor of the moves of the sequence 669 :return: The moves of the sequence 675 Delete a specific move of the sequence 676 :param move: the move to be deleted 679 self.__moves.remove(move)
684 A move contains all the position of all the motors for a specific point in space 699 Setter of the position of a motor in the move 700 :param motorName: The name of the motor we want to set the positio 701 :param position: The position of the motor 702 :return: Return None if succesful and an error string if not 708 return "There's no motor named that way" 712 Accessor for the positions in the move 713 :param motorName: The name of the motor 714 :return: Return the position if succesful and an error string if not 719 return "There's no motor named that way" 730 Class for a motor which has a position, a name and a status 732 def __init__(self, mainWindow=None, name="", goalPosition=0, status=False):
735 :param mainWindow: The main window of the ui 736 :param name: The name of the motor 737 :param pos: The position of the motor 738 :param status: the status of the motor 754 Setter of the goal positon 755 :param pos: the position 761 print(
"%s: %d" % (self.
__name, pos))
762 self.__window.sendMessage(
'a')
766 Accessor of the goal position 767 :return: The goal position of the motor 776 Setter of the current position 785 Accessor of the current position 786 :return: The current position of the motor 795 Setter of the name of the motor 796 :param name: The name of the motor 804 :return: The name of the motor 810 Setter of the status of the motor 811 :param status: The status 818 Accessor of the status 830 MainWindow initialization 836 self.
ui = QUiLoader().load(
"mainwindow.ui")
838 self.ui.setWindowTitle(
"RoboAide")
839 self.setMinimumHeight(100)
840 self.setMinimumWidth(250)
859 mot =
Motor(self,
"motor" + str(i))
860 self.
dictMot[mot.getName()] = mot
866 self.drawersList.append(Drawer(self,
"drawer" + str(i+1)))
877 self.ui.slider_mot1.sliderMoved.connect(
878 lambda: self.
dictMot[
"motor1"].setGoalPosition(self.ui.slider_mot1.value()))
879 self.ui.slider_mot2.sliderMoved.connect(
880 lambda: self.
dictMot[
"motor2"].setGoalPosition(self.ui.slider_mot2.value()))
881 self.ui.slider_mot3.sliderMoved.connect(
882 lambda: self.
dictMot[
"motor3"].setGoalPosition(self.ui.slider_mot3.value()))
883 self.ui.slider_mot4.sliderMoved.connect(
884 lambda: self.
dictMot[
"motor4"].setGoalPosition(self.ui.slider_mot4.value()))
885 self.ui.slider_mot5.sliderMoved.connect(
886 lambda: self.
dictMot[
"motor5"].setGoalPosition(self.ui.slider_mot5.value()))
887 self.ui.slider_mot6.sliderMoved.connect(
888 lambda: self.
dictMot[
"motor6"].setGoalPosition(self.ui.slider_mot6.value()))
894 self.ui.drawer1Open.clicked.connect(self.
drawersList[0].open)
895 self.ui.drawer2Open.clicked.connect(self.
drawersList[1].open)
896 self.ui.drawer3Open.clicked.connect(self.
drawersList[2].open)
897 self.ui.drawer1Close.clicked.connect(self.
drawersList[0].close)
898 self.ui.drawer2Close.clicked.connect(self.
drawersList[1].close)
899 self.ui.drawer3Close.clicked.connect(self.
drawersList[2].close)
913 self.app.aboutToQuit.connect(self.msgReception.stop)
914 self.app.aboutToQuit.connect(self.msgTransmission.stop)
918 with open(
'SavePort.json')
as save:
919 savedPort = json.load(save)
922 self.ui.portselection.setCurrentIndex(index)
925 print(
"The last port is not available")
926 except FileNotFoundError:
927 print(
"SavePort file not found")
928 self.ui.portselection.currentIndexChanged.connect(self.
connect_port)
932 Connect the selected port of the controller 933 :param: lastPort: name of the port that was last used 936 if isinstance(lastPort, str):
939 commPort = self.ui.portselection.currentText()
941 if self.
comm is not None:
942 self.app.aboutToQuit.connect(self.comm.close)
943 self.msgReception.start()
944 self.msgTransmission.start()
952 appIcon = QIcon(icon)
953 self.ui.setWindowIcon(appIcon)
957 Update motor slider positions 961 print(
"Updating motor slider positions")
962 self.ui.slider_mot1.setValue(self.
dictMot[
"motor1"].getCurrentPosition())
963 self.ui.slider_mot2.setValue(self.
dictMot[
"motor2"].getCurrentPosition())
964 self.ui.slider_mot3.setValue(self.
dictMot[
"motor3"].getCurrentPosition())
965 self.ui.slider_mot4.setValue(self.
dictMot[
"motor4"].getCurrentPosition())
966 self.ui.slider_mot5.setValue(self.
dictMot[
"motor5"].getCurrentPosition())
967 self.ui.slider_mot6.setValue(self.
dictMot[
"motor6"].getCurrentPosition())
968 print(
"Finished initializing slider positions")
972 Populate the available serial ports in the drop down menu 975 print(
"Scanning and populating list of available serial ports")
977 result = isinstance(self.
ports_list[index], str)
979 self.ui.portselection.addItem(self.
ports_list[index].device)
981 self.ui.portselection.addItem(self.
ports_list[index])
985 Package message and send on communication port 986 :param mode: mode in which the message should be interpreted by the controller 990 values = (mode.encode(),
991 self.
dictMot[
"motor1"].getGoalPosition(),
992 self.
dictMot[
"motor2"].getGoalPosition(),
993 self.
dictMot[
"motor3"].getGoalPosition(),
994 self.
dictMot[
"motor4"].getGoalPosition(),
995 self.
dictMot[
"motor5"].getGoalPosition(),
996 self.
dictMot[
"motor6"].getGoalPosition(),
1002 print(
"Outgoing: ", end=
'')
1004 packed_data = self.s.pack(*values)
1006 self.msgDeque.append(packed_data)
1009 print(
"Error sending message, serial not connected")
1013 Trigger vertical axis calibration 1016 print(
"Calibrating vertical axis")
1021 Signal all motors to stop and empty message deque 1024 self.msgDeque.clear()
1030 Make the struct and return it along with it's size in bytes 1031 :return: struct object, byte size of the struct 1045 structDefinition =
'c6H?3?c' 1046 s = struct.Struct(structDefinition)
1047 return s, struct.calcsize(structDefinition)
1051 if __name__ ==
"__main__":
1052 app = QApplication(sys.argv)
dictMot
Dictionnary of all motor objects.
listOfSequencesHandler
ListOfSequencesHandler object.
def loadSequences(listOfSequenceHandler, motors)
__window
The window in which the new sequence will be created.
def deleteMove(self, move)
def getListofMoveLabels(self)
__sequence
The new sequence.
def __init__(self, move=None, text=None, motors={}, parent=None)
def goToMoveOfTheLabel(self)
def connect_port(self, lastPort=None)
def __init__(self, motors={}, name="")
__createSequenceButton
The create a new sequence button.
__nameLabel
The label for the widget in which the name of the sequence is written.
msgReception
Message reception QThread object.
__movePositions
To store the different positions of the move.
isNew
State of the move (modified or new)
__layout
The layout of the create sequence window.
def updateSliderPositions(self, index=0)
def getSelectedItems(self)
def showMenu(self, event)
def setGoalPosition(self, pos)
def getMovePositions(self)
__motors
The motors of the robot.
__window
The main window of the ui.
def __init__(self, mainWindow, motors)
__ui
The ui in which the list of sequence is in.
def rightClickMenu(self, event)
__motors
The dictionary of all the motors.
__motors
A dictionary of all the motors.
listOfSliders
List of sliders in the create sequence window.
playSequenceThread
Thread to send the sequence to the motors.
__listOfSequencesHandler
The handler of the different sequences.
__mainWindow
The main window of the ui.
def removeSelectedItem(self)
robotInMotionMessage
Message to make the user put a name to the sequence TODO: Set the size of the message to something ap...
def modifyMove(self, label)
__warningMessage
Warning message to make sure the user doen't want to save the sequence.
__listOfMoveLabels
The list of the different moves that forms the sequence.
__mainWindow
The main window of the UI.
__modifySequence
Flag if the sequence is a modified one or a new one.
def addMove(self, newMove)
def calibrateVerticalAxis(self)
__noNameMessage
Message to make the user put a name to the sequence.
__name
The main window of the ui.
msgTransmission
Message transmission QThread object.
def addSequenceToList(self, modifySequence=False)
def setCurrentPosition(self, pos)
__listOfSequenceHandler
The handler of the list of sequence.
def addMovetoSequence(self)
__goalPosition
The goal position of the motor.
drawersList
List of drawers.
buttonBox
Buttons to accept or cancel the creation of a sequence.
def updateSlidersPositions(self)
nextMoveButton
Button to add a move to the sequence and procede to the next move.
def deleteMove(self, label)
__currentPosition
The current position of the motor.
def getGoalPosition(self)
def __init__(self, motors={})
__status
The status of the motor.
def getCurrentPosition(self)
__motorLabels
Labels for the motors in the UI.
def getNumberofMoves(self)
__wantedPositions
A dictionary of the positions of the new sequence.
nameEntry
The widget for the name of the sequenc.
def __init__(self, mainWindow=None, name="", goalPosition=0, status=False)
def populatePortsList(self)
def initSerialConnection(port)
def sendMessage(self, mode)
def __init__(self, motors={}, listOfSequenceHandler=None, sequence=None, modifySequence=False)
__motors
The dictionary of all the motors.
__motors
The dictionary of all the motors.
def moveDoubleClicked(self, moveItem)
def setMotorPosition(self, motorName, position)
def createWindow(self, modifySequence=False)
def __init__(self, moves, motors, listOfSequencesHandler, mainWindow)
def setStatus(self, status)
shouldStop
Stop indicator for the motors.
__moves
The moves of the sequence containing the different positions to reach.
msgDeque
Outgoing message deque.
__motors
A dictionary of all motors.
__name
The name of the sequence.
__move
The move for the label.
__listOfSequences
The list of sequence.
def getMotorPosition(self, motorName)