Linux PyQt tray application to control OctoPrint instances
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CamWindow.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #!/usr/bin/env python3
  2. # OctoTray Linux Qt System Tray OctoPrint client
  3. #
  4. # CamWindow.py
  5. #
  6. # see also:
  7. # https://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-example.html
  8. # https://stackoverflow.com/a/22618496
  9. import time
  10. from PyQt5 import QtNetwork
  11. from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QSlider, QPushButton
  12. from PyQt5.QtGui import QPixmap, QImageReader
  13. from PyQt5.QtCore import QUrl, QTimer, Qt
  14. from AspectRatioPixmapLabel import AspectRatioPixmapLabel
  15. class CamWindow(QWidget):
  16. reloadDelayDefault = 1000 # in ms
  17. statusDelayFactor = 2
  18. reloadOn = True
  19. sliderFactor = 100
  20. def __init__(self, parent, printer, *args, **kwargs):
  21. super(CamWindow, self).__init__(*args, **kwargs)
  22. self.app = parent.app
  23. self.manager = parent.manager
  24. self.manager.finished.connect(self.handleResponse)
  25. self.parent = parent
  26. self.printer = printer
  27. self.host = self.printer[0]
  28. self.url = "http://" + self.host + ":8080/?action=snapshot"
  29. self.setWindowTitle(parent.name + " Webcam Stream")
  30. self.setWindowIcon(parent.icon)
  31. box = QVBoxLayout()
  32. self.setLayout(box)
  33. label = QLabel(self.url)
  34. box.addWidget(label, 0)
  35. box.setAlignment(label, Qt.AlignHCenter)
  36. slide = QHBoxLayout()
  37. box.addLayout(slide, 0)
  38. self.slideStaticLabel = QLabel("Refresh")
  39. slide.addWidget(self.slideStaticLabel, 0)
  40. self.slider = QSlider(Qt.Horizontal)
  41. self.slider.setMinimum(int(100 / self.sliderFactor))
  42. self.slider.setMaximum(int(2000 / self.sliderFactor))
  43. self.slider.setTickInterval(int(100 / self.sliderFactor))
  44. self.slider.setPageStep(int(100 / self.sliderFactor))
  45. self.slider.setSingleStep(int(100 / self.sliderFactor))
  46. self.slider.setTickPosition(QSlider.TicksBelow)
  47. self.slider.setValue(int(self.reloadDelayDefault / self.sliderFactor))
  48. self.slider.valueChanged.connect(self.sliderChanged)
  49. slide.addWidget(self.slider, 1)
  50. self.slideLabel = QLabel(str(self.reloadDelayDefault) + "ms")
  51. slide.addWidget(self.slideLabel, 0)
  52. self.img = AspectRatioPixmapLabel()
  53. self.img.setPixmap(QPixmap(640, 480))
  54. box.addWidget(self.img, 1)
  55. self.statusLabel = QLabel("Status: unavailable")
  56. box.addWidget(self.statusLabel, 0)
  57. box.setAlignment(self.statusLabel, Qt.AlignHCenter)
  58. self.method = self.parent.getMethod(self.printer[0], self.printer[1])
  59. if self.method != "unknown":
  60. controls_power = QHBoxLayout()
  61. box.addLayout(controls_power, 0)
  62. self.turnOnButton = QPushButton("Turn O&n")
  63. self.turnOnButton.clicked.connect(self.turnOn)
  64. controls_power.addWidget(self.turnOnButton)
  65. self.turnOffButton = QPushButton("Turn O&ff")
  66. self.turnOffButton.clicked.connect(self.turnOff)
  67. controls_power.addWidget(self.turnOffButton)
  68. controls_temp = QHBoxLayout()
  69. box.addLayout(controls_temp, 0)
  70. self.cooldownButton = QPushButton("&Cooldown")
  71. self.cooldownButton.clicked.connect(self.cooldown)
  72. controls_temp.addWidget(self.cooldownButton)
  73. self.preheatToolButton = QPushButton("Preheat &Tool")
  74. self.preheatToolButton.clicked.connect(self.preheatTool)
  75. controls_temp.addWidget(self.preheatToolButton)
  76. self.preheatBedButton = QPushButton("Preheat &Bed")
  77. self.preheatBedButton.clicked.connect(self.preheatBed)
  78. controls_temp.addWidget(self.preheatBedButton)
  79. controls_home = QHBoxLayout()
  80. box.addLayout(controls_home, 0)
  81. self.homeAllButton = QPushButton("Home &All")
  82. self.homeAllButton.clicked.connect(self.homeAll)
  83. controls_home.addWidget(self.homeAllButton, 1)
  84. self.homeXButton = QPushButton("Home &X")
  85. self.homeXButton.clicked.connect(self.homeX)
  86. controls_home.addWidget(self.homeXButton, 0)
  87. self.homeYButton = QPushButton("Home &Y")
  88. self.homeYButton.clicked.connect(self.homeY)
  89. controls_home.addWidget(self.homeYButton, 0)
  90. self.homeZButton = QPushButton("Home &Z")
  91. self.homeZButton.clicked.connect(self.homeZ)
  92. controls_home.addWidget(self.homeZButton, 0)
  93. controls_move = QHBoxLayout()
  94. box.addLayout(controls_move, 0)
  95. self.XPButton = QPushButton("X+")
  96. self.XPButton.clicked.connect(self.moveXP)
  97. controls_move.addWidget(self.XPButton)
  98. self.XMButton = QPushButton("X-")
  99. self.XMButton.clicked.connect(self.moveXM)
  100. controls_move.addWidget(self.XMButton)
  101. self.YPButton = QPushButton("Y+")
  102. self.YPButton.clicked.connect(self.moveYP)
  103. controls_move.addWidget(self.YPButton)
  104. self.YMButton = QPushButton("Y-")
  105. self.YMButton.clicked.connect(self.moveYM)
  106. controls_move.addWidget(self.YMButton)
  107. self.ZPButton = QPushButton("Z+")
  108. self.ZPButton.clicked.connect(self.moveZP)
  109. controls_move.addWidget(self.ZPButton)
  110. self.ZMButton = QPushButton("Z-")
  111. self.ZMButton.clicked.connect(self.moveZM)
  112. controls_move.addWidget(self.ZMButton)
  113. controls_job = QHBoxLayout()
  114. box.addLayout(controls_job, 0)
  115. self.PauseButton = QPushButton("Pause/Resume")
  116. self.PauseButton.clicked.connect(self.pauseResume)
  117. controls_job.addWidget(self.PauseButton)
  118. self.CancelButton = QPushButton("Cancel Job")
  119. self.CancelButton.clicked.connect(self.cancelJob)
  120. controls_job.addWidget(self.CancelButton)
  121. self.loadImage()
  122. self.loadStatus()
  123. def pauseResume(self):
  124. self.parent.printerPauseResume(self.printer)
  125. def cancelJob(self):
  126. self.parent.printerJobCancel(self.printer)
  127. def moveXP(self):
  128. self.parent.printerMoveAction(self.printer, "x", int(self.parent.jogMoveLength), True)
  129. def moveXM(self):
  130. self.parent.printerMoveAction(self.printer, "x", -1 * int(self.parent.jogMoveLength), True)
  131. def moveYP(self):
  132. self.parent.printerMoveAction(self.printer, "y", int(self.parent.jogMoveLength), True)
  133. def moveYM(self):
  134. self.parent.printerMoveAction(self.printer, "y", -1 * int(self.parent.jogMoveLength), True)
  135. def moveZP(self):
  136. self.parent.printerMoveAction(self.printer, "z", int(self.parent.jogMoveLength), True)
  137. def moveZM(self):
  138. self.parent.printerMoveAction(self.printer, "z", -1 * int(self.parent.jogMoveLength), True)
  139. def homeX(self):
  140. self.parent.printerHomingAction(self.printer, "x")
  141. def homeY(self):
  142. self.parent.printerHomingAction(self.printer, "y")
  143. def homeZ(self):
  144. self.parent.printerHomingAction(self.printer, "z")
  145. def homeAll(self):
  146. self.parent.printerHomingAction(self.printer, "xyz")
  147. def turnOn(self):
  148. if self.method == "psucontrol":
  149. self.parent.printerOnAction(self.printer)
  150. elif self.method == "system":
  151. cmds = self.parent.getSystemCommands(self.printer[0], self.printer[1])
  152. for cmd in cmds:
  153. if "on" in cmd:
  154. self.parent.setSystemCommand(self.printer[0], self.printer[1], cmd)
  155. break
  156. def turnOff(self):
  157. if self.method == "psucontrol":
  158. self.parent.printerOffAction(self.printer)
  159. elif self.method == "system":
  160. cmds = self.parent.getSystemCommands(self.printer[0], self.printer[1])
  161. for cmd in cmds:
  162. if "off" in cmd:
  163. self.parent.setSystemCommand(self.printer[0], self.printer[1], cmd)
  164. break
  165. def cooldown(self):
  166. self.parent.printerCooldown(self.printer)
  167. def preheatTool(self):
  168. self.parent.printerHeatTool(self.printer)
  169. def preheatBed(self):
  170. self.parent.printerHeatBed(self.printer)
  171. def getHost(self):
  172. return self.host
  173. def sliderChanged(self):
  174. self.slideLabel.setText(str(self.slider.value() * self.sliderFactor) + "ms")
  175. def closeEvent(self, event):
  176. self.reloadOn = False
  177. self.url = ""
  178. self.parent.removeWebcamWindow(self)
  179. def scheduleLoadImage(self):
  180. if self.reloadOn:
  181. QTimer.singleShot(self.slider.value() * self.sliderFactor, self.loadImage)
  182. def scheduleLoadStatus(self):
  183. if self.reloadOn:
  184. QTimer.singleShot(self.slider.value() * self.sliderFactor * self.statusDelayFactor, self.loadStatus)
  185. def loadImage(self):
  186. url = QUrl(self.url)
  187. request = QtNetwork.QNetworkRequest(url)
  188. self.manager.get(request)
  189. def loadStatus(self):
  190. s = "Status: "
  191. t = self.parent.getTemperatureString(self.host, self.printer[1])
  192. if len(t) > 0:
  193. s += t
  194. else:
  195. s += "Unknown"
  196. progress = self.parent.getProgress(self.host, self.printer[1])
  197. if ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress) and (progress["completion"] != None) and (progress["printTime"] != None) and (progress["printTimeLeft"] != None):
  198. s += " - %.1f%%" % progress["completion"]
  199. s += " - runtime "
  200. s += time.strftime("%H:%M:%S", time.gmtime(progress["printTime"]))
  201. s += " - "
  202. s += time.strftime("%H:%M:%S", time.gmtime(progress["printTimeLeft"])) + " left"
  203. self.statusLabel.setText(s)
  204. self.scheduleLoadStatus()
  205. def handleResponse(self, reply):
  206. if reply.url().url() == self.url:
  207. if reply.error() == QtNetwork.QNetworkReply.NoError:
  208. reader = QImageReader(reply)
  209. reader.setAutoTransform(True)
  210. image = reader.read()
  211. if image != None:
  212. if image.colorSpace().isValid():
  213. image.convertToColorSpace(QColorSpace.SRgb)
  214. self.img.setPixmap(QPixmap.fromImage(image))
  215. self.scheduleLoadImage()
  216. else:
  217. print("Error decoding image: " + reader.errorString())
  218. else:
  219. print("Error loading image: " + reply.errorString())