psychopy 2024.1.4__py3-none-any.whl → 2024.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of psychopy might be problematic. Click here for more details.

Files changed (325) hide show
  1. psychopy/.DS_Store +0 -0
  2. psychopy/CHANGELOG.txt +206 -0
  3. psychopy/GIT_SHA +1 -0
  4. psychopy/VERSION +1 -0
  5. psychopy/__init__.py +77 -15
  6. psychopy/app/Resources/classic/plugin16.png +0 -0
  7. psychopy/app/Resources/classic/plugin16@2x.png +0 -0
  8. psychopy/app/Resources/dark/plugin16.png +0 -0
  9. psychopy/app/Resources/dark/plugin16@2x.png +0 -0
  10. psychopy/app/Resources/light/plugin16.png +0 -0
  11. psychopy/app/Resources/light/plugin16@2x.png +0 -0
  12. psychopy/app/__init__.py +76 -2
  13. psychopy/app/_psychopyApp.py +126 -101
  14. psychopy/app/builder/builder.py +14 -10
  15. psychopy/app/builder/dialogs/__init__.py +8 -8
  16. psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
  17. psychopy/app/builder/dialogs/paramCtrls.py +24 -57
  18. psychopy/app/builder/validators.py +2 -2
  19. psychopy/app/coder/codeEditorBase.py +8 -8
  20. psychopy/app/coder/coder.py +4 -4
  21. psychopy/app/connections/sendusage.py +2 -2
  22. psychopy/app/connections/updates.py +9 -9
  23. psychopy/app/dialogs.py +34 -2
  24. psychopy/app/idle.py +31 -0
  25. psychopy/app/jobs.py +21 -3
  26. psychopy/app/linuxconfig/__init__.py +9 -0
  27. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  28. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
  29. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  30. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
  31. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
  32. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  33. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
  34. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  35. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1011 -942
  36. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
  37. psychopy/app/pavlovia_ui/_base.py +33 -3
  38. psychopy/app/pavlovia_ui/search.py +0 -1
  39. psychopy/app/plugin_manager/dialog.py +104 -51
  40. psychopy/app/plugin_manager/packages.py +5 -0
  41. psychopy/app/plugin_manager/plugins.py +145 -67
  42. psychopy/app/preferencesDlg.py +8 -8
  43. psychopy/app/psychopyApp.py +11 -5
  44. psychopy/app/ribbon.py +124 -14
  45. psychopy/app/runner/runner.py +6 -1
  46. psychopy/app/stdout/stdOutRich.py +27 -11
  47. psychopy/app/themes/icons.py +52 -2
  48. psychopy/assets/__init__.py +0 -0
  49. psychopy/assets/click.png +0 -0
  50. psychopy/assets/clicknext.png +0 -0
  51. psychopy/assets/next.png +0 -0
  52. psychopy/assets/psychopy.ico +0 -0
  53. psychopy/assets/psychopy.png +0 -0
  54. psychopy/assets/templates/__init__.py +0 -0
  55. psychopy/assets/touch.png +0 -0
  56. psychopy/assets/touchnext.png +0 -0
  57. psychopy/assets/window.ico +0 -0
  58. psychopy/changes/2023.1.0.md +9 -0
  59. psychopy/changes/2024.1.0.md +16 -0
  60. psychopy/changes/__init__.py +0 -0
  61. psychopy/clock.py +2 -2
  62. psychopy/colors.py +2 -1
  63. psychopy/compatibility.py +53 -1
  64. psychopy/contrib/.DS_Store +0 -0
  65. psychopy/contrib/configobj/__init__.py +10 -8
  66. psychopy/data/__init__.py +3 -2
  67. psychopy/data/base.py +5 -5
  68. psychopy/data/experiment.py +130 -4
  69. psychopy/data/routine.py +56 -0
  70. psychopy/data/staircase.py +2 -2
  71. psychopy/data/trial.py +559 -97
  72. psychopy/data/utils.py +56 -21
  73. psychopy/demos/.DS_Store +0 -0
  74. psychopy/demos/builder/.DS_Store +0 -0
  75. psychopy/demos/builder/Design Templates/.DS_Store +0 -0
  76. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  77. psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
  78. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
  79. psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
  80. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
  81. psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
  82. psychopy/demos/builder/Hardware/.DS_Store +0 -0
  83. psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
  84. psychopy/demos/coder/.DS_Store +0 -0
  85. psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
  86. psychopy/demos/coder/iohub/.DS_Store +0 -0
  87. psychopy/demos/coder/misc/hdf5_2_csv +33 -0
  88. psychopy/event.py +30 -29
  89. psychopy/experiment/.DS_Store +0 -0
  90. psychopy/experiment/_experiment.py +6 -6
  91. psychopy/experiment/components/.DS_Store +0 -0
  92. psychopy/experiment/components/__init__.py +6 -3
  93. psychopy/experiment/components/_base.py +286 -131
  94. psychopy/experiment/components/aperture/.DS_Store +0 -0
  95. psychopy/experiment/components/brush/.DS_Store +0 -0
  96. psychopy/experiment/components/button/.DS_Store +0 -0
  97. psychopy/experiment/components/button/__init__.py +5 -1
  98. psychopy/experiment/components/buttonBox/.DS_Store +0 -0
  99. psychopy/experiment/components/camera/.DS_Store +0 -0
  100. psychopy/experiment/components/code/.DS_Store +0 -0
  101. psychopy/experiment/components/dots/.DS_Store +0 -0
  102. psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
  103. psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
  104. psychopy/experiment/components/form/.DS_Store +0 -0
  105. psychopy/experiment/components/form/__init__.py +6 -2
  106. psychopy/experiment/components/grating/.DS_Store +0 -0
  107. psychopy/experiment/components/grating/__init__.py +14 -3
  108. psychopy/experiment/components/image/.DS_Store +0 -0
  109. psychopy/experiment/components/image/__init__.py +14 -3
  110. psychopy/experiment/components/joyButtons/.DS_Store +0 -0
  111. psychopy/experiment/components/joystick/.DS_Store +0 -0
  112. psychopy/experiment/components/keyboard/.DS_Store +0 -0
  113. psychopy/experiment/components/keyboard/__init__.py +22 -10
  114. psychopy/experiment/components/microphone/.DS_Store +0 -0
  115. psychopy/experiment/components/microphone/__init__.py +59 -39
  116. psychopy/experiment/components/mouse/.DS_Store +0 -0
  117. psychopy/experiment/components/mouse/__init__.py +44 -29
  118. psychopy/experiment/components/movie/.DS_Store +0 -0
  119. psychopy/experiment/components/movie/__init__.py +1 -1
  120. psychopy/experiment/components/panorama/.DS_Store +0 -0
  121. psychopy/experiment/components/parallelOut/.DS_Store +0 -0
  122. psychopy/experiment/components/patch/.DS_Store +0 -0
  123. psychopy/experiment/components/polygon/.DS_Store +0 -0
  124. psychopy/experiment/components/polygon/__init__.py +26 -6
  125. psychopy/experiment/components/progress/.DS_Store +0 -0
  126. psychopy/experiment/components/ratingScale/.DS_Store +0 -0
  127. psychopy/experiment/components/resourceManager/.DS_Store +0 -0
  128. psychopy/experiment/components/roi/.DS_Store +0 -0
  129. psychopy/experiment/components/roi/__init__.py +5 -0
  130. psychopy/experiment/components/routineSettings/.DS_Store +0 -0
  131. psychopy/experiment/components/routineSettings/__init__.py +57 -10
  132. psychopy/experiment/components/serialOut/.DS_Store +0 -0
  133. psychopy/experiment/components/settings/.DS_Store +0 -0
  134. psychopy/experiment/components/settings/__init__.py +117 -42
  135. psychopy/experiment/components/slider/.DS_Store +0 -0
  136. psychopy/experiment/components/sound/.DS_Store +0 -0
  137. psychopy/experiment/components/sound/__init__.py +54 -19
  138. psychopy/experiment/components/static/.DS_Store +0 -0
  139. psychopy/experiment/components/static/__init__.py +1 -1
  140. psychopy/experiment/components/text/.DS_Store +0 -0
  141. psychopy/experiment/components/text/__init__.py +28 -3
  142. psychopy/experiment/components/textbox/.DS_Store +0 -0
  143. psychopy/experiment/components/textbox/__init__.py +12 -2
  144. psychopy/experiment/components/unknown/.DS_Store +0 -0
  145. psychopy/experiment/components/unknown/__init__.py +1 -2
  146. psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
  147. psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
  148. psychopy/experiment/components/variable/.DS_Store +0 -0
  149. psychopy/experiment/flow.py +11 -4
  150. psychopy/experiment/loops.py +85 -37
  151. psychopy/experiment/params.py +74 -32
  152. psychopy/experiment/py2js_transpiler.py +8 -1
  153. psychopy/experiment/routines/.DS_Store +0 -0
  154. psychopy/experiment/routines/_base.py +102 -22
  155. psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
  156. psychopy/experiment/routines/counterbalance/__init__.py +5 -1
  157. psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
  158. psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
  159. psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
  160. psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
  161. psychopy/experiment/routines/photodiodeValidator/__init__.py +6 -5
  162. psychopy/experiment/routines/unknown/.DS_Store +0 -0
  163. psychopy/gui/wxgui.py +4 -4
  164. psychopy/hardware/.DS_Store +0 -0
  165. psychopy/hardware/__init__.py +1 -1
  166. psychopy/hardware/base.py +12 -0
  167. psychopy/hardware/camera/__init__.py +1 -15
  168. psychopy/hardware/cedrus.py +10 -11
  169. psychopy/hardware/crs/colorcal.py +13 -22
  170. psychopy/hardware/crs/optical.py +10 -20
  171. psychopy/hardware/emulator.py +17 -14
  172. psychopy/hardware/eyetracker.py +42 -118
  173. psychopy/hardware/gammasci.py +4 -15
  174. psychopy/hardware/keyboard.py +102 -10
  175. psychopy/hardware/listener.py +3 -0
  176. psychopy/hardware/microphone.py +148 -18
  177. psychopy/hardware/minolta.py +8 -15
  178. psychopy/hardware/photodiode.py +191 -16
  179. psychopy/hardware/photometer/__init__.py +11 -19
  180. psychopy/hardware/pr.py +8 -15
  181. psychopy/hardware/speaker.py +39 -4
  182. psychopy/info.py +0 -71
  183. psychopy/iohub/.DS_Store +0 -0
  184. psychopy/iohub/__init__.py +1 -1
  185. psychopy/iohub/client/__init__.py +30 -20
  186. psychopy/iohub/client/keyboard.py +24 -24
  187. psychopy/iohub/datastore/__init__.py +2 -2
  188. psychopy/iohub/datastore/util.py +2 -2
  189. psychopy/iohub/default_config.yaml +1 -1
  190. psychopy/iohub/devices/.DS_Store +0 -0
  191. psychopy/iohub/devices/__init__.py +112 -25
  192. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  193. psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
  194. psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
  195. psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
  196. psychopy/iohub/devices/eyetracker/__init__.py +46 -0
  197. psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
  198. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
  199. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
  200. psychopy/iohub/server.py +2 -2
  201. psychopy/iohub/start_iohub_process.py +3 -0
  202. psychopy/iohub/util/__init__.py +62 -70
  203. psychopy/layout.py +5 -5
  204. psychopy/logging.py +8 -1
  205. psychopy/microphone.py +10 -37
  206. psychopy/platform_specific/__init__.py +0 -2
  207. psychopy/platform_specific/darwin.py +1 -3
  208. psychopy/platform_specific/linux.py +31 -33
  209. psychopy/platform_specific/win32.py +38 -13
  210. psychopy/plugins/__init__.py +148 -116
  211. psychopy/plugins/util.py +39 -0
  212. psychopy/preferences/Darwin.spec +4 -2
  213. psychopy/preferences/FreeBSD.spec +4 -2
  214. psychopy/preferences/Linux.spec +4 -2
  215. psychopy/preferences/Windows.spec +4 -2
  216. psychopy/preferences/baseNoArch.spec +4 -2
  217. psychopy/preferences/preferences.py +47 -24
  218. psychopy/projects/pavlovia.py +47 -4
  219. psychopy/scripts/psyexpCompile.py +0 -4
  220. psychopy/session.py +153 -21
  221. psychopy/sound/__init__.py +31 -21
  222. psychopy/sound/_base.py +20 -3
  223. psychopy/sound/audioclip.py +320 -33
  224. psychopy/sound/backend_ptb.py +47 -58
  225. psychopy/sound/backend_pygame.py +1 -1
  226. psychopy/sound/backend_pysound.py +6 -15
  227. psychopy/sound/transcribe.py +53 -0
  228. psychopy/tests/.DS_Store +0 -0
  229. psychopy/tests/data/.DS_Store +0 -0
  230. psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
  231. psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
  232. psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
  233. psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
  234. psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
  235. psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
  236. psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
  237. psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
  238. psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
  239. psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
  240. psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
  241. psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
  242. psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
  243. psychopy/tests/data/correctScript/.DS_Store +0 -0
  244. psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
  245. psychopy/tests/data/test_session/.DS_Store +0 -0
  246. psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
  247. psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
  248. psychopy/tests/test_app/.DS_Store +0 -0
  249. psychopy/tests/test_app/conftest.py +2 -2
  250. psychopy/tests/test_app/test_speed.py +4 -1
  251. psychopy/tests/test_data/test_TrialHandler2.py +146 -1
  252. psychopy/tests/test_experiment/.DS_Store +0 -0
  253. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
  254. psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
  255. psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
  256. psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
  257. psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
  258. psychopy/tests/test_experiment/test_py2js.py +1 -1
  259. psychopy/tests/test_hardware/test_keyboard.py +31 -0
  260. psychopy/tests/test_hardware/test_ports.py +1 -11
  261. psychopy/tests/test_liaison/test_Liaison.py +47 -0
  262. psychopy/tests/test_misc/test_core.py +5 -0
  263. psychopy/tests/test_session/test_Session.py +5 -1
  264. psychopy/tests/test_tools/test_versionchooser.py +39 -8
  265. psychopy/tests/test_visual/test_all_stimuli.py +0 -97
  266. psychopy/tests/test_visual/test_image.py +6 -5
  267. psychopy/tests/test_visual/test_textbox.py +36 -0
  268. psychopy/tests/utils.py +4 -0
  269. psychopy/tools/filetools.py +1 -1
  270. psychopy/tools/pkgtools.py +160 -137
  271. psychopy/tools/versionchooser.py +10 -10
  272. psychopy/tools/wizard.py +3 -3
  273. psychopy/visual/.DS_Store +0 -0
  274. psychopy/visual/backends/pygletbackend.py +24 -13
  275. psychopy/visual/basevisual.py +5 -11
  276. psychopy/visual/button.py +2 -14
  277. psychopy/visual/helpers.py +5 -5
  278. psychopy/visual/line.py +1 -2
  279. psychopy/visual/movie2.py +7 -816
  280. psychopy/visual/movie3.py +7 -589
  281. psychopy/visual/movies/__init__.py +8 -11
  282. psychopy/visual/movies/frame.py +5 -2
  283. psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
  284. psychopy/visual/noise.py +8 -7
  285. psychopy/visual/patch.py +7 -16
  286. psychopy/visual/radial.py +9 -7
  287. psychopy/visual/ratingscale.py +8 -1415
  288. psychopy/visual/secondorder.py +10 -9
  289. psychopy/visual/shape.py +7 -2
  290. psychopy/visual/text.py +1 -1
  291. psychopy/visual/textbox2/textbox2.py +28 -5
  292. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
  293. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/RECORD +307 -213
  294. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
  295. psychopy/app/Resources/click.png +0 -0
  296. psychopy/app/Resources/next.png +0 -0
  297. psychopy/experiment/components/patch/__init__.py +0 -121
  298. psychopy/experiment/components/patch/classic/patch.png +0 -0
  299. psychopy/experiment/components/patch/dark/patch.png +0 -0
  300. psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
  301. psychopy/experiment/components/patch/light/patch.png +0 -0
  302. psychopy/experiment/components/patch/light/patch@2x.png +0 -0
  303. psychopy/experiment/components/ratingScale/__init__.py +0 -337
  304. psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
  305. psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
  306. psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
  307. psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
  308. psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
  309. psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
  310. psychopy/platform_specific/posix.py +0 -16
  311. psychopy/tests/test_sound/test_microphone.py +0 -217
  312. psychopy/tests/test_visual/test_ratingScale.py +0 -299
  313. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
  314. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
  315. /psychopy/{app/Resources → assets}/USB-C.png +0 -0
  316. /psychopy/{app/Resources → assets}/USB.png +0 -0
  317. /psychopy/{app/Resources → assets}/creditCard.png +0 -0
  318. /psychopy/{app/Resources → assets}/default.mp3 +0 -0
  319. /psychopy/{app/Resources → assets}/default.mp4 +0 -0
  320. /psychopy/{app/Resources → assets}/default.png +0 -0
  321. /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
  322. /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
  323. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
  324. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
  325. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
psychopy/gui/wxgui.py CHANGED
@@ -13,11 +13,11 @@ import wx
13
13
  import numpy
14
14
  import os
15
15
  from psychopy.localization import _translate
16
- from pkg_resources import parse_version
16
+ from packaging.version import Version
17
17
 
18
18
  OK = wx.ID_OK
19
19
 
20
- thisVer = parse_version(wx.__version__)
20
+ thisVer = Version(wx.__version__)
21
21
 
22
22
  def ensureWxApp():
23
23
  # make sure there's a wxApp prior to showing a gui, e.g., for expInfo
@@ -26,9 +26,9 @@ def ensureWxApp():
26
26
  wx.Dialog(None, -1) # not shown; FileDialog gives same exception
27
27
  return True
28
28
  except wx._core.PyNoAppError:
29
- if thisVer < parse_version('2.9'):
29
+ if thisVer < Version('2.9'):
30
30
  return wx.PySimpleApp()
31
- elif thisVer >= parse_version('4.0') and thisVer < parse_version('4.1'):
31
+ elif thisVer >= Version('4.0') and thisVer < Version('4.1'):
32
32
  raise Exception(
33
33
  "wx>=4.0 clashes with pyglet and making it unsafe "
34
34
  "as a PsychoPy gui helper. Please install PyQt (4 or 5)"
Binary file
@@ -7,7 +7,7 @@ from itertools import chain
7
7
  from psychopy import logging
8
8
  from . import eyetracker, listener
9
9
  from .manager import DeviceManager, deviceManager
10
- from .base import BaseDevice
10
+ from .base import BaseDevice, BaseResponse, BaseResponseDevice
11
11
 
12
12
  try:
13
13
  from collections.abc import Iterable
psychopy/hardware/base.py CHANGED
@@ -14,6 +14,7 @@ __all__ = [
14
14
 
15
15
  import json
16
16
  import inspect
17
+ import time
17
18
 
18
19
 
19
20
  class BaseResponse:
@@ -170,6 +171,17 @@ class BaseResponseDevice(BaseDevice):
170
171
  """
171
172
  pass
172
173
 
174
+ def hasUnfinishedMessage(self):
175
+ """
176
+ If there is a message which have been partially received but not finished (e.g.
177
+ getting the start of a message from a serial device but no end of line character
178
+ yet), this will return True.
179
+
180
+ If not implemented or not relevant on a given device (e.g. Keyboard, which only
181
+ sends full messages), this will always return False.
182
+ """
183
+ return False
184
+
173
185
  def parseMessage(self, message):
174
186
  raise NotImplementedError(
175
187
  "All subclasses of BaseDevice must implement the method `parseMessage`"
@@ -1725,7 +1725,7 @@ class Camera:
1725
1725
 
1726
1726
  # current camera frame since the start of recording
1727
1727
  self._player = None # media player instance
1728
- self._status = NOT_STARTED
1728
+ self.status = NOT_STARTED
1729
1729
  self._isRecording = False
1730
1730
  self._bufferSecs = float(bufferSecs)
1731
1731
  self._lastFrame = None # use None to avoid imports for ImageStim
@@ -1943,20 +1943,6 @@ class Camera:
1943
1943
  """
1944
1944
  return getCameraDescriptions(collapse=collapse)
1945
1945
 
1946
- @property
1947
- def status(self):
1948
- """Status flag for the camera (`int`).
1949
-
1950
- Can be either `RECORDING`, `STOPPED`, `STOPPING`, or `NOT_STARTED`. This
1951
- property used in Builder output scripts and does not update on its own.
1952
-
1953
- """
1954
- return self._status
1955
-
1956
- @status.setter
1957
- def status(self, value):
1958
- self._status = value
1959
-
1960
1946
  @property
1961
1947
  def device(self):
1962
1948
  """Camera to use (`str` or `None`).
@@ -19,18 +19,17 @@ instead (bundled with Standalone PsychoPy)::
19
19
  """
20
20
 
21
21
  import psychopy.logging as logging
22
+ from psychopy.tools.pkgtools import PluginStub
23
+
24
+
25
+ class RB730(
26
+ PluginStub,
27
+ plugin="psychopy-cedrus",
28
+ doclink="https://psychopy.github.io/psychopy-cedrus/coder/RB730"
29
+ ):
30
+ pass
31
+
22
32
 
23
- try:
24
- from psychopy_cedrus import RB730
25
- except (ModuleNotFoundError, ImportError):
26
- logging.error(
27
- "Support for Cedrus Corporation hardware is not available this "
28
- "session. Please install `psychopy-cedrus` and restart the session "
29
- "to enable support.")
30
- except Exception as e:
31
- logging.error(
32
- "Error encountered while loading `psychopy-cedrus`. Check logs for "
33
- "more information.")
34
33
 
35
34
  if __name__ == "__main__":
36
35
  pass
@@ -5,26 +5,17 @@
5
5
  # Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
6
6
  # Distributed under the terms of the GNU General Public License (GPL).
7
7
 
8
- import psychopy.logging as logging
9
-
10
- try:
11
- from psychopy_crs.colorcal import ColorCAL
12
- except (ModuleNotFoundError, ImportError, NameError):
13
- logging.error(
14
- "Support for Cambridge Research Systems ColorCAL is not available this "
15
- "session. Please install `psychopy-crs` and restart the session to "
16
- "enable support.")
17
- except Exception as e:
18
- logging.error(
19
- "Error encountered while loading `psychopy-crs`. Check logs for more "
20
- "information.")
21
- else:
22
- # Monkey-patch our metadata into CRS class if missing required attributes
23
- if not hasattr(ColorCAL, "longName"):
24
- setattr(ColorCAL, "longName", "CRS ColorCAL")
25
-
26
- if not hasattr(ColorCAL, "driverFor"):
27
- setattr(ColorCAL, "driverFor", ["colorcal"])
28
-
29
- if __name__ == "__main__":
8
+
9
+ from psychopy.tools.pkgtools import PluginStub
10
+
11
+
12
+ class ColorCAL(PluginStub, plugin="psychopy-crs", doclink="https://psychopy.github.io/psychopy-crs/coder/ColorCAL"):
30
13
  pass
14
+
15
+
16
+ # Monkey-patch our metadata into CRS class if missing required attributes
17
+ if not hasattr(ColorCAL, "longName"):
18
+ setattr(ColorCAL, "longName", "CRS ColorCAL")
19
+
20
+ if not hasattr(ColorCAL, "driverFor"):
21
+ setattr(ColorCAL, "driverFor", ["colorcal"])
@@ -21,26 +21,16 @@
21
21
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
22
  # THE SOFTWARE.
23
23
 
24
- import psychopy.logging as logging
24
+ from psychopy.tools.pkgtools import PluginStub
25
25
 
26
- try:
27
- from psychopy_crs.optical import OptiCAL
28
- except (ModuleNotFoundError, ImportError):
29
- logging.error(
30
- "Support for Cambridge Research Systems OptiCAL is not available this "
31
- "session. Please install `psychopy-crs` and restart the session to "
32
- "enable support.")
33
- except Exception as e:
34
- logging.error(
35
- "Error encountered while loading `psychopy-crs`. Check logs for more "
36
- "information.")
37
- else:
38
- # Monkey-patch our metadata into CRS class if missing required attributes
39
- if not hasattr(OptiCAL, "longName"):
40
- setattr(OptiCAL, "longName", "CRS OptiCal")
41
26
 
42
- if not hasattr(OptiCAL, "driverFor"):
43
- setattr(OptiCAL, "driverFor", ["optical"])
44
-
45
- if __name__ == "__main__":
27
+ class OptiCAL(PluginStub, plugin="psychopy-crs", doclink="https://psychopy.github.io/psychopy-crs/coder/OptiCAL"):
46
28
  pass
29
+
30
+
31
+ # Monkey-patch our metadata into CRS class if missing required attributes
32
+ if not hasattr(OptiCAL, "longName"):
33
+ setattr(OptiCAL, "longName", "CRS OptiCal")
34
+
35
+ if not hasattr(OptiCAL, "driverFor"):
36
+ setattr(OptiCAL, "driverFor", ["optical"])
@@ -18,20 +18,23 @@ These are optional components that can be obtained by installing the
18
18
 
19
19
  """
20
20
 
21
- import psychopy.logging as logging
22
-
23
- try:
24
- from psychopy_mri_emulator import (
25
- SyncGenerator, ResponseEmulator, launchScan)
26
- except (ModuleNotFoundError, ImportError):
27
- logging.error(
28
- "Support for software fMRI emulation is not available this session. "
29
- "Please install `psychopy-mri-emulator` and restart the session to "
30
- "enable support.")
31
- except Exception as e:
32
- logging.error(
33
- "Error encountered while loading `psychopy-mri-emulator`. Check logs "
34
- "for more information.")
21
+
22
+ from psychopy.tools.pkgtools import PluginStub
23
+
24
+
25
+ class SyncGenerator(
26
+ PluginStub,
27
+ plugin="psychopy-mri-emulator"
28
+ ):
29
+ pass
30
+
31
+
32
+ class ResponseEmulator(
33
+ PluginStub,
34
+ plugin="psychopy-mri-emulator"
35
+ ):
36
+ pass
37
+
35
38
 
36
39
  if __name__ == "__main__":
37
40
  pass
@@ -1,48 +1,46 @@
1
1
  from psychopy.constants import STARTED, NOT_STARTED, PAUSED, STOPPED, FINISHED
2
2
  from psychopy.alerts import alert
3
3
  from psychopy import logging
4
+ from psychopy.iohub.devices import importDeviceModule
4
5
  from psychopy.tools.attributetools import AttributeGetSetMixin
5
6
  from copy import copy
7
+ import importlib
6
8
  import sys
7
9
 
8
10
 
9
11
  class EyetrackerControl(AttributeGetSetMixin):
10
- currentlyRecording = False
11
12
 
12
13
  def __init__(self, tracker, actionType="Start and Stop"):
13
14
  self.tracker = tracker
14
15
  self.actionType = actionType
15
- self._status = NOT_STARTED
16
+ self.status = NOT_STARTED
17
+
18
+ def start(self):
19
+ """
20
+ Start recording
21
+ """
22
+ # if previously at a full stop, clear events
23
+ if not self.tracker.isRecordingEnabled():
24
+ logging.exp("eyetracker.clearEvents()")
25
+ self.tracker.clearEvents()
26
+ # start recording
27
+ self.tracker.setRecordingState(True)
28
+ logging.exp("eyetracker.setRecordingState(True)")
29
+
30
+ def stop(self):
31
+ """
32
+ Stop recording
33
+ """
34
+ self.tracker.setRecordingState(False)
35
+ logging.exp("eyetracker.setRecordingState(False)")
16
36
 
17
37
  @property
18
- def status(self):
19
- return self._status
20
-
21
- @status.setter
22
- def status(self, value):
23
- old = self._status
24
- new = self._status = value
25
- # Skip if there's no change
26
- if new == old:
27
- return
28
- # Start recording if set to STARTED
29
- if new in (STARTED,):
30
- if old in (NOT_STARTED, STOPPED, FINISHED):
31
- # If was previously at a full stop, clear events before starting again
32
- if self.actionType.find('Start') >= 0 and EyetrackerControl.currentlyRecording is False:
33
- logging.exp("eyetracker.clearEvents()")
34
- self.tracker.clearEvents()
35
- # Start recording
36
- if self.actionType.find('Start') >= 0 and not EyetrackerControl.currentlyRecording:
37
- self.tracker.setRecordingState(True)
38
- logging.exp("eyetracker.setRecordingState(True)")
39
- EyetrackerControl.currentlyRecording = True
40
- # Stop recording if set to any stop constants
41
- if new in (NOT_STARTED, PAUSED, STOPPED, FINISHED):
42
- if self.actionType.find('Stop') >= 0 and EyetrackerControl.currentlyRecording:
43
- self.tracker.setRecordingState(False)
44
- logging.exp("eyetracker.setRecordingState(False)")
45
- EyetrackerControl.currentlyRecording = False
38
+ def currentlyRecording(self):
39
+ """
40
+ Check if the eyetracker is currently recording
41
+ added for backwards compatibility, should be removed in future
42
+ """
43
+ return self.tracker.isRecordingEnabled()
46
44
 
47
45
  @property
48
46
  def pos(self):
@@ -84,94 +82,20 @@ class EyetrackerCalibration:
84
82
  def __iter__(self):
85
83
  """Overload dict() method to return in ioHub format"""
86
84
  tracker = self.eyetracker.getIOHubDeviceClass(full=True)
87
-
88
- # Make sure that target will use the same color space and units as calibration
89
- if self.target.colorSpace == self.colorSpace and self.target.units == self.units:
90
- target = self.target
91
- else:
92
- target = copy(self.target)
93
- target.colorSpace = self.colorSpace
94
- target.units = self.units
95
- # Get self as dict
96
- asDict = {}
97
-
98
- textColor = self.textColor
99
- if isinstance(textColor, str) and textColor.lower() == 'auto':
100
- textColor = None
101
-
102
- if tracker == 'eyetracker.hw.sr_research.eyelink.EyeTracker':
103
- # As EyeLink
104
- asDict = {
105
- 'target_attributes': dict(target),
106
- 'type': self.targetLayout,
107
- 'auto_pace': self.progressMode == "time",
108
- 'pacing_speed': self.targetDelay,
109
- 'randomize': self.randomisePos,
110
- 'text_color': textColor,
111
- 'screen_background_color': getattr(self.win._color, self.colorSpace)
112
- }
113
- elif tracker == 'eyetracker.hw.tobii.EyeTracker':
114
- # As Tobii
115
- targetAttrs = dict(target)
116
- targetAttrs['animate'] = {
117
- 'enable': self.movementAnimation,
118
- 'expansion_ratio': self.expandScale,
119
- 'contract_only': self.expandScale == 1
120
- }
121
- asDict = {
122
- 'target_attributes': targetAttrs,
123
- 'type': self.targetLayout,
124
- 'randomize': self.randomisePos,
125
- 'auto_pace': self.progressMode == "time",
126
- 'target_delay': self.targetDelay,
127
- 'target_duration': self.targetDur,
128
- 'unit_type': self.units,
129
- 'color_type': self.colorSpace,
130
- 'text_color': textColor,
131
- 'screen_background_color': getattr(self.win._color, self.colorSpace),
132
- }
133
- elif tracker == 'eyetracker.hw.gazepoint.gp3.EyeTracker':
134
- # As GazePoint
135
- targetAttrs = dict(target)
136
- targetAttrs['animate'] = {
137
- 'enable': self.movementAnimation,
138
- 'expansion_ratio': self.expandScale,
139
- 'contract_only': self.expandScale == 1
140
- }
141
- asDict = {
142
- 'use_builtin': False,
143
- 'target_delay': self.targetDelay,
144
- 'target_duration': self.targetDur,
145
- 'target_attributes': targetAttrs,
146
- 'type': self.targetLayout,
147
- 'randomize': self.randomisePos,
148
- 'unit_type': self.units,
149
- 'color_type': self.colorSpace,
150
- 'text_color': textColor,
151
- 'screen_background_color': getattr(self.win._color, self.colorSpace),
152
- }
153
-
154
- elif tracker == 'eyetracker.hw.mouse.EyeTracker':
155
- # As MouseGaze
156
- targetAttrs = dict(target)
157
- targetAttrs['animate'] = {
158
- 'enable': self.movementAnimation,
159
- 'expansion_ratio': self.expandScale,
160
- 'contract_only': self.expandScale == 1
161
- }
162
- # Run as MouseGaze
163
- asDict = {
164
- 'target_attributes': targetAttrs,
165
- 'type': self.targetLayout,
166
- 'randomize': self.randomisePos,
167
- 'auto_pace': self.progressMode == "time",
168
- 'pacing_speed': self.targetDelay,
169
- 'unit_type': self.units,
170
- 'color_type': self.colorSpace,
171
- 'text_color': textColor,
172
- 'screen_background_color': getattr(self.win._color, self.colorSpace),
173
- }
174
- # Return
85
+ # split into package and class name
86
+ pkgName = ".".join(tracker.split(".")[:-1])
87
+ clsName = tracker.split(".")[-1]
88
+ # make sure pkgName is fully qualified
89
+ if not pkgName.startswith("psychopy.iohub.devices."):
90
+ pkgName = "psychopy.iohub.devices." + pkgName
91
+ # import package
92
+ pkg = importDeviceModule(pkgName)
93
+ # get tracker class
94
+ trackerCls = getattr(pkg, clsName)
95
+ # get self as dict
96
+ asDict = trackerCls.getCalibrationDict(self)
97
+
98
+ # return
175
99
  for key, value in asDict.items():
176
100
  yield key, value
177
101
 
@@ -14,19 +14,8 @@ These are optional components that can be obtained by installing the
14
14
 
15
15
  """
16
16
 
17
- import psychopy.logging as logging
18
-
19
- try:
20
- from psychopy_gammasci import S470
21
- except (ModuleNotFoundError, ImportError):
22
- logging.error(
23
- "Support for Gamma-Scientific Inc. hardware is not available this "
24
- "session. Please install `psychopy-gammasci` and restart the session "
25
- "to enable support.")
26
- except Exception as e:
27
- logging.error(
28
- "Error encountered while loading `psychopy-gammasci`. Check logs for "
29
- "more information.")
30
-
31
- if __name__ == "__main__":
17
+ from psychopy.tools.pkgtools import PluginStub
18
+
19
+
20
+ class S470(PluginStub, plugin="psychopy-gammasci", doclink="https://psychopy.github.io/psychopy-gammasci/coder/S470"):
32
21
  pass
@@ -67,6 +67,7 @@ import psychopy.clock
67
67
  from psychopy import logging
68
68
  from psychopy.constants import NOT_STARTED
69
69
  import time
70
+ import numpy as np
70
71
 
71
72
  from psychopy.hardware.base import BaseResponseDevice, BaseResponse
72
73
  from psychopy.hardware import DeviceManager
@@ -231,6 +232,25 @@ class Keyboard(AttributeGetSetMixin):
231
232
  keyList=keyList, ignoreKeys=ignoreKeys, waitRelease=waitRelease, clear=clear
232
233
  )
233
234
 
235
+ def getState(self, keys):
236
+ """
237
+ Get the current state of a key or set of keys
238
+
239
+ Parameters
240
+ ----------
241
+ keys : str or list[str]
242
+ Either the code for a single key, or a list of key codes.
243
+
244
+ Returns
245
+ -------
246
+ keys : bool or list[bool]
247
+ True if pressed, False if not. Will be a single value if given a
248
+ single key, or a list of bools if given a list of keys.
249
+ """
250
+ return self.device.getState(
251
+ keys=keys
252
+ )
253
+
234
254
  def waitKeys(self, maxWait=float('inf'), keyList=None, waitRelease=True,
235
255
  clear=True):
236
256
  return self.device.waitKeys(
@@ -493,7 +513,7 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
493
513
  if resp.value in ignoreKeys:
494
514
  wanted = False
495
515
  # if we got this far and the key is still wanted and not present, add it to output
496
- if wanted and resp not in keys:
516
+ if wanted and not any(k is resp for k in keys):
497
517
  keys.append(resp)
498
518
  # if clear=True, mark wanted responses as toClear
499
519
  if wanted and clear:
@@ -504,6 +524,70 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
504
524
 
505
525
  return keys
506
526
 
527
+ def getState(self, keys):
528
+ """
529
+ Get the current state of a key or set of keys
530
+
531
+ Parameters
532
+ ----------
533
+ keys : str or list[str]
534
+ Either the code for a single key, or a list of key codes.
535
+
536
+ Returns
537
+ -------
538
+ keys : bool or list[bool]
539
+ True if pressed, False if not. Will be a single value if given a
540
+ single key, or a list of bools if given a list of keys.
541
+ """
542
+ # if given a string, convert to a list
543
+ if isinstance(keys, str):
544
+ keys = [keys]
545
+ # start off False
546
+ state = [False] * len(keys)
547
+
548
+ if KeyboardDevice._backend == 'ptb':
549
+ # use ptb.Keyboard.check if backend is ptb
550
+ for buffer in self._buffers.values():
551
+ # get output from ptb
552
+ anyPressed, t, mat = buffer.dev.check()
553
+ # if got any key...
554
+ if mat.any():
555
+ # convert each key index to a key name
556
+ for i in np.where(mat.flatten())[0]:
557
+ # account for ptb's 1-based indexing
558
+ i = int(i) + 1
559
+ # get key name from index (or None if not applicable)
560
+ name = keyNames.get(i, None)
561
+ # check if it's on our list
562
+ if name in keys:
563
+ state[keys.index(name)] = True
564
+ elif KeyboardDevice._backend == 'iohub':
565
+ # get current state of ioHub keyboard
566
+ ioHubState = KeyboardDevice._iohubKeyboard.getCurrentDeviceState()
567
+ # iterate through pressed keys
568
+ for i in ioHubState.get("pressed_keys", {}):
569
+ # iohub returns strings - integerise
570
+ i = int(i)
571
+ # get key name from index (or None if not applicable)
572
+ name = keyNames.get(i, None)
573
+ # check if it's on our list
574
+ if name in keys:
575
+ state[keys.index(name)] = True
576
+ else:
577
+ # make a key state handler
578
+ handler = event.pyglet.window.key.KeyStateHandler()
579
+ # iterate through our list of keys
580
+ for i, key in enumerate(keys):
581
+ # if handler has an entry for the given key, it's pressed
582
+ state[i] = handler[getattr(event.pyglet.window.key, key.upper())]
583
+
584
+ # if state is a single value, remove list wrapper
585
+ if len(state) == 1:
586
+ state = state[0]
587
+
588
+ return state
589
+
590
+
507
591
  def dispatchMessages(self):
508
592
  if KeyboardDevice._backend == 'ptb':
509
593
  for buffer in self._buffers.values():
@@ -551,10 +635,13 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
551
635
  response = None
552
636
 
553
637
  if KeyboardDevice._backend == 'ptb':
554
- message['time'] -= self.clock.getLastResetTime()
555
638
  if message['down']:
556
639
  # if message is from a key down event, make a new response
557
- response = KeyPress(code=message['keycode'], tDown=message['time'])
640
+ response = KeyPress(
641
+ code=message['keycode'],
642
+ tDown=message['time'] - logging.defaultClock.getLastResetTime()
643
+ )
644
+ response.rt = message['time'] - self.clock.getLastResetTime()
558
645
  self._keysStillDown.append(response)
559
646
  else:
560
647
  # if message is from a key up event, alter existing response
@@ -562,7 +649,7 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
562
649
  if key.code == message['keycode']:
563
650
  response = key
564
651
  # calculate duration
565
- key.duration = message['time'] - key.tDown
652
+ key.duration = message['time'] - key.tDown - logging.defaultClock.getLastResetTime()
566
653
  # remove key from stillDown
567
654
  self._keysStillDown.remove(key)
568
655
  # stop processing keys as we're done
@@ -572,7 +659,8 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
572
659
  if message.type == "KEYBOARD_PRESS":
573
660
  # if message is from a key down event, make a new response
574
661
  response = KeyPress(code=message.char, tDown=message.time, name=message.key)
575
- response.rt = response.tDown - (self.clock.getLastResetTime() - self._iohubKeyboard.clock.getLastResetTime())
662
+ response.rt = response.tDown - (
663
+ self.clock.getLastResetTime() - self._iohubKeyboard.clock.getLastResetTime())
576
664
  self._keysStillDown.append(response)
577
665
  else:
578
666
  # if message is from a key up event, alter existing response
@@ -599,9 +687,9 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
599
687
 
600
688
  def waitKeys(self, maxWait=float('inf'), keyList=None, waitRelease=True,
601
689
  clear=True):
602
- """Same as `~psychopy.hardware.keyboard.Keyboard.getKeys`,
690
+ """Same as `~psychopy.hardware.keyboard.Keyboard.getKeys`,
603
691
  but halts everything (including drawing) while awaiting keyboard input.
604
-
692
+
605
693
  :Parameters:
606
694
  maxWait : any numeric value.
607
695
  Maximum number of seconds period and which keys to wait for.
@@ -621,9 +709,9 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
621
709
  clear : **True** or False
622
710
  Whether to clear the keyboard event buffer (and discard preceding
623
711
  keypresses) before starting to monitor for new keypresses.
624
-
712
+
625
713
  Returns None if times out.
626
-
714
+
627
715
  """
628
716
  timer = psychopy.clock.Clock()
629
717
 
@@ -642,6 +730,7 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
642
730
 
643
731
  def clearEvents(self, eventType=None):
644
732
  """Clear the events from the Keyboard such as previous key presses"""
733
+ # clear backend buffers
645
734
  if KeyboardDevice._backend == 'ptb':
646
735
  for buffer in self._buffers.values():
647
736
  buffer.flush() # flush the device events to the soft buffer
@@ -653,6 +742,9 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
653
742
  else:
654
743
  global event
655
744
  event.clearEvents(eventType)
745
+ # clear dispatched responses
746
+ self.responses = []
747
+
656
748
  logging.info("Keyboard events cleared", obj=self)
657
749
 
658
750
 
@@ -744,7 +836,7 @@ class _KeyBuffer(object):
744
836
  if not keyList and not waitRelease:
745
837
  keyPresses = list(self._keysStillDown)
746
838
  for k in list(self._keys):
747
- if not any(x.name == k.name and x.tDown == k.tDown for x in keyPresses):
839
+ if not any(x.name == k.name and x.tDown == k.tDown for x in keyPresses):
748
840
  keyPresses.append(k)
749
841
  if clear:
750
842
  self._keys = deque()
@@ -135,6 +135,9 @@ class ListenerLoop(threading.Thread):
135
135
  # dispatch messages from devices
136
136
  for device in self.devices:
137
137
  device.dispatchMessages()
138
+ # if there are no more devices attached, stop
139
+ if not len(self.devices):
140
+ self._active = False
138
141
  # sleep for 10ms
139
142
  time.sleep(self.refreshRate)
140
143