From c85d4b4aa9379851154313d285de56c391f77ebb Mon Sep 17 00:00:00 2001 From: gac-S_Changer Date: Tue, 8 Sep 2020 11:53:11 +0200 Subject: [PATCH] Startup --- config/devices - Copy.properties | 67 + config/devices-modbus.properties | 42 + config/devices.properties | 57 + config/jcae.properties | 11 + config/mail.properties | 9 + config/plugins.properties | 21 + config/settings.properties | 19 + config/setup-modbus.properties | 18 + config/setup.properties | 20 + config/tasks.properties | 2 + config/variables.properties | 2 + devices/20161117_163816.png.properties | 20 + devices/A1.properties | 3 + devices/A2.properties | 3 + devices/A3.properties | 3 + devices/A4.properties | 3 + devices/A5.properties | 3 + devices/B1.properties | 3 + devices/B2.properties | 3 + devices/B3.properties | 3 + devices/B4.properties | 3 + devices/B5.properties | 3 + devices/BasePlate.properties | 1 + devices/C1.properties | 3 + devices/C2.properties | 3 + devices/C3.properties | 3 + devices/C4.properties | 3 + devices/C5.properties | 3 + devices/D1.properties | 3 + devices/D2.properties | 3 + devices/D3.properties | 3 + devices/D4.properties | 3 + devices/D5.properties | 3 + devices/E1.properties | 3 + devices/E2.properties | 3 + devices/E3.properties | 3 + devices/E4.properties | 3 + devices/E5.properties | 3 + devices/F1.properties | 3 + devices/F2.properties | 3 + devices/F3.properties | 3 + devices/F4.properties | 3 + devices/F5.properties | 3 + devices/R1.properties | 3 + devices/R2.properties | 3 + devices/R3.properties | 3 + devices/R4.properties | 3 + devices/R5.properties | 3 + devices/RoomTemperatureBasePlate.properties | 1 + devices/Time.properties | 10 + devices/air_pressure.properties | 5 + devices/cx.properties | 17 + devices/cz.properties | 17 + devices/dewar_level.properties | 6 + devices/dispatcher.properties | 11 + devices/dp1.properties | 11 + devices/fx.properties | 17 + devices/fy.properties | 17 + devices/gripper_cam.properties | 20 + devices/img.properties | 24 + devices/led_ctrl.properties | 8 + devices/led_ctrl_1.properties | 9 + devices/led_ctrl_2.properties | 9 + devices/led_ctrl_3.properties | 9 + devices/led_level.properties | 9 + devices/m1.properties | 13 + devices/m2.properties | 13 + devices/monitoring_cam.properties | 20 + devices/n2_pressure.properties | 5 + devices/p1.properties | 9 + devices/phase_separator.properties | 8 + devices/phase_separator_level.properties | 6 + devices/rim_heater_temp.properties | 6 + devices/robot x.properties | 9 + devices/robot.properties | 8 + devices/robot_j1.properties | 10 + devices/robot_j2.properties | 10 + devices/robot_j3.properties | 10 + devices/robot_j4.properties | 10 + devices/robot_j5.properties | 10 + devices/robot_j6.properties | 10 + devices/robot_modbus.properties | 8 + devices/robot_rx.properties | 10 + devices/robot_ry.properties | 10 + devices/robot_rz.properties | 10 + devices/robot_x.properties | 10 + devices/robot_y.properties | 10 + devices/robot_z.properties | 10 + devices/ry.properties | 17 + devices/smart_magnet.properties | 8 + devices/smc_current.properties | 9 + devices/smc_current_rb.properties | 6 + devices/src1.properties | 24 + devices/src2.properties | 24 + devices/top_cam.properties | 20 + devices/ue.properties | 6 + devices/wago.properties | 8 + plugins/BarcodeReaderPanel.form | 114 ++ plugins/BarcodeReaderPanel.java | 170 ++ plugins/Beeper.java | 72 + plugins/Commands.form | 804 ++++++++ plugins/Commands.java | 928 +++++++++ plugins/Hexiposi.form | 34 + plugins/Hexiposi.java | 78 + plugins/HexiposiPanel.form | 81 + plugins/HexiposiPanel.java | 110 ++ plugins/LN2.form | 34 + plugins/LN2.java | 106 ++ plugins/LaserUE.java | 90 + plugins/LaserUEPanel.form | 59 + plugins/LaserUEPanel.java | 98 + plugins/MXSC-1.14.0.jar | Bin 0 -> 286787 bytes plugins/MjpegSource2.java | 149 ++ plugins/PuckDetectionPanel.form | 201 ++ plugins/PuckDetectionPanel.java | 281 +++ plugins/Recovery.form | 140 ++ plugins/Recovery.java | 288 +++ plugins/RobotModbus.java | 21 + plugins/RobotPanel.form | 587 ++++++ plugins/RobotPanel.java | 685 +++++++ plugins/RobotTcp.java | 21 + plugins/SmartMagnetConfig.java | 14 + plugins/SmartMagnetPanel.form | 284 +++ plugins/SmartMagnetPanel.java | 320 ++++ plugins/TestZMQ.java | 26 + plugins/WagoPanel.form | 167 ++ plugins/WagoPanel.java | 207 +++ plugins/gui.form | 62 + plugins/gui.java | 96 + script/LN2_Monitoring.scd | 15 + script/LevelMonitoring.scd | 12 + script/calibration/BinarySearchYZ.py | 29 + script/calibration/HillClimbingXZ.py | 45 + script/calibration/ScanRZ.py | 31 + script/calibration/ScanX.py | 101 + script/calibration/ScanY.py | 99 + script/calibration/ScanYZ.py | 93 + script/calibration/ToolCalibration.py | 60 + script/calibration/ToolCalibration2.py | 86 + script/calibration/ToolCalibration3.py | 83 + script/calibration/ToolCalibration6d.py | 79 + script/calibration/ToolCalibration6s.py | 79 + script/client/PShellClient.py | 369 ++++ script/client/TellClient.py | 184 ++ script/client/sseclient.py | 163 ++ script/client/tell.py | 79 + script/data/get_samples_info.py | 4 + script/data/pucks.py | 51 + script/data/reports.py | 39 + script/data/samples.py | 350 ++++ script/data/set_samples_info.py | 4 + script/devices/BarcodeReader.py | 83 + script/devices/Gonio.py | 43 + script/devices/Hexiposi.py | 175 ++ script/devices/LaserDistance.py | 27 + script/devices/LedCtrl.py | 46 + script/devices/OneWire.py | 134 ++ script/devices/RobotModbus.py | 46 + script/devices/RobotMotors.py | 62 + script/devices/RobotSC.py | 358 ++++ script/devices/RobotTCP.py | 919 +++++++++ script/devices/SmartMagnet.py | 129 ++ script/devices/Wago.py | 175 ++ script/hexiposi_positon.scd | 11 + script/imgproc/CameraCalibration.py | 156 ++ script/imgproc/CoverDetection.py | 98 + script/imgproc/CoverDetectionCalibration.py | 40 + script/imgproc/CreateMask.py | 29 + script/imgproc/LedDetectionFilter.py | 102 + script/imgproc/LedDetectionProc.py | 106 ++ script/imgproc/Utils.py | 113 ++ script/local.groovy | 3 + script/local.js | 4 + script/local.py | 438 +++++ script/motion/calibrate_tool.py | 32 + script/motion/dry.py | 58 + script/motion/get_aux.py | 21 + script/motion/get_dewar.py | 30 + script/motion/get_gonio.py | 20 + script/motion/homing_hexiposi.py | 18 + script/motion/mount.py | 153 ++ script/motion/move_aux.py | 18 + script/motion/move_cold.py | 22 + script/motion/move_dewar.py | 18 + script/motion/move_gonio.py | 18 + script/motion/move_heater.py | 18 + script/motion/move_home.py | 18 + script/motion/move_park.py | 18 + script/motion/move_scanner.py | 30 + script/motion/put_aux.py | 21 + script/motion/put_dewar.py | 30 + script/motion/put_gonio.py | 23 + script/motion/recover.py | 179 ++ script/motion/robot_recover.py | 16 + script/motion/scan_pin.py | 99 + script/motion/tools.py | 180 ++ script/motion/trash.py | 36 + script/motion/unmount.py | 108 ++ script/setup/ExposureScan.py | 33 + script/setup/Layout.py | 292 +++ script/tasks/ColdPositionTimeout.py | 23 + script/tasks/LedMonitoring.py | 26 + script/tasks/MangnetMonitoring.py | 24 + script/test/CameraCalibration.py | 71 + script/test/CoverDetection.py | 116 ++ script/test/PuckDetection.py | 24 + script/test/PuckDetectionTest.py | 11 + script/test/RobotCartesianScan.py | 28 + script/test/SampleDataInput.py | 1861 ++++++++++++++++++ script/test/SampleDataInput_Dominik.py | 1862 +++++++++++++++++++ script/test/SampleDetection.py | 23 + script/test/TestAlign.py | 9 + script/test/TestBugPcAPI | 4 + script/test/TestBugPcAPI.py | 30 + script/test/TestBugPcAPI2.py | 6 + script/test/TestBugPcAPI3.py | 4 + script/test/TestCalib.py | 30 + script/test/TestCameraStability1.py | 11 + script/test/TestCameraStability2.py | 14 + script/test/TestCameraStability3.py | 9 + script/test/TestCmdSynchronization.py | 32 + script/test/TestCoverDetection.py | 19 + script/test/TestEuclidean.py | 23 + script/test/TestLaserScan.py | 12 + script/test/TestLayout.py | 21 + script/test/TestMicrohawk.py | 10 + script/test/TestRecover.py | 15 + script/test/TestRelays.py | 12 + script/test/TestRemoveBackground.py | 31 + script/test/TestRobot.py | 41 + script/test/TestRobot2.py | 72 + script/test/TestRobotCmds.py | 32 + script/test/TestRobotCmds2.py | 25 + script/test/cycle_time | 4 + script/test/imgtest.py | 47 + script/test/ip | 15 + script/test/mount_profile.py | 144 ++ script/test/onewire.py | 122 ++ script/test/test.py | 24 + script/test/test_hexiposi.py | 8 + script/test/test_segments.py | 16 + script/test/test_swingutils.py | 24 + script/test/then.py | 3 + script/test/transfer_profile.py | 23 + script/test/unmount_profile.py | 98 + script/tools/CheckGripper.py | 23 + script/tools/CheckPuckDetection.py | 6 + script/tools/Math.py | 68 + script/tools/RestartPuckDetection.py | 8 + script/tools/SshExec.py | 61 + script/tools/StopPuckDetection.py | 8 + 251 files changed, 19362 insertions(+) create mode 100644 config/devices - Copy.properties create mode 100644 config/devices-modbus.properties create mode 100644 config/devices.properties create mode 100644 config/jcae.properties create mode 100644 config/mail.properties create mode 100644 config/plugins.properties create mode 100644 config/settings.properties create mode 100644 config/setup-modbus.properties create mode 100644 config/setup.properties create mode 100644 config/tasks.properties create mode 100644 config/variables.properties create mode 100644 devices/20161117_163816.png.properties create mode 100644 devices/A1.properties create mode 100644 devices/A2.properties create mode 100644 devices/A3.properties create mode 100644 devices/A4.properties create mode 100644 devices/A5.properties create mode 100644 devices/B1.properties create mode 100644 devices/B2.properties create mode 100644 devices/B3.properties create mode 100644 devices/B4.properties create mode 100644 devices/B5.properties create mode 100644 devices/BasePlate.properties create mode 100644 devices/C1.properties create mode 100644 devices/C2.properties create mode 100644 devices/C3.properties create mode 100644 devices/C4.properties create mode 100644 devices/C5.properties create mode 100644 devices/D1.properties create mode 100644 devices/D2.properties create mode 100644 devices/D3.properties create mode 100644 devices/D4.properties create mode 100644 devices/D5.properties create mode 100644 devices/E1.properties create mode 100644 devices/E2.properties create mode 100644 devices/E3.properties create mode 100644 devices/E4.properties create mode 100644 devices/E5.properties create mode 100644 devices/F1.properties create mode 100644 devices/F2.properties create mode 100644 devices/F3.properties create mode 100644 devices/F4.properties create mode 100644 devices/F5.properties create mode 100644 devices/R1.properties create mode 100644 devices/R2.properties create mode 100644 devices/R3.properties create mode 100644 devices/R4.properties create mode 100644 devices/R5.properties create mode 100644 devices/RoomTemperatureBasePlate.properties create mode 100644 devices/Time.properties create mode 100644 devices/air_pressure.properties create mode 100644 devices/cx.properties create mode 100644 devices/cz.properties create mode 100644 devices/dewar_level.properties create mode 100644 devices/dispatcher.properties create mode 100644 devices/dp1.properties create mode 100644 devices/fx.properties create mode 100644 devices/fy.properties create mode 100644 devices/gripper_cam.properties create mode 100644 devices/img.properties create mode 100644 devices/led_ctrl.properties create mode 100644 devices/led_ctrl_1.properties create mode 100644 devices/led_ctrl_2.properties create mode 100644 devices/led_ctrl_3.properties create mode 100644 devices/led_level.properties create mode 100644 devices/m1.properties create mode 100644 devices/m2.properties create mode 100644 devices/monitoring_cam.properties create mode 100644 devices/n2_pressure.properties create mode 100644 devices/p1.properties create mode 100644 devices/phase_separator.properties create mode 100644 devices/phase_separator_level.properties create mode 100644 devices/rim_heater_temp.properties create mode 100644 devices/robot x.properties create mode 100644 devices/robot.properties create mode 100644 devices/robot_j1.properties create mode 100644 devices/robot_j2.properties create mode 100644 devices/robot_j3.properties create mode 100644 devices/robot_j4.properties create mode 100644 devices/robot_j5.properties create mode 100644 devices/robot_j6.properties create mode 100644 devices/robot_modbus.properties create mode 100644 devices/robot_rx.properties create mode 100644 devices/robot_ry.properties create mode 100644 devices/robot_rz.properties create mode 100644 devices/robot_x.properties create mode 100644 devices/robot_y.properties create mode 100644 devices/robot_z.properties create mode 100644 devices/ry.properties create mode 100644 devices/smart_magnet.properties create mode 100644 devices/smc_current.properties create mode 100644 devices/smc_current_rb.properties create mode 100644 devices/src1.properties create mode 100644 devices/src2.properties create mode 100644 devices/top_cam.properties create mode 100644 devices/ue.properties create mode 100644 devices/wago.properties create mode 100644 plugins/BarcodeReaderPanel.form create mode 100644 plugins/BarcodeReaderPanel.java create mode 100644 plugins/Beeper.java create mode 100644 plugins/Commands.form create mode 100644 plugins/Commands.java create mode 100644 plugins/Hexiposi.form create mode 100644 plugins/Hexiposi.java create mode 100644 plugins/HexiposiPanel.form create mode 100644 plugins/HexiposiPanel.java create mode 100644 plugins/LN2.form create mode 100644 plugins/LN2.java create mode 100644 plugins/LaserUE.java create mode 100644 plugins/LaserUEPanel.form create mode 100644 plugins/LaserUEPanel.java create mode 100644 plugins/MXSC-1.14.0.jar create mode 100644 plugins/MjpegSource2.java create mode 100644 plugins/PuckDetectionPanel.form create mode 100644 plugins/PuckDetectionPanel.java create mode 100644 plugins/Recovery.form create mode 100644 plugins/Recovery.java create mode 100644 plugins/RobotModbus.java create mode 100644 plugins/RobotPanel.form create mode 100644 plugins/RobotPanel.java create mode 100644 plugins/RobotTcp.java create mode 100644 plugins/SmartMagnetConfig.java create mode 100644 plugins/SmartMagnetPanel.form create mode 100644 plugins/SmartMagnetPanel.java create mode 100644 plugins/TestZMQ.java create mode 100644 plugins/WagoPanel.form create mode 100644 plugins/WagoPanel.java create mode 100644 plugins/gui.form create mode 100644 plugins/gui.java create mode 100644 script/LN2_Monitoring.scd create mode 100644 script/LevelMonitoring.scd create mode 100644 script/calibration/BinarySearchYZ.py create mode 100644 script/calibration/HillClimbingXZ.py create mode 100644 script/calibration/ScanRZ.py create mode 100644 script/calibration/ScanX.py create mode 100644 script/calibration/ScanY.py create mode 100644 script/calibration/ScanYZ.py create mode 100644 script/calibration/ToolCalibration.py create mode 100644 script/calibration/ToolCalibration2.py create mode 100644 script/calibration/ToolCalibration3.py create mode 100644 script/calibration/ToolCalibration6d.py create mode 100644 script/calibration/ToolCalibration6s.py create mode 100644 script/client/PShellClient.py create mode 100644 script/client/TellClient.py create mode 100644 script/client/sseclient.py create mode 100644 script/client/tell.py create mode 100644 script/data/get_samples_info.py create mode 100644 script/data/pucks.py create mode 100644 script/data/reports.py create mode 100644 script/data/samples.py create mode 100644 script/data/set_samples_info.py create mode 100644 script/devices/BarcodeReader.py create mode 100644 script/devices/Gonio.py create mode 100644 script/devices/Hexiposi.py create mode 100644 script/devices/LaserDistance.py create mode 100644 script/devices/LedCtrl.py create mode 100644 script/devices/OneWire.py create mode 100644 script/devices/RobotModbus.py create mode 100644 script/devices/RobotMotors.py create mode 100644 script/devices/RobotSC.py create mode 100644 script/devices/RobotTCP.py create mode 100644 script/devices/SmartMagnet.py create mode 100644 script/devices/Wago.py create mode 100644 script/hexiposi_positon.scd create mode 100644 script/imgproc/CameraCalibration.py create mode 100644 script/imgproc/CoverDetection.py create mode 100644 script/imgproc/CoverDetectionCalibration.py create mode 100644 script/imgproc/CreateMask.py create mode 100644 script/imgproc/LedDetectionFilter.py create mode 100644 script/imgproc/LedDetectionProc.py create mode 100644 script/imgproc/Utils.py create mode 100644 script/local.groovy create mode 100644 script/local.js create mode 100644 script/local.py create mode 100644 script/motion/calibrate_tool.py create mode 100644 script/motion/dry.py create mode 100644 script/motion/get_aux.py create mode 100644 script/motion/get_dewar.py create mode 100644 script/motion/get_gonio.py create mode 100644 script/motion/homing_hexiposi.py create mode 100644 script/motion/mount.py create mode 100644 script/motion/move_aux.py create mode 100644 script/motion/move_cold.py create mode 100644 script/motion/move_dewar.py create mode 100644 script/motion/move_gonio.py create mode 100644 script/motion/move_heater.py create mode 100644 script/motion/move_home.py create mode 100644 script/motion/move_park.py create mode 100644 script/motion/move_scanner.py create mode 100644 script/motion/put_aux.py create mode 100644 script/motion/put_dewar.py create mode 100644 script/motion/put_gonio.py create mode 100644 script/motion/recover.py create mode 100644 script/motion/robot_recover.py create mode 100644 script/motion/scan_pin.py create mode 100644 script/motion/tools.py create mode 100644 script/motion/trash.py create mode 100644 script/motion/unmount.py create mode 100644 script/setup/ExposureScan.py create mode 100644 script/setup/Layout.py create mode 100644 script/tasks/ColdPositionTimeout.py create mode 100644 script/tasks/LedMonitoring.py create mode 100644 script/tasks/MangnetMonitoring.py create mode 100644 script/test/CameraCalibration.py create mode 100644 script/test/CoverDetection.py create mode 100644 script/test/PuckDetection.py create mode 100644 script/test/PuckDetectionTest.py create mode 100644 script/test/RobotCartesianScan.py create mode 100644 script/test/SampleDataInput.py create mode 100644 script/test/SampleDataInput_Dominik.py create mode 100644 script/test/SampleDetection.py create mode 100644 script/test/TestAlign.py create mode 100644 script/test/TestBugPcAPI create mode 100644 script/test/TestBugPcAPI.py create mode 100644 script/test/TestBugPcAPI2.py create mode 100644 script/test/TestBugPcAPI3.py create mode 100644 script/test/TestCalib.py create mode 100644 script/test/TestCameraStability1.py create mode 100644 script/test/TestCameraStability2.py create mode 100644 script/test/TestCameraStability3.py create mode 100644 script/test/TestCmdSynchronization.py create mode 100644 script/test/TestCoverDetection.py create mode 100644 script/test/TestEuclidean.py create mode 100644 script/test/TestLaserScan.py create mode 100644 script/test/TestLayout.py create mode 100644 script/test/TestMicrohawk.py create mode 100644 script/test/TestRecover.py create mode 100644 script/test/TestRelays.py create mode 100644 script/test/TestRemoveBackground.py create mode 100644 script/test/TestRobot.py create mode 100644 script/test/TestRobot2.py create mode 100644 script/test/TestRobotCmds.py create mode 100644 script/test/TestRobotCmds2.py create mode 100644 script/test/cycle_time create mode 100644 script/test/imgtest.py create mode 100644 script/test/ip create mode 100644 script/test/mount_profile.py create mode 100644 script/test/onewire.py create mode 100644 script/test/test.py create mode 100644 script/test/test_hexiposi.py create mode 100644 script/test/test_segments.py create mode 100644 script/test/test_swingutils.py create mode 100644 script/test/then.py create mode 100644 script/test/transfer_profile.py create mode 100644 script/test/unmount_profile.py create mode 100644 script/tools/CheckGripper.py create mode 100644 script/tools/CheckPuckDetection.py create mode 100644 script/tools/Math.py create mode 100644 script/tools/RestartPuckDetection.py create mode 100644 script/tools/SshExec.py create mode 100644 script/tools/StopPuckDetection.py diff --git a/config/devices - Copy.properties b/config/devices - Copy.properties new file mode 100644 index 0000000..cc3672f --- /dev/null +++ b/config/devices - Copy.properties @@ -0,0 +1,67 @@ +img=ch.psi.pshell.prosilica.Prosilica|25001 "PacketSize=1522;PixelFormat=Mono8;BinningX=1;BinningY=1;RegionX=300;RegionY=200;Width=1000;Height=1000"|||false +gripper_cam=ch.psi.pshell.imaging.MjpegSource|http://129.129.110.114/axis-cgi/mjpg/video.cgi||-1000| +microscan=ch.psi.pshell.serial.TcpDevice|129.129.100.200:2001||| +microscan_cmd=ch.psi.pshell.serial.TcpDevice|129.129.100.200:2003||| +ue=LaserUE|COM4|||false +#robot=RobotTcp|127.0.0.1:1000||| +#onewire=ch.psi.pshell.serial.TcpDevice|129.129.126.83:5000||| +puck_detection=ch.psi.mxsc.PuckDetection|tell-raspberrypi:5556||| +#robot_modbus=ch.psi.pshell.modbus.ModbusTCP|129.129.126.100:502||| +#jf1=ch.psi.pshell.modbus.AnalogInput|robot_modbus 0||100| +#jf2=ch.psi.pshell.modbus.AnalogInput|robot_modbus 1||100| +#jf3=ch.psi.pshell.modbus.AnalogInput|robot_modbus 2||100| +#jf4=ch.psi.pshell.modbus.AnalogInput|robot_modbus 3||100| +#jf5=ch.psi.pshell.modbus.AnalogInput|robot_modbus 4||100| +#jf6=ch.psi.pshell.modbus.AnalogInput|robot_modbus 5||100| +#robot_sts=ch.psi.pshell.modbus.AnalogInputArray|robot_modbus 6 6||100| +#robot_cmd=ch.psi.pshell.modbus.AnalogOutput|robot_modbus 12||| +#robot_args=ch.psi.pshell.modbus.AnalogOutputArray|robot_modbus 47 12||| +#robot_req=ch.psi.pshell.modbus.AnalogOutput|robot_modbus 13||| +#robot_ack=ch.psi.pshell.modbus.AnalogInput|robot_modbus 14||| +#robot_ret=ch.psi.pshell.modbus.AnalogInputArray|robot_modbus 15 12||| +#wago_back=ch.psi.pshell.modbus.ModbusTCP|SF-TEST-WAGO1:502||| +wago=ch.psi.pshell.modbus.ModbusTCP|wago-mxsc-1:502||| +led_ok_1=ch.psi.pshell.modbus.DigitalInput|wago 0||1000| +led_ok_2=ch.psi.pshell.modbus.DigitalInput|wago 1||1000| +led_ok_3=ch.psi.pshell.modbus.DigitalInput|wago 2||1000| +feedback_local_safety=ch.psi.pshell.modbus.DigitalInput|wago 3||1000| +feedback_psys_safety=ch.psi.pshell.modbus.DigitalInput|wago 4||1000| +filling_phase_separator=ch.psi.pshell.modbus.DigitalInput|wago 5||1000| +filling_dewar=ch.psi.pshell.modbus.DigitalInput|wago 6||1000| +dewar_level_high_alarm=ch.psi.pshell.modbus.DigitalInput|wago 7||1000| +guiding_tool_park=ch.psi.pshell.modbus.DigitalInput|wago 8||1000| +air_pressure_ok=ch.psi.pshell.modbus.DigitalInput|wago 9||1000|false +n2_pressure_ok=ch.psi.pshell.modbus.DigitalInput|wago 10||1000| +he_chamber_valve_1=ch.psi.pshell.modbus.DigitalInput|wago 15||1000| +he_chamber_valve_2=ch.psi.pshell.modbus.DigitalInput|wago 16||1000| +relays=ch.psi.pshell.modbus.DigitalOutputArray|wago 0 16||1000| +release_local_safety=ch.psi.pshell.modbus.DigitalOutput|wago 0||| +release_psys_safety=ch.psi.pshell.modbus.DigitalOutput|wago 1||| +ln2_main_power=ch.psi.pshell.modbus.DigitalOutput|wago 2||| +rim_heater=ch.psi.pshell.modbus.DigitalOutput|wago 3||| +phase_separator_ln2=ch.psi.pshell.modbus.DigitalOutput|wago 4||| +dewar_ln2=ch.psi.pshell.modbus.DigitalOutput|wago 5|||false +valve_he_chamber=ch.psi.pshell.modbus.DigitalOutput|wago 6||| +gripper_dryer=ch.psi.pshell.modbus.DigitalOutput|wago 7||| +valve_1=ch.psi.pshell.modbus.DigitalOutput|wago 8||| +valve_2=ch.psi.pshell.modbus.DigitalOutput|wago 9||| +valve_3=ch.psi.pshell.modbus.DigitalOutput|wago 10||| +valve_4=ch.psi.pshell.modbus.DigitalOutput|wago 11||| +#spare_do_1=ch.psi.pshell.modbus.DigitalOutput|wago 12||| +#spare_do_2=ch.psi.pshell.modbus.DigitalOutput|wago 13||| +#spare_do_3=ch.psi.pshell.modbus.DigitalOutput|wago 14||| +#spare_do_4=ch.psi.pshell.modbus.DigitalOutput|wago 15||| +phase_separator_level=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 0||10000| +dewar_level=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 1||10000| +rim_heater_temp=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 2||10000| +air_pressure=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 3||10000| +n2_pressure=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 4||10000| +#spare_ai_1=ch.psi.pshell.modbus.AnalogInput|wago 5||| +#spare_ai_2=ch.psi.pshell.modbus.AnalogInput|wago 6||| +#spare_ai_3=ch.psi.pshell.modbus.AnalogInput|wago 7||| +led_ctrl_1=ch.psi.pshell.modbus.ProcessVariable|wago 0||| +led_ctrl_2=ch.psi.pshell.modbus.ProcessVariable|wago 1||| +led_ctrl_3=ch.psi.pshell.modbus.ProcessVariable|wago 2||| +#spare_ao_3=ch.psi.pshell.modbus.AnalogOutput|wago 3||| +#cam=ch.psi.pshell.epics.AreaDetector|MX-SAMCAM||| +#img_back=ch.psi.pshell.imaging.CameraSource|cam||-100| diff --git a/config/devices-modbus.properties b/config/devices-modbus.properties new file mode 100644 index 0000000..3faf1af --- /dev/null +++ b/config/devices-modbus.properties @@ -0,0 +1,42 @@ +wago=ch.psi.pshell.modbus.ModbusTCP|tell6d-wago:502||| +led_ok_1=ch.psi.pshell.modbus.DigitalInput|wago 0||1000| +led_ok_2=ch.psi.pshell.modbus.DigitalInput|wago 1||1000| +led_ok_3=ch.psi.pshell.modbus.DigitalInput|wago 2||1000| +feedback_local_safety=ch.psi.pshell.modbus.DigitalInput|wago 3||1000| +feedback_psys_safety=ch.psi.pshell.modbus.DigitalInput|wago 4||1000| +filling_dewar=ch.psi.pshell.modbus.DigitalInput|wago 5||1000| +dewar_level_high_alarm=ch.psi.pshell.modbus.DigitalInput|wago 6||1000| +guiding_tool_park=ch.psi.pshell.modbus.DigitalInput|wago 7||1000| +air_pressure_ok=ch.psi.pshell.modbus.DigitalInput|wago 8||1000|false +n2_pressure_ok=ch.psi.pshell.modbus.DigitalInput|wago 9||1000| +smc_magnet_status=ch.psi.pshell.modbus.DigitalInput|wago 10||1000| +smc_mounted_1=ch.psi.pshell.modbus.DigitalInput|wago 11||1000| +smc_mounted_2=ch.psi.pshell.modbus.DigitalInput|wago 12||1000| +magnet_release=ch.psi.pshell.modbus.DigitalInput|wago 13||500| +spare_di_2=ch.psi.pshell.modbus.DigitalInput|wago 14||500| +spare_di_3=ch.psi.pshell.modbus.DigitalInput|wago 15||500| +spare_di_4=ch.psi.pshell.modbus.DigitalInput|wago 16||500| +relays=ch.psi.pshell.modbus.DigitalOutputArray|wago 0 16||1000| +release_local_safety=ch.psi.pshell.modbus.DigitalOutput|wago 0||| +release_psys_safety=ch.psi.pshell.modbus.DigitalOutput|wago 1||| +#spare_do_1=ch.psi.pshell.modbus.DigitalOutput|wago 2||| +#spare_do_2=ch.psi.pshell.modbus.DigitalOutput|wago 3||| +#spare_do_3=ch.psi.pshell.modbus.DigitalOutput|wago 4||| +gripper_dryer=ch.psi.pshell.modbus.DigitalOutput|wago 5||| +smc_sup_det=ch.psi.pshell.modbus.DigitalOutput|wago 6||| +valve_1=ch.psi.pshell.modbus.DigitalOutput|wago 7||| +valve_2=ch.psi.pshell.modbus.DigitalOutput|wago 8||| +valve_3=ch.psi.pshell.modbus.DigitalOutput|wago 9||| +valve_4=ch.psi.pshell.modbus.DigitalOutput|wago 10||| +dewar_level=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 0||10000| +rim_heater_temp=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 1||10000| +#spare_ai_1=ch.psi.pshell.modbus.AnalogInput|wago 2|||false +#spare_ai_2=ch.psi.pshell.modbus.AnalogInput|wago 3||| +smc_current_rb=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 4||1000| +#spare_ai_3=ch.psi.pshell.modbus.AnalogInput|wago 5||| +#spare_ai_4=ch.psi.pshell.modbus.AnalogInput|wago 6||| +#spare_ai_5=ch.psi.pshell.modbus.AnalogInput|wago 7||| +led_ctrl_1=ch.psi.pshell.modbus.ProcessVariable|wago 0||| +led_ctrl_2=ch.psi.pshell.modbus.ProcessVariable|wago 1||| +led_ctrl_3=ch.psi.pshell.modbus.ProcessVariable|wago 2||| +smc_current=ch.psi.pshell.modbus.ProcessVariable|wago 3||| diff --git a/config/devices.properties b/config/devices.properties new file mode 100644 index 0000000..439fff4 --- /dev/null +++ b/config/devices.properties @@ -0,0 +1,57 @@ +img=ch.psi.pshell.prosilica.Prosilica|204464 "PacketSize=1504;PixelFormat=Mono8;BinningX=1;BinningY=1;RegionX=290;RegionY=130;Width=1000;Height=1000;MulticastEnable=Off"||-200|false +#gripper_cam=ch.psi.pshell.imaging.MjpegSource|http://axis-accc8ea5e463.psi.ch/axis-cgi/mjpg/video.cgi?camera=1 reopen||-200|false +#monitoring_cam=ch.psi.pshell.imaging.MjpegSource|http://axis-accc8ea5e463.psi.ch/axis-cgi/mjpg/video.cgi?camera=2 reopen||-200| +#top_cam=ch.psi.pshell.imaging.MjpegSource|http://axis-accc8ea5e463.psi.ch/axis-cgi/mjpg/video.cgi?camera=3 true||-200| +#cam=ch.psi.pshell.epics.AreaDetector|MX-SAMCAM||| +mscan_pin=ch.psi.pshell.serial.TcpDevice|129.129.110.109:2001||| +mscan_pin_cmd=ch.psi.pshell.serial.TcpDevice|129.129.110.109:2003||| +mscan_puck=ch.psi.pshell.serial.TcpDevice|129.129.110.93:2001||| +mscan_puck_cmd=ch.psi.pshell.serial.TcpDevice|129.129.110.93:2003||| +ue=LaserUE|COM3 50 35|||true +puck_detection=ch.psi.mxsc.PuckDetection|tell6d-raspberrypi:5556||| +wago=ch.psi.pshell.modbus.ModbusTCP|tell6d-wago:502||| +led_ok_1=ch.psi.pshell.modbus.DigitalInput|wago 0||1000| +led_ok_2=ch.psi.pshell.modbus.DigitalInput|wago 1||1000| +led_ok_3=ch.psi.pshell.modbus.DigitalInput|wago 2||1000| +feedback_local_safety=ch.psi.pshell.modbus.DigitalInput|wago 3||1000| +feedback_psys_safety=ch.psi.pshell.modbus.DigitalInput|wago 4||1000| +filling_dewar=ch.psi.pshell.modbus.DigitalInput|wago 5||1000| +dewar_level_high_alarm=ch.psi.pshell.modbus.DigitalInput|wago 6||1000| +guiding_tool_park=ch.psi.pshell.modbus.DigitalInput|wago 7||1000| +air_pressure_ok=ch.psi.pshell.modbus.DigitalInput|wago 8||1000|false +n2_pressure_ok=ch.psi.pshell.modbus.DigitalInput|wago 9||1000| +smc_magnet_status=ch.psi.pshell.modbus.DigitalInput|wago 10||1000| +smc_mounted_1=ch.psi.pshell.modbus.DigitalInput|wago 11||1000| +smc_mounted_2=ch.psi.pshell.modbus.DigitalInput|wago 12||1000| +magnet_release=ch.psi.pshell.modbus.DigitalInput|wago 13||500| +detector_cleared=ch.psi.pshell.modbus.DigitalInput|wago 14||1000| +spare_di_3=ch.psi.pshell.modbus.DigitalInput|wago 15||1000| +spare_di_4=ch.psi.pshell.modbus.DigitalInput|wago 16||1000| +relays=ch.psi.pshell.modbus.DigitalOutputArray|wago 0 16||1000| +release_local_safety=ch.psi.pshell.modbus.DigitalOutput|wago 0||| +release_psys_safety=ch.psi.pshell.modbus.DigitalOutput|wago 1||| +#spare_do_1=ch.psi.pshell.modbus.DigitalOutput|wago 2||| +#spare_do_2=ch.psi.pshell.modbus.DigitalOutput|wago 3||| +#spare_do_3=ch.psi.pshell.modbus.DigitalOutput|wago 4||| +gripper_dryer=ch.psi.pshell.modbus.DigitalOutput|wago 5||| +smc_sup_det=ch.psi.pshell.modbus.DigitalOutput|wago 6||| +valve_1=ch.psi.pshell.modbus.DigitalOutput|wago 7||1000| +valve_2=ch.psi.pshell.modbus.DigitalOutput|wago 10||1000| +valve_3=ch.psi.pshell.modbus.DigitalOutput|wago 8||1000| +valve_4=ch.psi.pshell.modbus.DigitalOutput|wago 11||1000| +valve_5=ch.psi.pshell.modbus.DigitalOutput|wago 9||1000| +valve_6=ch.psi.pshell.modbus.DigitalOutput|wago 12||1000| +valve_7=ch.psi.pshell.modbus.DigitalOutput|wago 13||1000| +valve_8=ch.psi.pshell.modbus.DigitalOutput|wago 14||1000| +dewar_level=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 0||10000| +rim_heater_temp=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 1||10000| +#spare_ai_1=ch.psi.pshell.modbus.AnalogInput|wago 2|||false +#spare_ai_2=ch.psi.pshell.modbus.AnalogInput|wago 3||| +smc_current_rb=ch.psi.pshell.modbus.ReadonlyProcessVariable|wago 4||1000| +#spare_ai_3=ch.psi.pshell.modbus.AnalogInput|wago 5||| +#spare_ai_4=ch.psi.pshell.modbus.AnalogInput|wago 6||| +#spare_ai_5=ch.psi.pshell.modbus.AnalogInput|wago 7||| +led_ctrl_1=ch.psi.pshell.modbus.ProcessVariable|wago 0||| +led_ctrl_2=ch.psi.pshell.modbus.ProcessVariable|wago 1||| +led_ctrl_3=ch.psi.pshell.modbus.ProcessVariable|wago 2||| +smc_current=ch.psi.pshell.modbus.ProcessVariable|wago 3||| diff --git a/config/jcae.properties b/config/jcae.properties new file mode 100644 index 0000000..2bb0691 --- /dev/null +++ b/config/jcae.properties @@ -0,0 +1,11 @@ +#Tue Sep 06 15:10:24 CEST 2016 +ch.psi.jcae.ContextFactory.addressList= +ch.psi.jcae.ContextFactory.serverPort= +ch.psi.jcae.ContextFactory.maxArrayBytes=20000000 +ch.psi.jcae.ChannelFactory.retries=1 +ch.psi.jcae.ChannelFactory.timeout=500 +ch.psi.jcae.impl.DefaultChannelService.retries=2 +ch.psi.jcae.impl.DefaultChannelService.timeout=1000 +ch.psi.jcae.ContextFactory.autoAddressList=true +ch.psi.jcae.ContextFactory.useShellVariables=false +ch.psi.jcae.ContextFactory.addLocalBroadcastInterfaces=false diff --git a/config/mail.properties b/config/mail.properties new file mode 100644 index 0000000..e04b672 --- /dev/null +++ b/config/mail.properties @@ -0,0 +1,9 @@ +#Mon Oct 23 10:53:54 CEST 2017 +auth=None +from= +host= +port=0 +pwd= +smsSuffix=@sms.switch.ch +to= +usr= diff --git a/config/plugins.properties b/config/plugins.properties new file mode 100644 index 0000000..1522a96 --- /dev/null +++ b/config/plugins.properties @@ -0,0 +1,21 @@ +MXSC-1.10.0\ -\ Back.jar=disabled +Commands.java=disabled +Recovery.java=disabled +MXSC-1.10.0.jar=disabled +WagoPanel.java=enabled +LaserUEPanel.java=enabled +BarcodeReaderPanel.java=enabled +PuckDetectionPanel.java=enabled +HexiposiPanel.java=enabled +MjpegSource2.java=enabled +LN2.java=disabled +Hexiposi.java=disabled +RobotPanel.java=enabled +SmartMagnetConfig.java=disabled +SmartMagnetPanel.java=enabled +LaserUE.java=enabled +TestZMQ.java=disabled +RobotModbus.java=disabled +RobotTcp.java=disabled +Beeper.java=disabled +gui.java=disabled diff --git a/config/settings.properties b/config/settings.properties new file mode 100644 index 0000000..fd12ba9 --- /dev/null +++ b/config/settings.properties @@ -0,0 +1,19 @@ +#Tue Sep 08 11:53:06 CEST 2020 +dry_mount_counter=1 +room_temperature_enabled=false +pin_offset=0.0 +puck_types=true +pin_cleaner_timer=60 +imaging_enabled=false +dry_timestamp=1.599556340515E9 +roi_h=1000 +led_level=0.0 +beamline_status_enabled=false +force_dry_mount_count=15 +roi_y=123 +barcode_reader_scan_pucks=false +cold_position_timeout=3600 +force_dry_timeout=2700 +roi_w=1000 +roi_x=289 +valve_control=false diff --git a/config/setup-modbus.properties b/config/setup-modbus.properties new file mode 100644 index 0000000..eb292c1 --- /dev/null +++ b/config/setup-modbus.properties @@ -0,0 +1,18 @@ +#Wed Sep 14 15:16:45 CEST 2016 +configFile={config}/config.properties +configFileDevices={config}/devices-modbus.properties +configFilePlugins={config}/plugins.properties +configFileTasks={config}/tasks.properties +configPath={home}/config +contextPath={outp}/context +dataPath={outp}/data +devicesPath={home}/devices +extensionsPath={home}/extensions +imagesPath={outp}/images +libraryPath={script}; {script}/Lib +logPath={outp}/log +pluginsPath={home}/plugins +scriptPath={home}/script +scriptType=py +sessionsPath={outp}/sessions +wwwPath={home}/www diff --git a/config/setup.properties b/config/setup.properties new file mode 100644 index 0000000..7ad15eb --- /dev/null +++ b/config/setup.properties @@ -0,0 +1,20 @@ +#Tue Sep 17 15:06:05 CEST 2019 +configFile={config}/config.properties +configFileDevices={config}/devices.properties +configFilePlugins={config}/plugins.properties +configFileSettings={config}/settings.properties +configFileTasks={config}/tasks.properties +configFileVariables={config}/variables.properties +configPath={home}/config +contextPath={outp}/context +dataPath={outp}/data +devicesPath={home}/devices +extensionsPath={home}/extensions +imagesPath={outp}/images +libraryPath={script}; {script}/Lib +logPath={outp}/log +pluginsPath={home}/plugins +scriptPath={home}/script +scriptType=py +sessionsPath={outp}/sessions +wwwPath={home}/www diff --git a/config/tasks.properties b/config/tasks.properties new file mode 100644 index 0000000..92f138b --- /dev/null +++ b/config/tasks.properties @@ -0,0 +1,2 @@ +tasks/LedMonitoring=120 +tasks/ColdPositionTimeout=300 diff --git a/config/variables.properties b/config/variables.properties new file mode 100644 index 0000000..d3aee7a --- /dev/null +++ b/config/variables.properties @@ -0,0 +1,2 @@ +#Tue Sep 01 09:11:47 CEST 2020 +FileSequentialNumber=184 diff --git a/devices/20161117_163816.png.properties b/devices/20161117_163816.png.properties new file mode 100644 index 0000000..be1fd91 --- /dev/null +++ b/devices/20161117_163816.png.properties @@ -0,0 +1,20 @@ +#Mon Oct 23 15:10:20 CEST 2017 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/A1.properties b/devices/A1.properties new file mode 100644 index 0000000..d9b32f1 --- /dev/null +++ b/devices/A1.properties @@ -0,0 +1,3 @@ +#Fri Jan 31 16:58:30 CET 2020 +detection=Mechanical +disabled=false diff --git a/devices/A2.properties b/devices/A2.properties new file mode 100644 index 0000000..ce6347e --- /dev/null +++ b/devices/A2.properties @@ -0,0 +1,3 @@ +#Thu Jun 06 15:47:03 CEST 2019 +detection=Mechanical +disabled=false diff --git a/devices/A3.properties b/devices/A3.properties new file mode 100644 index 0000000..d21184c --- /dev/null +++ b/devices/A3.properties @@ -0,0 +1,3 @@ +#Tue Jan 21 09:46:43 CET 2020 +detection=Mechanical +disabled=false diff --git a/devices/A4.properties b/devices/A4.properties new file mode 100644 index 0000000..502a5d0 --- /dev/null +++ b/devices/A4.properties @@ -0,0 +1,3 @@ +#Thu Jun 06 15:40:34 CEST 2019 +detection=Mechanical +disabled=false diff --git a/devices/A5.properties b/devices/A5.properties new file mode 100644 index 0000000..adcc5f3 --- /dev/null +++ b/devices/A5.properties @@ -0,0 +1,3 @@ +#Tue Jan 21 09:46:49 CET 2020 +detection=Mechanical +disabled=false diff --git a/devices/B1.properties b/devices/B1.properties new file mode 100644 index 0000000..22f2102 --- /dev/null +++ b/devices/B1.properties @@ -0,0 +1,3 @@ +#Mon Apr 29 16:18:33 CEST 2019 +detection=Mechanical +disabled=false diff --git a/devices/B2.properties b/devices/B2.properties new file mode 100644 index 0000000..ea802c8 --- /dev/null +++ b/devices/B2.properties @@ -0,0 +1,3 @@ +#Mon Apr 29 16:40:00 CEST 2019 +detection=Mechanical +disabled=false diff --git a/devices/B3.properties b/devices/B3.properties new file mode 100644 index 0000000..ad0f99d --- /dev/null +++ b/devices/B3.properties @@ -0,0 +1,3 @@ +#Tue Sep 01 14:03:17 CEST 2020 +detection=Mechanical +disabled=false diff --git a/devices/B4.properties b/devices/B4.properties new file mode 100644 index 0000000..074e71d --- /dev/null +++ b/devices/B4.properties @@ -0,0 +1,3 @@ +#Sat Dec 14 17:27:09 CET 2019 +detection=Mechanical +disabled=false diff --git a/devices/B5.properties b/devices/B5.properties new file mode 100644 index 0000000..f768e0f --- /dev/null +++ b/devices/B5.properties @@ -0,0 +1,3 @@ +#Wed Dec 11 08:55:08 CET 2019 +detection=Mechanical +disabled=false diff --git a/devices/BasePlate.properties b/devices/BasePlate.properties new file mode 100644 index 0000000..1e25f83 --- /dev/null +++ b/devices/BasePlate.properties @@ -0,0 +1 @@ +#Fri Feb 10 14:35:38 CET 2017 diff --git a/devices/C1.properties b/devices/C1.properties new file mode 100644 index 0000000..52198c1 --- /dev/null +++ b/devices/C1.properties @@ -0,0 +1,3 @@ +#Tue Sep 01 08:37:42 CEST 2020 +detection=Mechanical +disabled=false diff --git a/devices/C2.properties b/devices/C2.properties new file mode 100644 index 0000000..fbcc31c --- /dev/null +++ b/devices/C2.properties @@ -0,0 +1,3 @@ +#Tue Sep 01 13:18:50 CEST 2020 +detection=Mechanical +disabled=false diff --git a/devices/C3.properties b/devices/C3.properties new file mode 100644 index 0000000..5210a65 --- /dev/null +++ b/devices/C3.properties @@ -0,0 +1,3 @@ +#Thu Sep 19 08:23:37 CEST 2019 +detection=Mechanical +disabled=false diff --git a/devices/C4.properties b/devices/C4.properties new file mode 100644 index 0000000..61fba0c --- /dev/null +++ b/devices/C4.properties @@ -0,0 +1,3 @@ +#Wed Dec 11 08:56:26 CET 2019 +detection=Mechanical +disabled=false diff --git a/devices/C5.properties b/devices/C5.properties new file mode 100644 index 0000000..354cfeb --- /dev/null +++ b/devices/C5.properties @@ -0,0 +1,3 @@ +#Thu Sep 19 08:23:43 CEST 2019 +detection=Mechanical +disabled=false diff --git a/devices/D1.properties b/devices/D1.properties new file mode 100644 index 0000000..d83b456 --- /dev/null +++ b/devices/D1.properties @@ -0,0 +1,3 @@ +#Thu Sep 19 08:23:48 CEST 2019 +detection=Mechanical +disabled=false diff --git a/devices/D2.properties b/devices/D2.properties new file mode 100644 index 0000000..a5df751 --- /dev/null +++ b/devices/D2.properties @@ -0,0 +1,3 @@ +#Tue Sep 08 09:28:59 CEST 2020 +detection=Mechanical +disabled=false diff --git a/devices/D3.properties b/devices/D3.properties new file mode 100644 index 0000000..2e933c9 --- /dev/null +++ b/devices/D3.properties @@ -0,0 +1,3 @@ +#Tue Sep 08 09:29:02 CEST 2020 +detection=Mechanical +disabled=false diff --git a/devices/D4.properties b/devices/D4.properties new file mode 100644 index 0000000..1be19b4 --- /dev/null +++ b/devices/D4.properties @@ -0,0 +1,3 @@ +#Tue Sep 08 09:29:05 CEST 2020 +detection=Mechanical +disabled=false diff --git a/devices/D5.properties b/devices/D5.properties new file mode 100644 index 0000000..9d34242 --- /dev/null +++ b/devices/D5.properties @@ -0,0 +1,3 @@ +#Tue Sep 08 09:29:08 CEST 2020 +detection=Mechanical +disabled=false diff --git a/devices/E1.properties b/devices/E1.properties new file mode 100644 index 0000000..861f9f8 --- /dev/null +++ b/devices/E1.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 14:28:32 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/E2.properties b/devices/E2.properties new file mode 100644 index 0000000..34dabc7 --- /dev/null +++ b/devices/E2.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:15 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/E3.properties b/devices/E3.properties new file mode 100644 index 0000000..a133c30 --- /dev/null +++ b/devices/E3.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:19 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/E4.properties b/devices/E4.properties new file mode 100644 index 0000000..818280c --- /dev/null +++ b/devices/E4.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:24 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/E5.properties b/devices/E5.properties new file mode 100644 index 0000000..7fb793f --- /dev/null +++ b/devices/E5.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:29 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/F1.properties b/devices/F1.properties new file mode 100644 index 0000000..b9fd935 --- /dev/null +++ b/devices/F1.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 11:31:09 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/F2.properties b/devices/F2.properties new file mode 100644 index 0000000..12b665e --- /dev/null +++ b/devices/F2.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:39 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/F3.properties b/devices/F3.properties new file mode 100644 index 0000000..321743f --- /dev/null +++ b/devices/F3.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:44 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/F4.properties b/devices/F4.properties new file mode 100644 index 0000000..a0e68f2 --- /dev/null +++ b/devices/F4.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:47 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/F5.properties b/devices/F5.properties new file mode 100644 index 0000000..2c095dc --- /dev/null +++ b/devices/F5.properties @@ -0,0 +1,3 @@ +#Fri Aug 14 10:51:50 CEST 2020 +detection=Mechanical +disabled=true diff --git a/devices/R1.properties b/devices/R1.properties new file mode 100644 index 0000000..fa91122 --- /dev/null +++ b/devices/R1.properties @@ -0,0 +1,3 @@ +#Tue Feb 19 12:07:25 CET 2019 +detection=Both +disabled=false diff --git a/devices/R2.properties b/devices/R2.properties new file mode 100644 index 0000000..fa91122 --- /dev/null +++ b/devices/R2.properties @@ -0,0 +1,3 @@ +#Tue Feb 19 12:07:25 CET 2019 +detection=Both +disabled=false diff --git a/devices/R3.properties b/devices/R3.properties new file mode 100644 index 0000000..fa91122 --- /dev/null +++ b/devices/R3.properties @@ -0,0 +1,3 @@ +#Tue Feb 19 12:07:25 CET 2019 +detection=Both +disabled=false diff --git a/devices/R4.properties b/devices/R4.properties new file mode 100644 index 0000000..fa91122 --- /dev/null +++ b/devices/R4.properties @@ -0,0 +1,3 @@ +#Tue Feb 19 12:07:25 CET 2019 +detection=Both +disabled=false diff --git a/devices/R5.properties b/devices/R5.properties new file mode 100644 index 0000000..fa91122 --- /dev/null +++ b/devices/R5.properties @@ -0,0 +1,3 @@ +#Tue Feb 19 12:07:25 CET 2019 +detection=Both +disabled=false diff --git a/devices/RoomTemperatureBasePlate.properties b/devices/RoomTemperatureBasePlate.properties new file mode 100644 index 0000000..a8e063a --- /dev/null +++ b/devices/RoomTemperatureBasePlate.properties @@ -0,0 +1 @@ +#Tue Feb 19 12:07:25 CET 2019 diff --git a/devices/Time.properties b/devices/Time.properties new file mode 100644 index 0000000..33011f0 --- /dev/null +++ b/devices/Time.properties @@ -0,0 +1,10 @@ +#Wed Jun 27 10:48:14 CEST 2018 +maxValue=NaN +minValue=NaN +offset=0.0 +precision=-1 +resolution=NaN +rotation=false +scale=1.0 +sign_bit=0 +unit=null diff --git a/devices/air_pressure.properties b/devices/air_pressure.properties new file mode 100644 index 0000000..e476b09 --- /dev/null +++ b/devices/air_pressure.properties @@ -0,0 +1,5 @@ +#Thu Mar 01 11:13:34 CET 2018 +offset=0.0 +precision=-1 +scale=1.0 +unit=null diff --git a/devices/cx.properties b/devices/cx.properties new file mode 100644 index 0000000..1720045 --- /dev/null +++ b/devices/cx.properties @@ -0,0 +1,17 @@ +#Fri Aug 10 15:47:53 CEST 2018 +defaultSpeed=2.0 +estbilizationDelay=0 +hasEnable=false +homingType=None +maxSpeed=NaN +maxValue=0.0 +minSpeed=NaN +minValue=0.0 +offset=0.0 +precision=3 +resolution=0.001 +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/cz.properties b/devices/cz.properties new file mode 100644 index 0000000..b8877dd --- /dev/null +++ b/devices/cz.properties @@ -0,0 +1,17 @@ +#Fri Aug 10 15:47:15 CEST 2018 +defaultSpeed=2.0 +estbilizationDelay=0 +hasEnable=false +homingType=None +maxSpeed=NaN +maxValue=0.0 +minSpeed=NaN +minValue=0.0 +offset=0.0 +precision=3 +resolution=0.001 +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/dewar_level.properties b/devices/dewar_level.properties new file mode 100644 index 0000000..ac55365 --- /dev/null +++ b/devices/dewar_level.properties @@ -0,0 +1,6 @@ +#Tue Jan 22 10:58:09 CET 2019 +offset=0.0 +precision=2 +scale=0.0032 +sign_bit=0 +unit=% diff --git a/devices/dispatcher.properties b/devices/dispatcher.properties new file mode 100644 index 0000000..5a3e672 --- /dev/null +++ b/devices/dispatcher.properties @@ -0,0 +1,11 @@ +#Thu May 03 11:54:38 CEST 2018 +disableCompression=false +keepListeningOnStop=false +mappingIncomplete=null +parallelHandlerProcessing=true +sendAwaitFirstMessage=false +sendBuildChannelConfig=null +sendStrategy=null +sendSyncTimeout=0 +socketType=DEFAULT +validationInconsistency=null diff --git a/devices/dp1.properties b/devices/dp1.properties new file mode 100644 index 0000000..c853c11 --- /dev/null +++ b/devices/dp1.properties @@ -0,0 +1,11 @@ +#Fri Jul 08 10:53:26 CEST 2016 +motor1=0.0|4.0|8.0|0.0 +motor2=0.0|5.0|3.0|NaN +motor3=null +motor4=null +motor5=null +motor6=null +motor7=null +motor8=null +positions=Park|Ready|Out|Clear +precision=-1 diff --git a/devices/fx.properties b/devices/fx.properties new file mode 100644 index 0000000..44a1a35 --- /dev/null +++ b/devices/fx.properties @@ -0,0 +1,17 @@ +#Fri Aug 10 15:47:14 CEST 2018 +defaultSpeed=2.0 +estbilizationDelay=0 +hasEnable=false +homingType=None +maxSpeed=NaN +maxValue=0.0 +minSpeed=NaN +minValue=0.0 +offset=0.0 +precision=3 +resolution=0.001 +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/fy.properties b/devices/fy.properties new file mode 100644 index 0000000..44a1a35 --- /dev/null +++ b/devices/fy.properties @@ -0,0 +1,17 @@ +#Fri Aug 10 15:47:14 CEST 2018 +defaultSpeed=2.0 +estbilizationDelay=0 +hasEnable=false +homingType=None +maxSpeed=NaN +maxValue=0.0 +minSpeed=NaN +minValue=0.0 +offset=0.0 +precision=3 +resolution=0.001 +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/gripper_cam.properties b/devices/gripper_cam.properties new file mode 100644 index 0000000..6fc903f --- /dev/null +++ b/devices/gripper_cam.properties @@ -0,0 +1,20 @@ +#Thu Jun 28 17:46:46 CEST 2018 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/img.properties b/devices/img.properties new file mode 100644 index 0000000..8ef03e0 --- /dev/null +++ b/devices/img.properties @@ -0,0 +1,24 @@ +#Wed Oct 30 16:03:50 CET 2019 +spatialCalOffsetY=-482.0 +invert=false +colormapMin=6.4 +spatialCalOffsetX=-476.0 +rotation=90.5119714057 +rotationCrop=true +scale=1.0 +rescaleFactor=1.0 +grayscale=false +spatialCalUnits=mm +flipVertically=false +roiHeight=964 +spatialCalScaleX=0.49344173073372655 +spatialCalScaleY=0.48738915251539194 +flipHorizontally=false +colormapAutomatic=false +colormapMax=18.133 +roiY=17 +roiX=25 +rescaleOffset=0.0 +roiWidth=952 +transpose=false +colormap=Grayscale diff --git a/devices/led_ctrl.properties b/devices/led_ctrl.properties new file mode 100644 index 0000000..00dcc48 --- /dev/null +++ b/devices/led_ctrl.properties @@ -0,0 +1,8 @@ +#Tue Jun 20 15:07:30 CEST 2017 +maxValue=0.4 +minValue=0.0 +offset=0.0 +precision=2 +resolution=NaN +scale=3.0E-4 +unit=V diff --git a/devices/led_ctrl_1.properties b/devices/led_ctrl_1.properties new file mode 100644 index 0000000..eb9cf02 --- /dev/null +++ b/devices/led_ctrl_1.properties @@ -0,0 +1,9 @@ +#Mon Sep 07 09:39:40 CEST 2020 +minValue=0.0 +unit=V +offset=0.0 +maxValue=1.0 +precision=2 +sign_bit=0 +scale=3.0E-4 +resolution=NaN diff --git a/devices/led_ctrl_2.properties b/devices/led_ctrl_2.properties new file mode 100644 index 0000000..eb9cf02 --- /dev/null +++ b/devices/led_ctrl_2.properties @@ -0,0 +1,9 @@ +#Mon Sep 07 09:39:40 CEST 2020 +minValue=0.0 +unit=V +offset=0.0 +maxValue=1.0 +precision=2 +sign_bit=0 +scale=3.0E-4 +resolution=NaN diff --git a/devices/led_ctrl_3.properties b/devices/led_ctrl_3.properties new file mode 100644 index 0000000..eb9cf02 --- /dev/null +++ b/devices/led_ctrl_3.properties @@ -0,0 +1,9 @@ +#Mon Sep 07 09:39:40 CEST 2020 +minValue=0.0 +unit=V +offset=0.0 +maxValue=1.0 +precision=2 +sign_bit=0 +scale=3.0E-4 +resolution=NaN diff --git a/devices/led_level.properties b/devices/led_level.properties new file mode 100644 index 0000000..52dee03 --- /dev/null +++ b/devices/led_level.properties @@ -0,0 +1,9 @@ +#Tue Jun 19 16:41:24 CEST 2018 +maxValue=NaN +minValue=NaN +offset=0.0 +precision=-1 +resolution=NaN +scale=1.0 +sign_bit=0 +unit=null diff --git a/devices/m1.properties b/devices/m1.properties new file mode 100644 index 0000000..1cf4553 --- /dev/null +++ b/devices/m1.properties @@ -0,0 +1,13 @@ +#Fri Jun 30 09:37:38 CEST 2017 +defaultSpeed=1.0 +estbilizationDelay=0 +maxSpeed=10.0 +maxValue=10.0 +minSpeed=0.1 +minValue=-10.0 +offset=0.0 +precision=2 +resolution=NaN +rotation=false +scale=1.0 +unit=mm diff --git a/devices/m2.properties b/devices/m2.properties new file mode 100644 index 0000000..1cf4553 --- /dev/null +++ b/devices/m2.properties @@ -0,0 +1,13 @@ +#Fri Jun 30 09:37:38 CEST 2017 +defaultSpeed=1.0 +estbilizationDelay=0 +maxSpeed=10.0 +maxValue=10.0 +minSpeed=0.1 +minValue=-10.0 +offset=0.0 +precision=2 +resolution=NaN +rotation=false +scale=1.0 +unit=mm diff --git a/devices/monitoring_cam.properties b/devices/monitoring_cam.properties new file mode 100644 index 0000000..fa56b35 --- /dev/null +++ b/devices/monitoring_cam.properties @@ -0,0 +1,20 @@ +#Thu Aug 09 11:01:09 CEST 2018 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/n2_pressure.properties b/devices/n2_pressure.properties new file mode 100644 index 0000000..e476b09 --- /dev/null +++ b/devices/n2_pressure.properties @@ -0,0 +1,5 @@ +#Thu Mar 01 11:13:34 CET 2018 +offset=0.0 +precision=-1 +scale=1.0 +unit=null diff --git a/devices/p1.properties b/devices/p1.properties new file mode 100644 index 0000000..07d9ba5 --- /dev/null +++ b/devices/p1.properties @@ -0,0 +1,9 @@ +#Fri Jun 30 09:37:38 CEST 2017 +maxValue=1000.0 +minValue=0.0 +offset=0.0 +precision=-1 +resolution=NaN +rotation=false +scale=1.0 +unit=mm diff --git a/devices/phase_separator.properties b/devices/phase_separator.properties new file mode 100644 index 0000000..122f8e9 --- /dev/null +++ b/devices/phase_separator.properties @@ -0,0 +1,8 @@ +#Wed Feb 28 17:35:59 CET 2018 +maxValue=30000.0 +minValue=0.0 +offset=0.0 +precision=-1 +resolution=NaN +scale=0.1 +unit=% diff --git a/devices/phase_separator_level.properties b/devices/phase_separator_level.properties new file mode 100644 index 0000000..92a5279 --- /dev/null +++ b/devices/phase_separator_level.properties @@ -0,0 +1,6 @@ +#Tue Jun 19 16:41:20 CEST 2018 +offset=-25.81632 +precision=2 +scale=0.003888 +sign_bit=0 +unit=% diff --git a/devices/rim_heater_temp.properties b/devices/rim_heater_temp.properties new file mode 100644 index 0000000..e1c3b95 --- /dev/null +++ b/devices/rim_heater_temp.properties @@ -0,0 +1,6 @@ +#Tue Jun 19 16:41:20 CEST 2018 +offset=-25.81632 +precision=2 +scale=0.003888 +sign_bit=0 +unit=C diff --git a/devices/robot x.properties b/devices/robot x.properties new file mode 100644 index 0000000..02ffbf3 --- /dev/null +++ b/devices/robot x.properties @@ -0,0 +1,9 @@ +#Tue Sep 12 15:00:38 CEST 2017 +maxValue=NaN +minValue=NaN +offset=0.0 +precision=-1 +resolution=NaN +rotation=false +scale=1.0 +unit=null diff --git a/devices/robot.properties b/devices/robot.properties new file mode 100644 index 0000000..97f6ad6 --- /dev/null +++ b/devices/robot.properties @@ -0,0 +1,8 @@ +#Thu Jul 21 08:35:47 CEST 2016 +offsetReadAnalogInput=0 +offsetReadAnalogOutput=0 +offsetReadDigitalInput=0 +offsetReadDigitalOutput=0 +offsetWriteAnalogOutput=0 +offsetWriteDigitalOutput=0 +timeout=1000 diff --git a/devices/robot_j1.properties b/devices/robot_j1.properties new file mode 100644 index 0000000..bc3a381 --- /dev/null +++ b/devices/robot_j1.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=180.0 +minValue=-180.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_j2.properties b/devices/robot_j2.properties new file mode 100644 index 0000000..6a892bc --- /dev/null +++ b/devices/robot_j2.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=127.5 +minValue=-127.5 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_j3.properties b/devices/robot_j3.properties new file mode 100644 index 0000000..07bff75 --- /dev/null +++ b/devices/robot_j3.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=152.5 +minValue=-152.5 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_j4.properties b/devices/robot_j4.properties new file mode 100644 index 0000000..41d8586 --- /dev/null +++ b/devices/robot_j4.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=270.0 +minValue=-270.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_j5.properties b/devices/robot_j5.properties new file mode 100644 index 0000000..dab429f --- /dev/null +++ b/devices/robot_j5.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=132.5 +minValue=-122.5 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_j6.properties b/devices/robot_j6.properties new file mode 100644 index 0000000..d688634 --- /dev/null +++ b/devices/robot_j6.properties @@ -0,0 +1,10 @@ +#Fri Aug 24 14:56:42 CEST 2018 +maxValue=400.0 +minValue=-400.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_modbus.properties b/devices/robot_modbus.properties new file mode 100644 index 0000000..31a13be --- /dev/null +++ b/devices/robot_modbus.properties @@ -0,0 +1,8 @@ +#Thu Jul 21 12:02:04 CEST 2016 +offsetReadAnalogInput=0 +offsetReadAnalogOutput=0 +offsetReadDigitalInput=0 +offsetReadDigitalOutput=0 +offsetWriteAnalogOutput=0 +offsetWriteDigitalOutput=0 +timeout=1000 diff --git a/devices/robot_rx.properties b/devices/robot_rx.properties new file mode 100644 index 0000000..df63f03 --- /dev/null +++ b/devices/robot_rx.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=180.0 +minValue=-180.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=true +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_ry.properties b/devices/robot_ry.properties new file mode 100644 index 0000000..a789ebf --- /dev/null +++ b/devices/robot_ry.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=180.0 +minValue=-180.0 +offset=0.0 +precision=-2 +resolution=0.05 +rotation=true +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_rz.properties b/devices/robot_rz.properties new file mode 100644 index 0000000..2b88035 --- /dev/null +++ b/devices/robot_rz.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=360.0 +minValue=-360.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=true +scale=1.0 +sign_bit=0 +unit=deg diff --git a/devices/robot_x.properties b/devices/robot_x.properties new file mode 100644 index 0000000..5cdadcc --- /dev/null +++ b/devices/robot_x.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=1000.0 +minValue=-1000.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=false +scale=1.0 +sign_bit=0 +unit=mm diff --git a/devices/robot_y.properties b/devices/robot_y.properties new file mode 100644 index 0000000..eb8022e --- /dev/null +++ b/devices/robot_y.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=1000.0 +minValue=-1000.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=false +scale=1.0 +sign_bit=0 +unit=null diff --git a/devices/robot_z.properties b/devices/robot_z.properties new file mode 100644 index 0000000..eb8022e --- /dev/null +++ b/devices/robot_z.properties @@ -0,0 +1,10 @@ +#Tue Jun 19 16:41:28 CEST 2018 +maxValue=1000.0 +minValue=-1000.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=false +scale=1.0 +sign_bit=0 +unit=null diff --git a/devices/ry.properties b/devices/ry.properties new file mode 100644 index 0000000..f43c3d2 --- /dev/null +++ b/devices/ry.properties @@ -0,0 +1,17 @@ +#Fri Aug 10 15:47:14 CEST 2018 +defaultSpeed=50.0 +estbilizationDelay=0 +hasEnable=false +homingType=None +maxSpeed=NaN +maxValue=0.0 +minSpeed=NaN +minValue=0.0 +offset=0.0 +precision=3 +resolution=0.001 +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=deg diff --git a/devices/smart_magnet.properties b/devices/smart_magnet.properties new file mode 100644 index 0000000..18a705b --- /dev/null +++ b/devices/smart_magnet.properties @@ -0,0 +1,8 @@ +#Mon Aug 31 09:09:24 CEST 2020 +reverseTime=0.2 +unmountCurrent=20.0 +holdingCurrent=50.0 +restingCurrent=30.0 +reverseCurrent=-10.0 +remanenceCurrent=-10.0 +mountCurrent=30.0 diff --git a/devices/smc_current.properties b/devices/smc_current.properties new file mode 100644 index 0000000..9d9ba17 --- /dev/null +++ b/devices/smc_current.properties @@ -0,0 +1,9 @@ +#Wed Jan 30 11:33:15 CET 2019 +maxValue=70.0 +minValue=-50.0 +offset=0.0 +precision=2 +resolution=NaN +scale=0.003 +sign_bit=0 +unit=mA diff --git a/devices/smc_current_rb.properties b/devices/smc_current_rb.properties new file mode 100644 index 0000000..7709ada --- /dev/null +++ b/devices/smc_current_rb.properties @@ -0,0 +1,6 @@ +#Tue Jun 19 16:45:06 CEST 2018 +offset=0.0 +precision=2 +scale=0.003 +sign_bit=15 +unit=mA diff --git a/devices/src1.properties b/devices/src1.properties new file mode 100644 index 0000000..a0b5d6d --- /dev/null +++ b/devices/src1.properties @@ -0,0 +1,24 @@ +#Thu Sep 22 14:45:06 CEST 2016 +colormap=Temperature +colormapAutomatic=true +colormapMax=255.0 +colormapMin=0.0 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/src2.properties b/devices/src2.properties new file mode 100644 index 0000000..2bf8406 --- /dev/null +++ b/devices/src2.properties @@ -0,0 +1,24 @@ +#Thu Sep 22 14:45:06 CEST 2016 +colormap=Grayscale +colormapAutomatic=true +colormapMax=255.0 +colormapMin=0.0 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/top_cam.properties b/devices/top_cam.properties new file mode 100644 index 0000000..827552b --- /dev/null +++ b/devices/top_cam.properties @@ -0,0 +1,20 @@ +#Thu Aug 09 11:46:12 CEST 2018 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/ue.properties b/devices/ue.properties new file mode 100644 index 0000000..f241f62 --- /dev/null +++ b/devices/ue.properties @@ -0,0 +1,6 @@ +#Thu Aug 31 16:41:09 CEST 2017 +baudRate=9600 +dataBits=DB_8 +parity=None +port=null +stopBits=SB_1 diff --git a/devices/wago.properties b/devices/wago.properties new file mode 100644 index 0000000..f2650ea --- /dev/null +++ b/devices/wago.properties @@ -0,0 +1,8 @@ +#Wed Feb 28 16:27:51 CET 2018 +offsetReadAnalogInput=0 +offsetReadAnalogOutput=0x200 +offsetReadDigitalInput=0 +offsetReadDigitalOutput=0x200 +offsetWriteAnalogOutput=0 +offsetWriteDigitalOutput=0 +timeout=1000 diff --git a/plugins/BarcodeReaderPanel.form b/plugins/BarcodeReaderPanel.form new file mode 100644 index 0000000..1d2c4af --- /dev/null +++ b/plugins/BarcodeReaderPanel.form @@ -0,0 +1,114 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/BarcodeReaderPanel.java b/plugins/BarcodeReaderPanel.java new file mode 100644 index 0000000..d6dabed --- /dev/null +++ b/plugins/BarcodeReaderPanel.java @@ -0,0 +1,170 @@ +import ch.psi.pshell.core.Context; +import ch.psi.pshell.device.Device; +import ch.psi.pshell.swing.DevicePanel; +import ch.psi.utils.swing.SwingUtils; +import java.util.concurrent.CompletableFuture; + +/** + * + */ +public class BarcodeReaderPanel extends DevicePanel { + + volatile boolean enabled; + public BarcodeReaderPanel() { + initComponents(); + this.startTimer(100); + } + + + CompletableFuture future; + + + @Override + public void setDevice(Device device){ + super.setDevice(device); + if (device != null){ + deviceStatePanel1.setDevice(device); + deviceValuePanel1.setDevice(device); + } + } + + @Override + public void onTimer(){ + if ((getDevice()!=null) && enabled){ + if ((future==null) || (future.isDone())){ + future = Context.getInstance().evalLineBackgroundAsync(getDevice().getName() + ".get()"); + } + } + } + + + void execute(String statement, boolean showReturn){ + try { + Context.getInstance().evalLineBackgroundAsync(statement).handle((ret, ex) -> { + if (BarcodeReaderPanel.this.isShowing()){ + if (ex != null){ + showException((Exception)ex); + } else if (showReturn){ + if (ret == null){ + SwingUtils.showMessage(this, "Return", "No code detected", 20000); + } else { + SwingUtils.showMessage(this, "Return", "Detected code: " +ret, 20000); + } + } + } + return ret; + }); + } catch (Exception ex) { + showException(ex); + } + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel3 = new javax.swing.JPanel(); + buttonEnable = new javax.swing.JButton(); + buttonDisable = new javax.swing.JButton(); + buttonRead = new javax.swing.JButton(); + deviceStatePanel1 = new ch.psi.pshell.swing.DeviceStatePanel(); + deviceValuePanel1 = new ch.psi.pshell.swing.DeviceValuePanel(); + + jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Commands")); + + buttonEnable.setText("Enable"); + buttonEnable.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonEnableActionPerformed(evt); + } + }); + + buttonDisable.setText("Disable"); + buttonDisable.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonDisableActionPerformed(evt); + } + }); + + buttonRead.setText("Read"); + buttonRead.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonReadActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonEnable) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonDisable) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonRead) + .addContainerGap()) + ); + + jPanel3Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonDisable, buttonEnable, buttonRead}); + + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonEnable) + .addComponent(buttonDisable) + .addComponent(buttonRead)) + .addContainerGap()) + ); + + deviceValuePanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Value")); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(deviceValuePanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(deviceStatePanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(deviceValuePanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(deviceStatePanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) + ); + }// //GEN-END:initComponents + + private void buttonEnableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonEnableActionPerformed + execute(getDevice().getName() + ".enable()", false); + enabled = true; + }//GEN-LAST:event_buttonEnableActionPerformed + + private void buttonDisableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonDisableActionPerformed + enabled = false; + execute(getDevice().getName() + ".disable()", false); + }//GEN-LAST:event_buttonDisableActionPerformed + + private void buttonReadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonReadActionPerformed + enabled = false; + execute(getDevice().getName() + ".read(5.0) ", true); + }//GEN-LAST:event_buttonReadActionPerformed + + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonDisable; + private javax.swing.JButton buttonEnable; + private javax.swing.JButton buttonRead; + private ch.psi.pshell.swing.DeviceStatePanel deviceStatePanel1; + private ch.psi.pshell.swing.DeviceValuePanel deviceValuePanel1; + private javax.swing.JPanel jPanel3; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/Beeper.java b/plugins/Beeper.java new file mode 100644 index 0000000..c1fea67 --- /dev/null +++ b/plugins/Beeper.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.device.*; +import com.sun.jna.Function; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.win32.StdCallLibrary; +import com.sun.jna.win32.W32APIFunctionMapper; +import com.sun.jna.win32.W32APITypeMapper; +import java.util.HashMap; +import java.util.Map; + +/** + */ +public class Beeper extends DeviceBase { + + public Beeper(String name) { + super(name); + } + + // JNA Mapping + static Map UNICODE_OPTIONS = new HashMap() { + + { + put("type-mapper", W32APITypeMapper.UNICODE); + put("function-mapper", W32APIFunctionMapper.UNICODE); + } + }; + + interface Kernel32 extends StdCallLibrary { + + public static final String LIBRARY_NAME = "kernel32"; + Kernel32 INSTANCE = (Kernel32) Native.loadLibrary(LIBRARY_NAME, Kernel32.class, UNICODE_OPTIONS); + Kernel32 SYNC_INSTANCE = (Kernel32) Native.synchronizedLibrary(INSTANCE); + + boolean Beep(int FREQUENCY, int DURATION); + + void Sleep(int DURATION); + + int CreateEventW(Pointer securityAttributes, boolean manualReset, boolean initialState, String name); + + int CreateThread(/*Pointer*/int lpThreadAttributes, IntByReference dwStackSize, Function lpStartAddress, Structure lpParameter, int dwCreationFlags, IntByReference lpThreadId); + public static final int STILL_ACTIVE = 259; + + int GetExitCodeThread(int hThread, IntByReference lpExitCode); + + int GetLastError(); + + boolean CloseHandle(int handle); + + } + private final int mEventHandle = Kernel32.INSTANCE.CreateEventW(Pointer.NULL, true, false, null); + + + //Public interface + public int getLastError(){ + return Kernel32.SYNC_INSTANCE.GetLastError(); + } + + public void sleep(int duration){ + Kernel32.SYNC_INSTANCE.Sleep(duration); + } + + public void beep(int frequency, int duration){ + Kernel32.SYNC_INSTANCE.Beep(frequency, duration); + } + +} diff --git a/plugins/Commands.form b/plugins/Commands.form new file mode 100644 index 0000000..26eeaf0 --- /dev/null +++ b/plugins/Commands.form @@ -0,0 +1,804 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/Commands.java b/plugins/Commands.java new file mode 100644 index 0000000..fb37d24 --- /dev/null +++ b/plugins/Commands.java @@ -0,0 +1,928 @@ +/* + * Copyright (c) 2014-2017 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.device.Device; +import ch.psi.pshell.device.DeviceAdapter; +import ch.psi.pshell.device.GenericDevice; +import ch.psi.pshell.ui.Panel; +import ch.psi.utils.State; +import ch.psi.utils.swing.SwingUtils; +import java.awt.Component; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JComponent; +import javax.swing.JSpinner; +import javax.swing.JTextField; + +/** + * + */ +public class Commands extends Panel { + + public Commands() { + initComponents(); + ((JSpinner.DefaultEditor)spinnerSegment.getEditor()).getTextField().setHorizontalAlignment(JTextField.CENTER); + ((JSpinner.DefaultEditor)spinnerPuck.getEditor()).getTextField().setHorizontalAlignment(JTextField.CENTER); + ((JSpinner.DefaultEditor)spinnerSample.getEditor()).getTextField().setHorizontalAlignment(JTextField.CENTER); + + + try{ + spinnerDryTime.setValue(getContext().getScriptManager().getVar("DEFAULT_DRY_HEAT_TIME")); + spinnerDrySpeed.setValue(getContext().getScriptManager().getVar("DEFAULT_DRY_SPEED")); + } catch(Exception ex){ + this.showException(ex); + } + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + GenericDevice basePlate = getDevice("BasePlate"); + if (basePlate != null){ + basePlate.addListener(new DeviceAdapter() { + @Override + public void onValueChanged(Device device, Object value, Object former) { + if (value!=null){ + String segment = ((Object[])value)[0].toString(); + spinnerSegment.setValue(segment); + Integer puck = (Integer) ((Object[])value)[1]; + spinnerPuck.setValue(Integer.valueOf(puck)); + Integer sample = (Integer) ((Object[])value)[2]; + if (sample!=null){ + spinnerSample.setValue(Integer.valueOf(sample)); + } + } + } + }); + } + } + + @Override + public void onStateChange(State state, State former) { + boolean enabled = (state == State.Ready); + for (Component c: SwingUtils.getComponentsByType(this, JComponent.class)){ + c.setEnabled(enabled); + } + spinnerSegment.setEnabled(enabled && !checkAuxiliary.isSelected()); + spinnerPuck.setEnabled(enabled && !checkAuxiliary.isSelected()); + buttonScanPuck.setEnabled(enabled && !checkAuxiliary.isSelected()); + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + //Callback to perform update - in event thread + @Override + protected void doUpdate() { + } + + @Override + public boolean isActive() { + return true; + } + + + void execute(String statement){ + execute(statement, false); + } + + void execute(String statement, boolean background){ + execute(statement, background, false); + } + + void execute(String statement, boolean background, boolean showReturn){ + try { + evalAsync(statement, background).handle((ret, ex) -> { + if (ex != null){ + if (getContext().getGlobal("recovering") == null) { + showException((Exception)ex); + } + } else if (showReturn){ + SwingUtils.showMessage(getTopLevel(), "Return", String.valueOf(ret)); + } + return ret; + }); + } catch (Exception ex) { + showException(ex); + } + } + + void execute(String script, Object args){ + try { + runAsync(script, args).handle((ret, ex) -> { + if (ex != null){ + showException((Exception)ex); + } + return ret; + }); + } catch (Exception ex) { + showException(ex); + } + } + + + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jTabbedPane1 = new javax.swing.JTabbedPane(); + tabCommands = new javax.swing.JPanel(); + pnDry = new javax.swing.JPanel(); + jLabel6 = new javax.swing.JLabel(); + spinnerDryTime = new ch.psi.utils.swing.HorizontalSpinner(); + buttonDry = new javax.swing.JButton(); + spinnerDrySpeed = new ch.psi.utils.swing.HorizontalSpinner(); + jLabel7 = new javax.swing.JLabel(); + ckeckParkOnDry = new javax.swing.JCheckBox(); + pnTransfer = new javax.swing.JPanel(); + buttonMount = new javax.swing.JButton(); + jPanel5 = new javax.swing.JPanel(); + jLabel4 = new javax.swing.JLabel(); + checkForce = new javax.swing.JCheckBox(); + spinnerSample = new ch.psi.utils.swing.HorizontalSpinner(); + spinnerPuck = new ch.psi.utils.swing.HorizontalSpinner(); + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + spinnerSegment = new ch.psi.utils.swing.HorizontalSpinner(); + jLabel3 = new javax.swing.JLabel(); + jLabel5 = new javax.swing.JLabel(); + checkDatamatrix = new javax.swing.JCheckBox(); + buttonUnmount = new javax.swing.JButton(); + buttonScanPin = new javax.swing.JButton(); + buttonScanPuck = new javax.swing.JButton(); + checkAuxiliary = new javax.swing.JCheckBox(); + jPanel1 = new javax.swing.JPanel(); + buttonGripperScan = new javax.swing.JButton(); + buttonTrash = new javax.swing.JButton(); + tabAdvanced = new javax.swing.JPanel(); + pnLowLevel = new javax.swing.JPanel(); + buttonMovePark = new javax.swing.JButton(); + buttonMoveCold = new javax.swing.JButton(); + buttonMoveGonio = new javax.swing.JButton(); + buttonMoveHeater = new javax.swing.JButton(); + buttonGetGonio = new javax.swing.JButton(); + buttonPutGonio = new javax.swing.JButton(); + buttonMoveDewar = new javax.swing.JButton(); + buttonMoveHome = new javax.swing.JButton(); + buttonMoveScanner = new javax.swing.JButton(); + buttonGetDewar = new javax.swing.JButton(); + buttonPutDewar = new javax.swing.JButton(); + buttonMoveAux = new javax.swing.JButton(); + buttonGetAux = new javax.swing.JButton(); + buttonPutAux = new javax.swing.JButton(); + pnDatabase = new javax.swing.JPanel(); + buttonClearSampleDb = new javax.swing.JButton(); + buttonResetPuckIds = new javax.swing.JButton(); + pnMotion = new javax.swing.JPanel(); + buttonRecover = new javax.swing.JButton(); + buttonEnableAll = new javax.swing.JButton(); + + pnDry.setBorder(javax.swing.BorderFactory.createTitledBorder("Drying")); + + jLabel6.setText("Heat time(s):"); + + spinnerDryTime.setModel(new javax.swing.SpinnerNumberModel(35.0d, 1.0d, 50.0d, 1.0d)); + spinnerDryTime.setRequestFocusEnabled(false); + + buttonDry.setText("Dry"); + buttonDry.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonDryActionPerformed(evt); + } + }); + + spinnerDrySpeed.setModel(new javax.swing.SpinnerNumberModel(0.4d, 0.1d, 1.0d, 0.1d)); + + jLabel7.setText("Speed(%):"); + + ckeckParkOnDry.setText("Park on finish"); + + javax.swing.GroupLayout pnDryLayout = new javax.swing.GroupLayout(pnDry); + pnDry.setLayout(pnDryLayout); + pnDryLayout.setHorizontalGroup( + pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnDryLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonDry, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(ckeckParkOnDry)) + .addGap(18, 25, Short.MAX_VALUE) + .addGroup(pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel6) + .addComponent(jLabel7)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(spinnerDrySpeed, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerDryTime, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + pnDryLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonDry, spinnerDrySpeed, spinnerDryTime}); + + pnDryLayout.setVerticalGroup( + pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnDryLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnDryLayout.createSequentialGroup() + .addGroup(pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonDry) + .addComponent(spinnerDryTime, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(pnDryLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerDrySpeed, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel7) + .addComponent(ckeckParkOnDry))) + .addComponent(jLabel6)) + .addContainerGap()) + ); + + pnTransfer.setBorder(javax.swing.BorderFactory.createTitledBorder("Sample Transfer")); + + buttonMount.setText("Mount"); + buttonMount.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMountActionPerformed(evt); + } + }); + + jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel4.setText("Force:"); + + checkForce.setSelected(true); + checkForce.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + + spinnerSample.setModel(new javax.swing.SpinnerNumberModel(1, 1, 16, 1)); + + spinnerPuck.setModel(new javax.swing.SpinnerNumberModel(1, 1, 5, 1)); + + jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel1.setText("Segment:"); + + jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel2.setText("Puck:"); + + spinnerSegment.setModel(new javax.swing.SpinnerListModel(new String[] {"A", "B", "C", "D", "E", "F"})); + + jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel3.setText("Sample:"); + + jLabel5.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel5.setText("Read DM:"); + + checkDatamatrix.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + + javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5); + jPanel5.setLayout(jPanel5Layout); + jPanel5Layout.setHorizontalGroup( + jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel5Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel3) + .addComponent(jLabel2) + .addComponent(jLabel1) + .addComponent(jLabel4) + .addComponent(jLabel5)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(spinnerPuck, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(checkForce) + .addComponent(checkDatamatrix) + .addComponent(spinnerSample, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerSegment, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0)) + ); + + jPanel5Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jLabel1, jLabel2, jLabel3, jLabel4, jLabel5}); + + jPanel5Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {spinnerPuck, spinnerSample, spinnerSegment}); + + jPanel5Layout.setVerticalGroup( + jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel5Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel1) + .addComponent(spinnerSegment, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel2) + .addComponent(spinnerPuck, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel3) + .addComponent(spinnerSample, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel4) + .addComponent(checkForce)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel5) + .addComponent(checkDatamatrix))) + ); + + buttonUnmount.setText("Unmount"); + buttonUnmount.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonUnmountActionPerformed(evt); + } + }); + + buttonScanPin.setText("Scan Pin"); + buttonScanPin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonScanPinActionPerformed(evt); + } + }); + + buttonScanPuck.setText("Scan Puck"); + buttonScanPuck.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonScanPuckActionPerformed(evt); + } + }); + + checkAuxiliary.setText("Auxiliary"); + checkAuxiliary.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkAuxiliaryActionPerformed(evt); + } + }); + + javax.swing.GroupLayout pnTransferLayout = new javax.swing.GroupLayout(pnTransfer); + pnTransfer.setLayout(pnTransferLayout); + pnTransferLayout.setHorizontalGroup( + pnTransferLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnTransferLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnTransferLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonMount, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonUnmount, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonScanPin, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonScanPuck, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(checkAuxiliary)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pnTransferLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonMount, buttonScanPin, buttonUnmount}); + + pnTransferLayout.setVerticalGroup( + pnTransferLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnTransferLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnTransferLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(pnTransferLayout.createSequentialGroup() + .addComponent(buttonMount) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonUnmount) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonScanPin) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonScanPuck) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(checkAuxiliary))) + .addContainerGap()) + ); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Gripper")); + + buttonGripperScan.setText("Scan"); + buttonGripperScan.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonGripperScanActionPerformed(evt); + } + }); + + buttonTrash.setText("Trash"); + buttonTrash.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonTrashActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonGripperScan, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonTrash, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonGripperScan) + .addComponent(buttonTrash)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + javax.swing.GroupLayout tabCommandsLayout = new javax.swing.GroupLayout(tabCommands); + tabCommands.setLayout(tabCommandsLayout); + tabCommandsLayout.setHorizontalGroup( + tabCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(tabCommandsLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(tabCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pnDry, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnTransfer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + ); + tabCommandsLayout.setVerticalGroup( + tabCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(tabCommandsLayout.createSequentialGroup() + .addContainerGap() + .addComponent(pnDry, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(pnTransfer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + jTabbedPane1.addTab("Commands", tabCommands); + + pnLowLevel.setBorder(javax.swing.BorderFactory.createTitledBorder("Low-level Motion Commands")); + + buttonMovePark.setText("Move Park"); + buttonMovePark.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveParkActionPerformed(evt); + } + }); + + buttonMoveCold.setText("Move Cold"); + buttonMoveCold.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveColdActionPerformed(evt); + } + }); + + buttonMoveGonio.setText("Move Gonio"); + buttonMoveGonio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveGonioActionPerformed(evt); + } + }); + + buttonMoveHeater.setText("Move Heater"); + buttonMoveHeater.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveHeaterActionPerformed(evt); + } + }); + + buttonGetGonio.setText("Get Gonio"); + buttonGetGonio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonGetGonioActionPerformed(evt); + } + }); + + buttonPutGonio.setText("Put Gonio"); + buttonPutGonio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPutGonioActionPerformed(evt); + } + }); + + buttonMoveDewar.setText("Move Dewar"); + buttonMoveDewar.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveDewarActionPerformed(evt); + } + }); + + buttonMoveHome.setText("Move Home"); + buttonMoveHome.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveHomeActionPerformed(evt); + } + }); + + buttonMoveScanner.setText("Move Scanner"); + buttonMoveScanner.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveScannerActionPerformed(evt); + } + }); + + buttonGetDewar.setText("Get Dewar"); + buttonGetDewar.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonGetDewarActionPerformed(evt); + } + }); + + buttonPutDewar.setText("Put Dewar"); + buttonPutDewar.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPutDewarActionPerformed(evt); + } + }); + + buttonMoveAux.setText("Move Aux"); + buttonMoveAux.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMoveAuxActionPerformed(evt); + } + }); + + buttonGetAux.setText("Get Aux"); + buttonGetAux.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonGetAuxActionPerformed(evt); + } + }); + + buttonPutAux.setText("Put Aux"); + buttonPutAux.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPutAuxActionPerformed(evt); + } + }); + + javax.swing.GroupLayout pnLowLevelLayout = new javax.swing.GroupLayout(pnLowLevel); + pnLowLevel.setLayout(pnLowLevelLayout); + pnLowLevelLayout.setHorizontalGroup( + pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnLowLevelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonMovePark, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonMoveAux) + .addComponent(buttonMoveCold, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonMoveHeater) + .addComponent(buttonGetGonio) + .addComponent(buttonGetDewar, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonGetAux, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 64, Short.MAX_VALUE) + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonPutDewar, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonPutGonio, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonMoveGonio, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonMoveHome, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonMoveDewar, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonMoveScanner) + .addComponent(buttonPutAux, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + pnLowLevelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonGetAux, buttonGetDewar, buttonGetGonio, buttonMoveAux, buttonMoveCold, buttonMoveDewar, buttonMoveGonio, buttonMoveHeater, buttonMoveHome, buttonMovePark, buttonMoveScanner, buttonPutAux, buttonPutDewar, buttonPutGonio}); + + pnLowLevelLayout.setVerticalGroup( + pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnLowLevelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonGetDewar) + .addComponent(buttonPutDewar)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonGetAux) + .addComponent(buttonPutAux)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonGetGonio) + .addComponent(buttonPutGonio)) + .addGap(18, 18, 18) + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonMoveGonio) + .addComponent(buttonMoveAux)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonMovePark) + .addComponent(buttonMoveHome)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonMoveDewar) + .addComponent(buttonMoveCold)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(pnLowLevelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonMoveHeater) + .addComponent(buttonMoveScanner)) + .addContainerGap()) + ); + + pnDatabase.setBorder(javax.swing.BorderFactory.createTitledBorder("Database")); + + buttonClearSampleDb.setText("Clear Sample Db"); + buttonClearSampleDb.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonClearSampleDbActionPerformed(evt); + } + }); + + buttonResetPuckIds.setText("Reset Puck IDs"); + buttonResetPuckIds.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonResetPuckIdsActionPerformed(evt); + } + }); + + javax.swing.GroupLayout pnDatabaseLayout = new javax.swing.GroupLayout(pnDatabase); + pnDatabase.setLayout(pnDatabaseLayout); + pnDatabaseLayout.setHorizontalGroup( + pnDatabaseLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnDatabaseLayout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonClearSampleDb) + .addGap(18, 18, Short.MAX_VALUE) + .addComponent(buttonResetPuckIds) + .addContainerGap()) + ); + + pnDatabaseLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonClearSampleDb, buttonResetPuckIds}); + + pnDatabaseLayout.setVerticalGroup( + pnDatabaseLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnDatabaseLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnDatabaseLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonClearSampleDb) + .addComponent(buttonResetPuckIds)) + .addContainerGap()) + ); + + pnMotion.setBorder(javax.swing.BorderFactory.createTitledBorder("Motion")); + + buttonRecover.setText("Dewar Recovery"); + buttonRecover.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonRecoverActionPerformed(evt); + } + }); + + buttonEnableAll.setText("Enable Motion"); + buttonEnableAll.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonEnableAllActionPerformed(evt); + } + }); + + javax.swing.GroupLayout pnMotionLayout = new javax.swing.GroupLayout(pnMotion); + pnMotion.setLayout(pnMotionLayout); + pnMotionLayout.setHorizontalGroup( + pnMotionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnMotionLayout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonRecover) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonEnableAll) + .addContainerGap()) + ); + + pnMotionLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonEnableAll, buttonRecover}); + + pnMotionLayout.setVerticalGroup( + pnMotionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnMotionLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnMotionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonRecover) + .addComponent(buttonEnableAll)) + .addContainerGap()) + ); + + javax.swing.GroupLayout tabAdvancedLayout = new javax.swing.GroupLayout(tabAdvanced); + tabAdvanced.setLayout(tabAdvancedLayout); + tabAdvancedLayout.setHorizontalGroup( + tabAdvancedLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(tabAdvancedLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(tabAdvancedLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pnMotion, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnDatabase, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnLowLevel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(0, 0, 0)) + ); + tabAdvancedLayout.setVerticalGroup( + tabAdvancedLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(tabAdvancedLayout.createSequentialGroup() + .addContainerGap() + .addComponent(pnLowLevel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(pnDatabase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(pnMotion, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + jTabbedPane1.addTab("Advanced", tabAdvanced); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jTabbedPane1, javax.swing.GroupLayout.Alignment.TRAILING) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jTabbedPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + ); + }// //GEN-END:initComponents + + private void buttonEnableAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonEnableAllActionPerformed + execute("enable_motion()", true); + }//GEN-LAST:event_buttonEnableAllActionPerformed + + private void buttonMountActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMountActionPerformed + String segment = checkAuxiliary.isSelected() ? "X" : (String) spinnerSegment.getValue(); + int puck = checkAuxiliary.isSelected() ? 1 :(Integer) spinnerPuck.getValue(); + int sample = (Integer) spinnerSample.getValue(); + String force = checkForce.isSelected() ? "True" : "False"; + String readDatamatrix = checkDatamatrix.isSelected() ? "True" : "False"; + execute("mount('" + segment + "'," + puck + "," + sample + ", force=" + force + ", read_dm=" + readDatamatrix + ")"); + + }//GEN-LAST:event_buttonMountActionPerformed + + private void buttonMoveAuxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveAuxActionPerformed + execute("move_aux()"); + }//GEN-LAST:event_buttonMoveAuxActionPerformed + + private void buttonUnmountActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonUnmountActionPerformed + String segment = checkAuxiliary.isSelected() ? "X" : (String) spinnerSegment.getValue(); + int puck = checkAuxiliary.isSelected() ? 1 :(Integer) spinnerPuck.getValue(); + int sample = (Integer) spinnerSample.getValue(); + String force = checkForce.isSelected() ? "True" : "False"; + execute("unmount('" + segment + "'," + puck + "," + sample + ", force=" + force + ")"); + }//GEN-LAST:event_buttonUnmountActionPerformed + + private void buttonGetDewarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonGetDewarActionPerformed + String segment = (String) spinnerSegment.getValue(); + int puck = (Integer) spinnerPuck.getValue(); + int sample = (Integer) spinnerSample.getValue(); + String force = checkForce.isSelected() ? "True" : "False"; + execute("get_dewar('" + segment + "'," + puck + "," + sample + ", force=" + force + ")"); + }//GEN-LAST:event_buttonGetDewarActionPerformed + + private void buttonPutDewarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPutDewarActionPerformed + String segment = (String) spinnerSegment.getValue(); + int puck = (Integer) spinnerPuck.getValue(); + int sample = (Integer) spinnerSample.getValue(); + String force = checkForce.isSelected() ? "True" : "False"; + execute("put_dewar('" + segment + "'," + puck + "," + sample + ", force=" + force + ")"); + }//GEN-LAST:event_buttonPutDewarActionPerformed + + private void buttonGetGonioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonGetGonioActionPerformed + String force = checkForce.isSelected() ? "True" : "False"; + execute("get_gonio(force=" + force + ")"); + }//GEN-LAST:event_buttonGetGonioActionPerformed + + private void buttonPutGonioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPutGonioActionPerformed + String force = checkForce.isSelected() ? "True" : "False"; + execute("put_gonio(force=" + force + ")"); + }//GEN-LAST:event_buttonPutGonioActionPerformed + + private void buttonMoveParkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveParkActionPerformed + execute("move_park()"); + }//GEN-LAST:event_buttonMoveParkActionPerformed + + private void buttonMoveHomeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveHomeActionPerformed + execute("move_home()"); + }//GEN-LAST:event_buttonMoveHomeActionPerformed + + private void buttonMoveGonioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveGonioActionPerformed + execute("move_gonio()"); + }//GEN-LAST:event_buttonMoveGonioActionPerformed + + private void buttonMoveDewarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveDewarActionPerformed + execute("move_dewar()"); + }//GEN-LAST:event_buttonMoveDewarActionPerformed + + private void buttonMoveScannerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveScannerActionPerformed + execute("move_scanner()"); + }//GEN-LAST:event_buttonMoveScannerActionPerformed + + private void buttonMoveHeaterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveHeaterActionPerformed + execute("move_heater()"); + }//GEN-LAST:event_buttonMoveHeaterActionPerformed + + private void buttonDryActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonDryActionPerformed + double dryTime = (Double) spinnerDryTime.getValue(); + double streamTime = (Double) spinnerDrySpeed.getValue(); + boolean park = ckeckParkOnDry.isSelected(); + + execute("dry(" + dryTime + ", " + streamTime + (park ? ", wait_cold=-1": "") + ")"); + }//GEN-LAST:event_buttonDryActionPerformed + + private void buttonRecoverActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonRecoverActionPerformed + execute("robot_recover()"); + }//GEN-LAST:event_buttonRecoverActionPerformed + + private void buttonMoveColdActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMoveColdActionPerformed + execute("move_cold()"); + }//GEN-LAST:event_buttonMoveColdActionPerformed + + private void buttonClearSampleDbActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonClearSampleDbActionPerformed + execute("clear_samples_info()", true); + }//GEN-LAST:event_buttonClearSampleDbActionPerformed + + private void buttonResetPuckIdsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonResetPuckIdsActionPerformed + execute("reset_puck_datamatrix()", true); + }//GEN-LAST:event_buttonResetPuckIdsActionPerformed + + private void buttonScanPinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonScanPinActionPerformed + String segment = checkAuxiliary.isSelected() ? "X" : (String) spinnerSegment.getValue(); + int puck = checkAuxiliary.isSelected() ? 1 :(Integer) spinnerPuck.getValue(); + int sample = (Integer) spinnerSample.getValue(); + String force = checkForce.isSelected() ? "True" : "False"; + execute("scan_pin('" + segment + "'," + puck + "," + sample + ", force=" + force + ")", false, true); + }//GEN-LAST:event_buttonScanPinActionPerformed + + private void buttonScanPuckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonScanPuckActionPerformed + String segment = checkAuxiliary.isSelected() ? "X" : (String) spinnerSegment.getValue(); + int puck = checkAuxiliary.isSelected() ? 1 :(Integer) spinnerPuck.getValue(); + String force = checkForce.isSelected() ? "True" : "False"; + execute("scan_puck('" + segment + "'," + puck + ", force=" + force + ")", false, true); + }//GEN-LAST:event_buttonScanPuckActionPerformed + + private void buttonGetAuxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonGetAuxActionPerformed + int sample = (Integer) spinnerSample.getValue(); + execute("get_aux('" + sample + "')"); + }//GEN-LAST:event_buttonGetAuxActionPerformed + + private void buttonPutAuxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPutAuxActionPerformed + int sample = (Integer) spinnerSample.getValue(); + execute("put_aux('" + sample + "')"); + }//GEN-LAST:event_buttonPutAuxActionPerformed + + private void checkAuxiliaryActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkAuxiliaryActionPerformed + onStateChange(getState(), null); + }//GEN-LAST:event_checkAuxiliaryActionPerformed + + private void buttonGripperScanActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonGripperScanActionPerformed + execute("scan_gripper()"); + }//GEN-LAST:event_buttonGripperScanActionPerformed + + private void buttonTrashActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonTrashActionPerformed + execute("trash()"); + }//GEN-LAST:event_buttonTrashActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonClearSampleDb; + private javax.swing.JButton buttonDry; + private javax.swing.JButton buttonEnableAll; + private javax.swing.JButton buttonGetAux; + private javax.swing.JButton buttonGetDewar; + private javax.swing.JButton buttonGetGonio; + private javax.swing.JButton buttonGripperScan; + private javax.swing.JButton buttonMount; + private javax.swing.JButton buttonMoveAux; + private javax.swing.JButton buttonMoveCold; + private javax.swing.JButton buttonMoveDewar; + private javax.swing.JButton buttonMoveGonio; + private javax.swing.JButton buttonMoveHeater; + private javax.swing.JButton buttonMoveHome; + private javax.swing.JButton buttonMovePark; + private javax.swing.JButton buttonMoveScanner; + private javax.swing.JButton buttonPutAux; + private javax.swing.JButton buttonPutDewar; + private javax.swing.JButton buttonPutGonio; + private javax.swing.JButton buttonRecover; + private javax.swing.JButton buttonResetPuckIds; + private javax.swing.JButton buttonScanPin; + private javax.swing.JButton buttonScanPuck; + private javax.swing.JButton buttonTrash; + private javax.swing.JButton buttonUnmount; + private javax.swing.JCheckBox checkAuxiliary; + private javax.swing.JCheckBox checkDatamatrix; + private javax.swing.JCheckBox checkForce; + private javax.swing.JCheckBox ckeckParkOnDry; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel5; + private javax.swing.JTabbedPane jTabbedPane1; + private javax.swing.JPanel pnDatabase; + private javax.swing.JPanel pnDry; + private javax.swing.JPanel pnLowLevel; + private javax.swing.JPanel pnMotion; + private javax.swing.JPanel pnTransfer; + private javax.swing.JSpinner spinnerDrySpeed; + private javax.swing.JSpinner spinnerDryTime; + private javax.swing.JSpinner spinnerPuck; + private javax.swing.JSpinner spinnerSample; + private javax.swing.JSpinner spinnerSegment; + private javax.swing.JPanel tabAdvanced; + private javax.swing.JPanel tabCommands; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/Hexiposi.form b/plugins/Hexiposi.form new file mode 100644 index 0000000..4687d34 --- /dev/null +++ b/plugins/Hexiposi.form @@ -0,0 +1,34 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/Hexiposi.java b/plugins/Hexiposi.java new file mode 100644 index 0000000..2c55c2c --- /dev/null +++ b/plugins/Hexiposi.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014-2018 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.core.Context; +import ch.psi.pshell.ui.StripChart; +import ch.psi.pshell.ui.App; +import ch.psi.pshell.ui.Panel; +import ch.psi.utils.State; +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + */ +public class Hexiposi extends Panel { + final StripChart stripChart; + public Hexiposi() { + initComponents(); + stripChart = new StripChart(this.getTopLevel(), false, App.getStripChartFolderArg()); + panel.add(stripChart.getPlotPanel()); + + try { + stripChart.open(new File(Context.getInstance().getSetup().expandPath("{home}/stripchart/hexiposi_positon.scd"))); + stripChart.start(); + } catch (Exception ex) { + showException(ex); + Logger.getLogger(Hexiposi.class.getName()).log(Level.WARNING, null, ex); + } + + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + + } + + @Override + public void onStateChange(State state, State former) { + + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + + //Callback to perform update - in event thread + @Override + protected void doUpdate() { + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + panel = new javax.swing.JPanel(); + + panel.setLayout(new java.awt.BorderLayout()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel panel; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/HexiposiPanel.form b/plugins/HexiposiPanel.form new file mode 100644 index 0000000..65d0c18 --- /dev/null +++ b/plugins/HexiposiPanel.form @@ -0,0 +1,81 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/HexiposiPanel.java b/plugins/HexiposiPanel.java new file mode 100644 index 0000000..b29a526 --- /dev/null +++ b/plugins/HexiposiPanel.java @@ -0,0 +1,110 @@ + + +import ch.psi.pshell.core.Context; +import ch.psi.pshell.device.Device; +import ch.psi.pshell.swing.DevicePanel; + +/** + * + */ +public class HexiposiPanel extends DevicePanel { + + /** + * Creates new form HexiposiPositionPanel + */ + public HexiposiPanel() { + initComponents(); + } + + @Override + public void setDevice(Device device){ + super.setDevice(device); + discretePositionerPanel.setDevice(device); + } + + @Override + public void setEnabled(boolean enabled){ + super.setEnabled(enabled); + discretePositionerPanel.setEnabled(enabled); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + discretePositionerPanel = new ch.psi.pshell.swing.DiscretePositionerPanel(); + jPanel1 = new javax.swing.JPanel(); + buttonHoming = new javax.swing.JButton(); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Initialize")); + + buttonHoming.setText("Homing"); + buttonHoming.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonHomingActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonHoming) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(buttonHoming) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(discretePositionerPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(0, 0, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(discretePositionerPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// //GEN-END:initComponents + + private void buttonHomingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonHomingActionPerformed + try { + //Context.getInstance().evalLineAsync("hexiposi.move_home()").handle((ret, ex) -> { + Context.getInstance().evalLineAsync("homing_hexiposi()").handle((ret, ex) -> { + if (ex != null){ + showException((Exception)ex); + } + return ret; + }); + } catch (Exception ex) { + showException(ex); + } + + }//GEN-LAST:event_buttonHomingActionPerformed + + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonHoming; + private ch.psi.pshell.swing.DiscretePositionerPanel discretePositionerPanel; + private javax.swing.JPanel jPanel1; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/LN2.form b/plugins/LN2.form new file mode 100644 index 0000000..4687d34 --- /dev/null +++ b/plugins/LN2.form @@ -0,0 +1,34 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/LN2.java b/plugins/LN2.java new file mode 100644 index 0000000..2300e49 --- /dev/null +++ b/plugins/LN2.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014-2018 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.core.Context; +import ch.psi.pshell.plot.TimePlotBase; +import ch.psi.pshell.ui.StripChart; +import ch.psi.pshell.ui.App; +import ch.psi.pshell.ui.Panel; +import ch.psi.utils.State; +import ch.psi.utils.swing.SwingUtils; +import java.awt.Dimension; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + */ +public class LN2 extends Panel { + final StripChart stripChart; + TimePlotBase plot; + public LN2() { + initComponents(); + stripChart = new StripChart(this.getTopLevel(), false, App.getStripChartFolderArg()); + panel.add(stripChart.getPlotPanel()); + + try { + stripChart.open(new File(Context.getInstance().getSetup().expandPath("{home}/stripchart/LN2_Monitoring.scd"))); + stripChart.start(); + } catch (Exception ex) { + showException(ex); + Logger.getLogger(LN2.class.getName()).log(Level.WARNING, null, ex); + } + + plot = (TimePlotBase) SwingUtils.getComponentsByType(stripChart.getPlotPanel(),TimePlotBase.class)[0]; + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + } + + + @Override + public void onStop() { + //saveImage(); + super.onStop(); + } + + @Override + public void onStateChange(State state, State former) { + if ( (state == State.Closing) || + ((state == State.Initializing) && (former != State.Invalid)) + ){ + saveImage(); + } + + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + public void saveImage(){ + getLogger().severe("Saving image"); + try { + String fileName = new File(getContext().getSetup().expandPath("{images}/ln2/{date}_{time}.png")).getCanonicalPath(); + getLogger().severe("File: " + fileName); + plot.saveSnapshot(fileName, "png", new Dimension(1200,800)); + } catch (Exception ex) { + getLogger().log(Level.SEVERE, null, ex); + } + getLogger().severe("Done"); + } + + //Callback to perform update - in event thread + @Override + protected void doUpdate() { + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + panel = new javax.swing.JPanel(); + + panel.setLayout(new java.awt.BorderLayout()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel panel; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/LaserUE.java b/plugins/LaserUE.java new file mode 100644 index 0000000..cb79550 --- /dev/null +++ b/plugins/LaserUE.java @@ -0,0 +1,90 @@ +import ch.psi.pshell.device.Readable; +import ch.psi.pshell.serial.SerialPortDevice; +import ch.psi.pshell.serial.SerialPortDeviceConfig; +import static ch.psi.utils.BitMask.*; +import ch.psi.utils.Convert; +import ch.psi.utils.State; +import java.io.IOException; + +/* + * + */ +public class LaserUE extends SerialPortDevice { + + final Readable readable; + final double range; + final double offset; + + public LaserUE(String name, String port, double range, double offset) { + super(name, port, 921600, SerialPortDeviceConfig.DataBits.DB_8, SerialPortDeviceConfig.StopBits.SB_1, SerialPortDeviceConfig.Parity.None); + this.range = range; + this.offset = offset; + this.setMode(Mode.FullDuplex); + readable = new Readable() { + @Override + public Object read() throws IOException, InterruptedException { + return take(); + } + + @Override + public String getName() { + return LaserUE.this.getName() + "_readout"; + } + }; + } + + @Override + protected void doInitialize() throws IOException, InterruptedException { + super.doInitialize(); + //TODO: Start DAQ in ILD1320: http://www.micro-epsilon.com/download/manuals/man--optoNCDT-1320--en.pdf + setState(State.Ready); + write("OUTPUT RS422\n\r"); + } + + public Readable getReadable() { + return readable; + } + + int value = 0; + int count = 0; + + @Override + protected void onByte(int rx) { + int index = ((rx & BIT7) > 0) ? 2 : (((rx & BIT6) > 0) ? 1 : 0); + if (count == index) { + if (index == 0) { + value = rx & 0x3F; + count = 1; + } else if (index == 1) { + value = ((rx & 0x3F) << 6) + value; + count = 2; + } else if (index == 2) { + value = ((rx & 0x0F) << 12) + value; + //double val = ((double)value)/1000; + double val = (0.01) * (102.0 * value / 65520.0 - 1) * range; + if ((val <= 0.0) || (val > range)) { + val = Double.NaN; + } else { + val+=offset; + val = Convert.roundDouble(val, 3); + } + setCache(val); + count = 0; + } else { + count = 0; + } + } else { + count = 0; + } + } + + public static void main(String[] args) throws Exception { + LaserUE l = new LaserUE("UE", "COM3", 50.0, 35.0); + l.setMonitored(true); + l.initialize(); + + Thread.sleep(10000); + l.close(); + } +} + diff --git a/plugins/LaserUEPanel.form b/plugins/LaserUEPanel.form new file mode 100644 index 0000000..c9c5f8d --- /dev/null +++ b/plugins/LaserUEPanel.form @@ -0,0 +1,59 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/LaserUEPanel.java b/plugins/LaserUEPanel.java new file mode 100644 index 0000000..017fb77 --- /dev/null +++ b/plugins/LaserUEPanel.java @@ -0,0 +1,98 @@ +import ch.psi.pshell.core.Context; +import ch.psi.pshell.device.Device; +import ch.psi.pshell.swing.DevicePanel; +import ch.psi.utils.swing.SwingUtils; + +/** + * + */ +public class LaserUEPanel extends DevicePanel { + + public LaserUEPanel() { + initComponents(); + } + + @Override + public void setDevice(Device device){ + super.setDevice(device); + if (device!=null){ + historyChart.addDevice("Laser Distance", device); + } else { + for (Device d: historyChart.getDevices()){ + historyChart.removeDevice(device); + } + } + } + + + void execute(String statement, boolean showReturn){ + try { + Context.getInstance().evalLineBackgroundAsync(statement).handle((ret, ex) -> { + if (LaserUEPanel.this.isShowing()){ + if (ex != null){ + showException((Exception)ex); + } else if (showReturn){ + if (ret == null){ + SwingUtils.showMessage(this, "Return", "No code detected", 20000); + } else { + SwingUtils.showMessage(this, "Return", "Detected code: " +ret, 20000); + } + } + } + return ret; + }); + } catch (Exception ex) { + showException(ex); + } + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + deviceStatePanel2 = new ch.psi.pshell.swing.DeviceStatePanel(); + try { + historyChart = new ch.psi.pshell.swing.HistoryChart(); + } catch (java.lang.ClassNotFoundException e1) { + e1.printStackTrace(); + } catch (java.lang.InstantiationException e2) { + e2.printStackTrace(); + } catch (java.lang.IllegalAccessException e3) { + e3.printStackTrace(); + } + deviceValuePanel1 = new ch.psi.pshell.swing.DeviceValuePanel(); + + deviceStatePanel2.setDeviceName("ue"); + + deviceValuePanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Value")); + deviceValuePanel1.setDeviceName("ue"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(historyChart, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(deviceStatePanel2, javax.swing.GroupLayout.DEFAULT_SIZE, 297, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deviceValuePanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 297, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(historyChart, javax.swing.GroupLayout.DEFAULT_SIZE, 307, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(deviceStatePanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(deviceValuePanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + ); + }// //GEN-END:initComponents + + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private ch.psi.pshell.swing.DeviceStatePanel deviceStatePanel2; + private ch.psi.pshell.swing.DeviceValuePanel deviceValuePanel1; + private ch.psi.pshell.swing.HistoryChart historyChart; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/MXSC-1.14.0.jar b/plugins/MXSC-1.14.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..c935b068934c4f4db5da21980b3310f2ec904e03 GIT binary patch literal 286787 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

j`;URMoPWzFo0r3*B}8vD<3zb6f2U~ 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≪sPZG)3R8P%xP3?t@th6WE+p8Pqd+(I> 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

VYnG}>%g})fLiWrZq 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⪴c;piTLd&@*`@t^eMk0nXe*3iN^#i!v-zZ_ z8=xDU&A0p78qg1dAyZLU=dM!zihWA!Cn&3g>?6h=Rk&!0+JM8b4U95pGw@JX0X@|d zZc#?F7U_}>`_4k(13prxcC?`0=Sl3lZVKFf{QIwdOkI)$?cL^`j)NB~< z5e|d4=_hiaG4nyuL&%eVLB$oUVmhNHg@iV6h6~@!kbLEe4jyh`e{R@!N$p3)KO_PG zrUey@r$d+B$~+c33xe^k31Be1`Y|h53)~{x7kYKIZc(sN6D0Of3V@@MTwBIJMww_+O;m6M3#_ahLi?OweDG>Y5Nr~aNqI%BR zuO&Xh^*(=gZ3LyPT+XlKrD^`$p2o>=0{PrGK=0iiLM~hQ*s4s8I`MbJjv{wvn!tJX z22kz)<4l|7uYsp?td#;#?m-*bi8d!7;7T+hIc3_dRiCe5Qp|nxVzlVG5Ir3h@eem^ zWbXVD6)2uSWdR*s6PAVm<`VZyIFjH>}n+#5POTPc}|wv0v&`^+hhd5*d(p6em!ZpWz9qi|<_yFO#4% zBA={R#iSbL&A4-Z5h7ycuBVPJ2WODGW3bXIr+8kWS&d#Hh`42hjJ)MADN5GzUyB=Y zljp5#nuUs;RFX-_wiCv;loUUi{<40X!50_jGUd`rH2roS7MAL3wVC2;fyz;lN|ig& z3s*v6tFNXXlDL?0>vA@i6~i?~Ue4#j4Cg=DD~Hh1_ce77VMHgvgU*rerU(X(S1vWu z_M!uT*K*@ub`1Egaqopb&xb8egF@BlqP?=NEKjHSxZz&%$%>}DWUa*VP0=0t04FVN zS@Vt4P@d>sOzJ+qv-4_30_o>+qOLpXuUlN_viw3lZM;|67Cr;~D}*Hrvvy6)yV+FO2(h~T$7WRD+&(Xo#|5_X8< zz0!G!FJQizwG*f6JO`Zapxp#CbKnF#a$rD3a9}{g+Rz~<@a8Bmxdfz+;$N+e_QeV2 ze(LxLM-mLv&m=G4OVbvF#~&1|PNdh%gR_h43K6{8_mSip%kDDa1%G`9)D5EWFzNBO z>X}P!S==T%CE~3RLc9!*v~&O}za(#DLO+Br++dn^RywXV7;mqe@b5pSwm20*Ca?|N z1Aor{aX0N>6TB7a9w8)ttIE)+d5<`3i+e3fmC)b7D}SP4 zm%AdkFg7#RZ$S4uJ3ySMQ*R||6M*@xHfNB}H+%}8yVUfhYMyRJW)BMV+)HU}9|HzlK{SSIeVCPedhJFoYY#FHww%xl>VlFtnq9(HIGDk()po^R8HfyKb6 zHYN^@3#@BMs1`9gEl(GFDcs8HPtCr>{@V5#JLuPgjBf4`^>8kB-}O)SAG`tC2P=7y zt4B9%UXlUinh{_&&?BbKyvFd8nL!_Egy#hwc?>baqVxkP;Z`M+gn51hd|Wq};(qk+ zMq&{%w&0M^!yv`4@JC2ga{WLnKzb)>B7sS$@=zN-{*ceg6vmtrYQQ&~Oa%7n3GFLZ ztA$R)H#s`T0_AwWJifpMHm9bgNzrWyZRA&MEWci%Y~N5 zS~{x{#U*)2xOSwQo&$KIrGvzS`aFFW_sKVAEp6e$v32 zpQo8O2fWt*2zh6VJ8ngX$t0d(j5m;t>NsWB%E;kMQKGBVVr>@1a&O0X?`-?1z-lk1 zo;~+zGNH;zvr%*&>9vPRhpVL5vz|2Nh2j< zDiL^Qu5@;f1qM-HYa--TDS4_768-Hoo7MXWPS!BY+%PHft0~mSSb<=;{v6NcBB;>+ za1E?6Gc#HLM7jT(+5M$ltCij&;6C_$c7r||8kl%=36Tmi+Qz~k$~B%j{>|2(aFa6R zEsP5{{7GMCkcHJ_e>8c??bXx!V^t(Mtd#8HVSio(vur+ddOw^fO^I~6mPrzwhiYR* zzE;azMhj)iH0o0T?Uo&~r4|kL8Pn+QN>9QBc5HqY3D(od=g=LAGm!wZm+rzreeTO9 zpK(!(7d0k}FjlT$>CmcO$8zxI85@~hj!)srusE7=o<}<2Fg@1oo_(pSdz>+}DI((M zG0hvCrgYTADabu4kZAs1IBX&cc%O1DH|>Vde@MWe(M}-NsU%90lt9F@nc|NctP8_f zKw=xw>&Nsj>hgv@DD>C{E|4O#02*aX4q0~?!Ry_#sG>~c22-oah9JC$b9mM6HJHgRJT%{dsOEi+uGK+a4p$V7xDT*t4ZWM&;$Z?XVs{OU+-63(S zlNKTFRYh)`oXqdIKYH5gEzt2{u{SJ`tibx#aJe7MbUI1SD3Ta`E^oY?RW`9LWo2@@ ze8X~L4U_%|+G{xevJtwTG#>B`d;dD!pcE5s=(8d^oWDtNuT-rr@)mdH0~$61B*8tS zD^0)?Cd#K9+OHY+8Qq>02z*YK7Xim&1_MT7ht z_mKSna&q8xpNP+h zjLOLAX0UjzD3RYS7E7-s$V7sMRBkpXR&r9-?AS*sJV(w=m3s#S;et7;SthzHmGimkV1gR zlnzG>i^l2~_G@77_4_Evyo*mCw$ovJUb1Vw59#2H0?bjcA{_gFw_RT;xZcM)C1w=8 zzZ>SosXdN=1ovN~x&JO*FfYabf~z;vS5c4}D_7b`zZTGJ2ZK zYG!)y>*f)>lP7_WjM*R;_XkHmQ+&S@4l7)75skBLyg|*W>e|l)+cPzf^9*xog+tVe zfDZ8@n_}a-9;P35h*|<>pA!7S^bZ+j$RlyM?fZ8|h9%^v&ywp1i`nzhZ6Sm|0?`H-uJmu&cw0dATRw#z69V=3PT zk8=b;VO9Lf@UP{18PEtjVWJLyhBNa!6+nTuc4a%>;s)y8f>xU>VtHx6)ir(7(pK9d zD0>L#WsJwyYgOKg5_FnJOb-->LzV;SaTtdp7MdPlifre~PhqZtQ(&Wp=GMT3E&hYGC2_^hcZ)+EPh zLnLPz`&nr9gSfC5nhF}HioI721X~zv*z?ovk)vJ)tU%_7E zP}D&7$(^v2gxoM6jPfctnh0B%x%`D)Ab_Ej#*^W~b`V}zxAk41iwga39EabJLdmc@ ziTqa}hFvLG{jrQ$uttf(yO1D6vEhuER)dzf{JK72OD<(m7OdE_MH&pL<3{#emBsg{ zQ8Fbhe*Da|0E1ZFO-6fK+wDz@SJf>{E8ie82_xCmgGQUt=UReZ=+MY_&JjfZI4a-R zMUoZ+mASJ-mpY`6;ST~)i#=+fZ>Bhy6#}2u!yndsu`1?@bg(q(OHP+MQJ&91*8@J9 zz*@BFi&2w$uxyWW{_Ab zvI~>*4|u!&blEkMopOMk(cDV&0sd-3xQR>6DFcUF2KvRm@v==1*edA`KgVLkJxfzm z*K66$cL3t>11C12h$0Lh!C2yj+N<|il>U_)ZQI8HxRzRqmhp?6N(sl7+R{37hQb7X zWm1zw$DiYSBX&79Ccl@( zjZXKeY6>|*XzL>S?N*92HCvV<^US*QSA1V+Tr7^ITT(4mYvG$?<+IG!Di!I3LBa7} z(s}#fF`u&$)H72u`}6xgX$Ptbj%}cXQPZ6CSbF8Wr2u;NmnN4p=?+$p`4XP7gKusL zD;mM>f52N@2 zQA1)J*NPOrTYBAOnAj;Iuu0raaq~Qxc;biVE^c)>Ps`F{U%IU%4XQoTOIlQZH78+; zIA-;WV=v}T;b6fYFskuC!7P^4e@7yQ8^&wByq%uyw{2Ewv|^9=p`!OCAEGV<8`YVl zm^6zg5wpbkCnw8!{t{Lg8gB;Xa~uf8m2O3u)L%+xy@8@Ua+h~_92s~S%|kFM!DR?tMN*w|`T zkKbB)p&Qi=m-F=Yqx6hSo~s-x?r54u7s&|BoQ^`-+#X`1V&o;le*Vz?{Ja}~3Z>9U z%%<8Gw-eB`C2P<8q#oJK#`f_F&KS})+_PR0yEeTMmzK1VN9{w>*3Y;gOUp1UDdmk5 z`qLrp4Su8d*5a_)GF%(DE9Y}gJ{-34;nq$4*9=={^D6wU^|&J~eRxO_S}4aVgxn#M z)W=@QBg%U=n`lKPxcQ>)s;6dpsi)f%2A1Bs>}plT^2>y80Ax}LqFv*LR{9mF#=5Uy z{xM47q)UcL*ytmGNR$3v79*o%hw9M>bqYx{ymj|XW!{y(0qH_@z2MqX7|pGBw1@By z!*J0{8(3+^_F*QSvzc&j2zJxOv(u-n60r~jiknKVvc9=UpEu3Uqu1}0vN}bpzW92< z-LZ0YZW8&6!ui1=^yp$E(zrtT!w~e7mN&CV_XneUf{DTighdUxs&ox(QGyc~R0@3O zM0dqpSH6T+b4AI_Q7xvVjQo)9JhWYrU*5k^4uV*#a^+aSSTU!p$K;y+K_GmB`ho9PC+4L%xL$XniKlqimn(K~ zJ+mv^2N&!Ol}q}UU_ZOsUHpc3(lLH=@;pzjv&`NFKPNqEf>!9ML z-ldM!37clu#8vn_TYud=SAx9iPP>hU86Gz&!$U%Rf8%_T=!l?>m7>>c_k>@9Gv%bi z@-;*@zD0xpzoru2k|k}r&CvGM4CBj_g}9efX*t@au>jt4U#o+r65)#FNt2ou^>CQC z(JicClBUwQMScACYB5ud5-IG=*C-{lXIpyhTu)oC;>|CvBK?)3d63_F+wcNOf#Sq`yUl|eu1M6k&six(rKVm$I!o# zEEQ$W3df)g4Kr3RAA8HM*@;Vc7ot&X9Aj~ywvC}t2abakqx!29vz{5PE1A`?DY#ek z%71M@YCyr1uJHN4`JLU?=0wu|H&qYIUpyqtK?!H91#*m3 zDWObR`O~z^@f1r-M>AAP;i{R^X0cjda=&wKoh26<0N+*Oi6}d=C_p@(9Iq^lRGA4k z;NkcIH)OqJiE2{Ad&s70L8du17(1#Wm2-#^BLn5;pj?)fwR!s*lg|L9QN0|LLs~Ul zZXyQ2n8D7&4o}2UpqfZE$W&u7;>mA&y^YwzaiqJmkYOiGhkC*rVOcTC=8qwC)s#^f zKzYpjQ4=R|P#HaL$=wF{C@)xV`u;A*o~a7rG2)Axu~ZRX7@r3(5KiD)%01BiL&0u_ zbNO<<)glu6&Myc1DN5yRtgB#)%5EX7(JqpjZQwIco6h(k+#|uDmWj4idMLxy{L+z= zlR|D`v|#7G)k6P*>w{vRWymZhR+09L=7Dobznfk5iRUJ&;^%vkUTyr2jmRu&M$eL3ecXin}y)s z5}P|9g0z>8Aj;(xJDA|!Qk#k3-r}2Iz`f-*7r?zm2iiV9N+S8$A%6ie&H3PNj1Qru z2E`MHnvLg|M|VT~iQ?yuK-+~N)HiV0u8*@uP-E8^pZwtt5kR+WVrhcVT_N9!Cth`2 z6#!MT04DanC#<}<)EWd4O3CJA#gg1rk)qmMK3_#l$!1xqF(q5_**T?*?rGZq4bLDW zyWS|UHC@xuCS}r{cEV(hW-OOZ0whlJ^dG|nJc z@ag(2x4zWT2xfh7tVArMkXr`!lu5T$tqMWYXQGkv9CAl%R+zWg(;l;8jxIM2pU%e} z<=n9#K2@y;c*uM8?-ZJX8Eg_C=FONLJC(>IP9Kf)q!-WVIJsBJ7aln!$h*uP5%a9k zhxZ>gg2rmF#ff=z0)q9EDp<6?49y=Q^LRmMU@aB& z@`)@KX=4(rH7u1|EtmVsk0}WCg6q~$3aS4**r6rcsHqLM5tTq;gGu;cGLileI<3bq zW00HnP0dR&3B|aItVw10=_3 zFrO$+z{}SC5j27`p4t=2Xb{**YQAp>elF7NxNp zBDx(|%c6FX$5{Utdv6t$XSOwsCP)bG9w4~8JHg#ug1ZHG2=4Cg?i$?P-QC??&r7;@ z@6&zGx4(V)U;GytBXGqS^~{=cR;^X5O3Ik%S!s%7dFKpDcPGA5x)Qr;5WAk}-!D>bnp!Pj3o|?~gL**_fGV%UExk zUxskI@b-Y)mFKRWVxUxrj-Ks1KuR=W*h^iRz#U5i4eTW5_K`;plbjByD-sS}H1p89 z+OSyrckNL1@i4hh*9W$Y96{n)pI^o97$?_oHi$PI>PCUj8DpP$q9OM?5>5=^4G3QM zfU>J+BwhUdD$KG??X(}XS?=H{zFNaa?qOwl2OROm`GVG_rgaOP~qX2lpKOWugz4;yUk}5u`%-S?r-1 z@*86{<0rMqLpo=rfK_B%<=&H_(A6us6~QOh2`+As+T~N)MPk49A+uC%3yZ@Czen{< zE@luei#yE2Ej~D{x>adv5e@Bv_|G>{a11}5METr!KP%EUElN|1wX{o9BOdDGDtDcr zVjO1k{eZ7V#XQU{j7K}nO=qIZDv5ERX{4&>#M8QL$5q8M3;Q<>2(W zZ2guzbd?o%zWHuDtjFbvLY_qJGs6GSbcxi?MV$?XV^O`{yT(~jQ~vhx-# zES`KPC&rrY^W2#~rq>VrazkYVA1q#DYx>-vT~Wkj(hj%2fokWEt{`Au{eTR#Ae0dww+sfqGuQJr z6Kgy@ky#m_ZA3QTmkgOmUGqD=M_XwC{17lfY0T$f8)Bh?+1Nnam<#w--~^>5A2YGw z9DMeYQRr!aI@Y9MBUQc65zmgAQsak5xd)~gi24ip)*YDxMd6+~AhMMd`8uZpJvD)y zMQ7(si%hx`B4@!BiJgV-WFx%Sqb!ny3&rwbEfTr&sJ5`rP-j_*t63uF;CUVS2_H2# zXjnXTH%wDt*0O=!J8U+TO1##_VTr9MldKR`&)LM=H44_b#CwD)=(&6~&h3spadf#k zJYO!r#N$8E=Z3MUAA0NNd}C1@?6sOiN+CejNs|wHm~>*%DC$|5gh;{H_-TKDKoK*m z8(H&lEV8Uu=Rh@ud?n~Ar)eDbqzB>P!-c<9t1XmR?p~$1GbF0;8QdFJoS3Gz?=>Y= zjC-F=Zd=8&Pkn!k&t4+imX4gJx{fIEtGK*GcEx0jx8#&<3Ec>RJB&#low|vt&kPaj zY{GK)T;@>Cr})I#P3x(deI|24clOz&Q=V{RSZ4{aicutjS9p#!+b?c;@#B`xV8D5I z0dH`@5}H~q2Oubkt>2ms*v)8Oe8?r*Hpq%@6qEIv+n3Sj5}r1FcWtx$HwAVl94j9v zn!7o`s2Ne=TRzMsp>DBVS*u%Gv=0pz2YwHaPiEfubOr6``ykmo*_^!JQV$4uW=;&> zP}o(ns(ygZTI(wwN#8)Za&QcNfL%5LBymzGHqTDNmsekRo5y{tUx^a9Mr&S;3T9R| z*bGGhJFSR8XaTXTlHVFZ;XbWV5Ba4$i_ijZS!>p-5NBG^VU4wl?G~q8hkgcb>{|%? z4AeFm%)9wE`8ADDuE%CDsV8@`jT+!AwEvZp!@d2I1xm3@Vs=`*)Tk{afh zu|@4F`y6Ofv`UmLf9|%;tLl24MSUb>&U@mYO<`RD6h!vOGA~R&=B@UnX7*R4+AYfF znx-(dq(mqXk39V&l66U(J<`MYDJCk zP@OvxZS_*ehy0%dx*j^U(TOqz%Eh@R^F2Ly(?Y-*;90*b`*lMOSzYcfFm>vLDzl3z zM_1n)hCC%{RBRjp&L%tJQDbWAB+4X@}{Q1S0E{ zlyzmFyK`QdK2B} z8!_NXXz|YaBo{zO^>LzOBBlt+AyA!Jp@peC5TI)D!@BZ@>HdB*S5MT#_j8 zV}6Zl<@XIz%wWQR5Y1IBx`|4OTKaA=xs1sV7RDB(spU^Kr!&k`0;A4mwa#7Sz3E^f z3xO~hyL>gjx8&mSWHrhyD?ZJy&CPPIi|FTd+`mb zIw5!iZ&!D^iR>aXt=yPLauYu=92D>SfVI#lIMgm=u31lQ6{ea6F8f(g5oa|972ln~gkX4eG@x(+Xn#D{eJmh^p~!bZ1D% zRi>;*kb~s03#-9EE&iNiaaiT5b><3;CCj7yBe>PFVmhg?$Vkr~&(t)r?k4q=p4Sq*rktF5ufhRKBAc5t$ui9xr7>HJ zyxO_lhnZe-r^}@%eVMq!ZY847*5cYR`?D=q{LUAt(xVuWwrZ5KD$p`$wK|2C=X7ghlC61oiPKm;3K5#HvaT2|4N2dA za(`uFdW8hiZNsY{()d7a6#@rbCTVqCnmz9Sv0c;Hv{@9b`@pZ%ZWMPka2Sv>59bzg zj*ZvUDrHW+@pQW9&H0n1wq~WWvDb42#P3J%UGmpECD(FrC>!MN;#NZVy^GJ^i`kK- zj50#OmJ-|Ghn_(nJ`1i~z_gA$gR>%E{xX-_Sl1&!7J+&3nQ4_KyTr`;xNoz7Dk(s9 zJ&-{S&LwCDd&14@XAcK)J0MMefgO^}9>8pS7vYEo&;G>yl*VQ+Lg@{5j;7CfDN@#s zwumkQJ8&2MO7oqVER-|^AsN>hDjBWsE{3}s#J!!Bo9`Dq;Vz}<7@bWr6q5wB2&6*+ z?I_tzHt$QdcsGrxqj#frYJ_)+1AJcj~5qbWfce+C4gVDT9NUF^4g=GG4R)_ibxNKg$d_*S>3 z(a8L;eIL2?k83K20tC<;fCFdG|37D+|4uXiy8XRmF$FkucBH<{CK;V)E*c$E$G}4+ zZ9@*^8BtUt96KyPRs>>+X*g4Fcig|q-+U<|98=t_i*rw+H46>`7fLv`2snI3-&DQU z>hTJ(4pRrMSCOZ?^3yL=1+6}()8G@Kyh6R%Iz(BK^pwop#Q20B&7GU34`sMeWB}SE z7OgE-M}-NyMlnvqwY~423jXq9XgnAceHy07w_Kn!tu`E7UpbA39T>evek*$= zKK~0*m#X<l}x{h;J4uRF9AHvs@kWJslK>6d|1u@99(IcIzee z%;D`@B12Tl7C1H+4ht~@CMlj7&x%fCtgZjm;d2Hyn|Kh$1}KHJdQKa&FanS~CQdts zW=2a+csFTO=q{=32)$l%sDrROuWFatCSe7Cr4K{Tml>*Ple7w_l88ehV-!-kFUGRB zyvORHE?V6(3ddmZj97k>ykF}>2J2ImtXy~!#pMk9i}kn5P45E|?-EjNWxjf7VX~dd zUT`8b;WQ=V2Ub3b4jTsNG-Fu4;Z@aO@K0{;o+JL zN8=xsHpaixL;m>iT9lm}5$E9FS~Ql(0~w?A&3I{WQ!4enyjZ=#wT8exc?Oz)CE8II z3vT&p8cN_TvP57$mB)MyBQh2fw@SzmXS_^goSY*unU`beJW(T2el0phZ*0m|-6UL9 zS#T-U25Yc8as&96{OeLz_ihDNE3x1_1M*ixB}}Gu-n}ajh~P$q82m zsH>YDYF5+UAHrz&%&D4FFWA)`iMJdY&MkV+tTteJ`!wpOyp=Yom|Up6?;6hCdvUZb zz%=e7z`iYyq`+Re6C=6GcZk65Zt7wWcl4>8AVP2*bnu2%A*Sn`8-u2Ae5DvQL{HKA z5$-j0Jf`Cj*3kmxhn|BR;#iTgLe-7_DP8FY4m-NSIVfuD{x{B3!y_;xj{QtB*FaQF z10D>`i?2TZEZy`L93euE4+@}~SL-qyy(xW(k8IwS702M8)~?qn()7+Lzq==ov}Lb1 zL9`ODL)7{&vESBM!k_!znfljc*s~x*CDPK%P_NtPP*MV!bPMzAJStL_^&Ns3_~5&B zc;>(#bQu)=h<&1|k6(zz+`XjM+PMXGPKH%;X=1As4 z9Ugkqmn~B|_d~krNvK*1zS4eaa{B2aD7#V~8S!wYZ4i!#<0)NQ!=no9(q9S~=15gC z(V*R5a%5K$u;s;jvBw82A=Bvk?6=j7tMddV+QzE$0J{)IL4A zT=U1zBI1W}n+ev;+f!UNq|CfA|4q&viLLB)9VaUdVE845Vl2p73*BWbO`DWH*#wGQQgIxX4eGSt30jgN+p7s{>GwxqGI_+h3-3omk2?(Vt}nxC^2)HkIycgVxr6k)`$g^jZU#mvrYOxf;eYr+d6{jOyGO_841- zC9^%w;v2Q0S6KdOfjps}8W{gg0HjI&nr40^=r#CB-M4qM1?rY&ejsQkv0oKYaGFa6 z*`qsMp2MFTQa$acAX>XWgjf!PF)(QF0`)L2#2v+gLhGqDdT*7IWFs%^(u9YnmwyHm{puth5HIoy!wsr6xq zPU@iCNc}-wAZW%PHoHTR*nn~4dwDEAKyTB$&^)55el&A3IiMOtWT2(4lIp3X zNsk_FjlYKp5GE1r>QGDr&|9n)lkLfh>N?Z8MBI+M>-}!s&iBmvzmUfv46aR**Ipwy zQ`fq2B;6PZ@it9j>iKgMk5MM3BgV1&D|U`ROPH&vg6{5MZm7r&)~4hY3U0}Z88Ou8 z#9{O>Yh#sBwW~;cPqkV#ngRtSm2KHSR%OIPQSl|8Z!0d&&ZIO`_(q7DSdAPYz6b_N za5s6oSD&cZ&5#CX&xCo2I>AKY3j9_NWA{AvlKxtlPIi2Cn$y5zjE=5*CXljt_CasWRHHlxZ=SID1agmF;sRN9oVAhl-j2oIA#J`a~$0!r#CW3gZ zDN{vse%0=<%-d>(>5OXVPrUr9o=^8G`X#xfWvG(66E1B2c2h)Eq}zq<2lcYMlsSf6 zSQj7U0|64?lyrfQ=!@J{V6(d>mrUYXx|IBBdYqK}MtO?7{L|+mVH(cRp~(a*;^yXa z&9vU(t@9|!SPw-Er4)YFDT?S|z~QeF9twMG>yG%93>)cXb_(f}g-00-DoM;R2!r zo!zLFjWG!oA#*}ANTsa@AJ>sp;`wV`qihr-7S*gKfx%nPc7&z~f=M-MrLaaTyRk$@ zphHlsTJY2>G)~4}DO|_tSFs)l`K0oI>7!t!)k zBZtuUGYP@IfyDT=L|DXjJx;F{&}Dnf8RkC(D~tUA1yT;&Q5KLIue~-a z>7||CqAKa=s)sQY*%K&1>`|unGJ-3fAj$d+jwrFolGFoU6F^+?Erl=H5hc0%>T@uy zSsaH^{PR57#BmhRe$3%GVQCMOF}}TU(+_WkpBBcS*>jhEWZObD<*-+%v3YV%k{V5- zUkk(H;-&ghar(MV((lKndE>VHe3?Xp>8I17dnt+=f{)+ph(Sdt!+pj35)=Oww_H%x zf=AS%H)Gk8?^5zhoCADIyd(VNf)KTsfzDvdZ8yQ0)v}iwt}FDj63E^O1e>K2E%bg^ zAZ$GG-UgZdr*a=9wR1*C^o*$;PF@O0gm?~lF!?~kGQGi4G_)Nx$ zA*+g2X=Tr50SOLrGE9@js~-9iszmwfF`rV!&x*CUUEjB~WoXXGb;^ugWT$O$UwY3Z zt`LHYaO`|pbud_UGE7T!S{>0GbAQ2VKe{i27Q=H`?z4299-YKaVu_{WXwGV2V27(Vt5WugH?%`rBvqP?li1JT}D7|ENT z#o%yEB8ii>Ba#qCp*_qX_XeR9skXTXk0&pibtYw`OQ2Q!lY)YzW+ZvNjfU3?P9q33 z5u@9PgkoYy;U(fa2S_@q3yP2+`|87>D$>{LIx|d+oXi6a&!G0}JQ)aTjMpnnRP#0Tl7R4ZilIeU1fvojO14}sg^rB7o#JnLF;EaAXbd80ak+GHe0YoB)55Sz>&2L^d zK*IYgi=tswJI&N$lBiV#-zl=sz+@k*8E@%66@hBx((zQhfGFX$cQF*fo2;YNp0hFj z2-89p-t5!^V)Jr(L^dD_(N5wfM)Gf03ZmSUXx>3~L2rG+BYGu_M}hYyxa7jsxx`V2 zrzL!r*Z^q`&6x}Kp=WF#vwKs<1;{4E8w0Lk6ohk|0rABkkTXhyct zGe_)5s8*g#kgO2A+b<6}_{4xdDtAd^*y88fB=@>qx)8qehCcOUc=LFl0~1;%hLLcGVzE&ET{ikWa`nsQyFxUk~-ye&X%aN)f)G!FDS8`mstu$1j`yBCH< z(N4aJviy*m$-Q`2)aA1}pJW#*MjWWD%nEe=9_T1aN@Fcz}=={kvyRV50M5_aI1$%LTs$YQ6=K415{d0yZ@?zjn^7C;-}3G46P z0KubC2-RKX7@c*qj{i{Op#~fA!?$cABj&^ER6@eDX*g#84u?=jlI<)#b)M@9UV?_! zRqxmA$$me`^HBi0_|vR3-8ti_2??fhA15&K^4xiQJM%ovIVCp6e7D0V?2XEgm7LS?z>OX;SAd&Fk+IViV8Ccq5s2d8JOe=LbwUXqr zN%E$?E)?2+C2#(zUUwB1f5{<-?ec)vVaV6M(Pl)@yWvcpS8MpVQQKp_#LvISJyy7n zgwVri5!=%1!h%CtAXF`GbILX30GFq634E@LrLJzS-f5Dbab8Oq_Lgmhn#S0k^Wp%4 z>YNDh5-?v#NP%}hWTXEW$fzvazyjW!Xx7;13l!}Wq;pyyliU}%nvbF^s4m@#j);~Y zFT^u1WIL7|^Fs+`g)`zCM;cHN#-tNoz&Ae(pg6Nv%WoWZ%^-W}Lu8_jwa*2kqDk4HUp{9Sd?mqhaz~R#?^>4hb%OrXXJ=IJFV-vwIDPBH zv2JPr{L3-AWF7a!+?gY5Mz^epHdg>a{Toj7HC5DgN|#H$a1y%sl}qIQ(e(FDadEe5 zJ~za8ZMq@D?_E~u(!sY^nG4c8R<4(IOlwtw8dyw`nR z7yw+*V*!*r_WxY&f8Oc0lO2*=;z8ovVxa|L27&OIRQmbVH)i&$9T;z^ESZYvqUcwY ziX;|3IwLwkY_Ln26e==p#gCVbiDzi*TW9eO$t*&? z+GN*<%yw`om|hw2pOhfY#8iSoNkI-IF=%8?4@mc_JX$Z^IZ&hRW}T1D@zLsPadq*+ z6&>*Jr5zfxgaTct5P3{v7dgcfWC%*xX7J+Q6i{zkvDj7|=s+$}4Hne@7}rtuD8ceO z;n&|yWJfrkENYh843bhvkt~qvl7}kx1&Ub?xQ~&th@pAl!92-oAOM>p)r0V!$T8KM z3e~xBp8SLx%mrqnl|XD8cV)3RB2_WEx};g*o+G26N{$42^cm8JUFAy)<5z*55aOBu z=b|GW@Sz;xKEGIwF0e(hK%IjpkH)@23wm7~Tgnn0%9+~nuX^xpX{uut-NSD2F4E8mbqZWIUboC5`)ZAR^IBk3>2D7EzAwfQA{bYv=P z{p-Nt)&w(>Cn(I3ZLe_0;kMn%FCwXZsX$d{$GkDbLabefd^CRN)9U)X?y{2eoo+L8 zRDpx1rjZ!M*AJP)V0mY{^qr8+A?w*+aDU8_GoUHv85}#?7e>Wo}{^beP(`{?Sw+qDBXa!J=GySSBD_b+jNgz`3TIw0z#Z$a}cPRX?NiJV{j)>5sx-2li--{bgz0Ut;6)aTVfk2&K35@N z%>NyG3MhOy&S1XKP%)sL*$=*Tg)yHBcV%yUA;1MZ73nBt$yb?^eJQN%BjMI4YMSdfK*zz9}XY zWZ-`9o?=f6j$*gJ`T>5Kej{H*xM;C!e5NXnr<2RDv`JE!9tpAN5H)H`-Eixby=Y`& z_vbY1D{)p2IEscUs0qVT{aNvb+NkBjAFqqMC<7!mk>uIodiFY1X~#4rv(facd$euC zx`61@@H+_Mp#@(OsaqSrojxt74Q882@P_h~io&O&Gj)VmbK`f+4>E_F^0ss-JH?uV z)^+iC*GuLKXeq)bN2N&*^VQRPHJ1!!Bl2Q1qY(6MM6k3IFb4%6mno9+94r`eWyYEt zn~eH>BW7pon2lm^1V;`o?6pJd^u93ss1WVZ2#+%iS^a=3Ks>NWxF*oq`^c!g^+}f- z@(;{W(xN!uV0}sDe|B1%g`^n|L)G7YQvbFEVfKQu>hX>_Si(6MX9ncy0T4$Q#e`!5 zx%F-(&pYNeF~PqvZ~J?l)0de{{}-75m7-t{NcVtqk0Zx+z^=)XrueELCM&wBZT6gn ziwjLe&?g3TaCc&F9Ix&$a3b@fMuZWH^ZfCZWb1h01CFY7LsQ~{VVaY@!RzzO4JHrf z(nrw(--JHr07kR>`Rj|$K9sI(*8(%$@XVwDZC7&zjz=8<*Wp;ME!8}n&zCaBv1jzt zN%S=A8h)~U*MxkmGOB6IAn$;OLxJ!dCzk-s5q@JHIZJnxv#cs&Vb12Ub;2Gc zvx}!Bm_BeksfWK{IB!CvjPh2D$9#@s1DW zilWYY|VdTp7#glOp7iIgLl>MbuOJxnFt2`r9{?hE_)|2o0&&=qEGMScb(t8 z*ZFQMNt`7&-`2 zd?i{leM#YVIG9j!PD%@lTwT_9jTfN`R&USii3hv#~rQsdN-|1tL z@+i><11B$!>(-(ckS%kO5~V`c4;aT{uKYWq$cNxsW>gJEV${e7O2c-`vA#rw(4jWa zJ@OiyiOMdNccwxGvdBOx}sZ9TM! z=hv625e`*ov?04HGk%&6Ifqh%cNG`u7ELgOv~pPJ1ik+Q?%YKLxsfaA z(i6-Xj>0}nvsA(@*P9UP@)W3YyDk8B#8*Pq6Jiq7hZ=2A1do68^m~(EWKI7& z?*GnR_!qxx#OKtgC-FAq%3|asiMB(21{aVo(t|(KY3XToe>O+E|W1K@wX(^pJaTa2_8nQ!q zNx)tGB$)b;ZS+?)0K+E(7~bpQ$2-H<{bu-b)Rnm5Fh}y-etsPgDL`&l${K8(x{BX^ z@GPBa-g~v%(RiFpvHijDuQY-Tvx$JN zZjte4S0@7d-PQ9B(lCE__0vDPdTpoz#!hbqE9VLo#dutZ*C!ao!uq3$%aC!*uQ~fR zRdoXnkrkDfm?alr`2S*fOUG}w)0#-VG9Mv^Drud+>tv19PVGTpw6!aiZM8^&q?RyC|ekearGU59U_f4*ehMe%btYka zQ*z?VZnCA}?%Wvwbju5}pZ?APP8^VxL5w&@Txm~6G#vf@gG}sDga9+)%4M!+dnfWM5*o*`kuwGhbpI?nw5BNQ#As}2CPOa>~TVH zIhd3ZBXD97>ui;7LdCMGo>X&s$=uvYt}@Z@=L61xsPXTW1K?Op5heU)R2I?61YTl} zuI_E#cp)ZhxD;fw%%}?Sp=Jura>hoo3=m z56Q;&w0-%%p!mOH8vcmBo=xFAYfeg-{Uu*qN}2=jjFN*w<^=-jTa8TNzGhZu(50_r zF;?SIJU%)3o~P+*S9h<%yl(fO>blMviGGO}6Tijf9^szwMS>@#ZTt`6h7LHSQdpn@|K+u0rdOv}s63Gl#*sY99V?ir-Rc zqpTilPL<6`LhG2)O(Wfd^-cp&yc+=a(%-O$?v}T$5~1tAPw<(53BH7y9jZ7qrw@!{ zSMy34CTDc3<@W?HF9KujxAO#;<|0{#d|Uz3Tw+sEIbfPwnZsjLJctHPbdc!lQ}r=) z#O~;m_i0x-DxjwboBDH_>rP+fBo&?-6c!1rYRCzr84lIMce6mDoZ@Conk+EP_4{+0 zTeFxXSqn*?dO7LDFdrZ=TGxsVYQm-*18Mol8zdQBE7lt9GZLDvHKLht|6L33Q5I!g zV-t7b>KDmy1`tx5uI}}47TfkBrZwmYjuvK{%%c!;Bk&n@UUM;^*4Z0Bgr6rEiT*Lp z0dRK#-@!{~D&g7u^!HkCw64+kFL3`Wz~O(wUHL!Y{uVHe^M7&wKMVK%&x%J4fYuKB zpV#%jvK{`!ecO4}iKy_Vv{11aQ18v3Wl1RC>-`E8sm+o}t=f7ac8UJ+7l@b`_chqH zkceUV--`X}X=9K2Lz5$BMu_ug*m7P9K&cmn&vDJ7rR})sW19Qr@?UR}7jJfyB46Mt zJ^Gj?x)byh1B$&*+ETK;nca}YL0pPX9>DPvHyPyB^s5r2bn$>o{X;^A9|9Me5sw`-q8dj0-%sXtPNJggBQ8YulAyL{s~oK)2f$d<&w!>_U;h zH+DIj)ZdLgwy$Uju&!epy{~RPnZr+cd%B#Rm}dOvv{-<3od^SJD7uDzI_Qyf4)wz7 zP8PuNY>Iz!Jf!Y|aC)*pA5>XL9>bwZ;Qdj{<^Pq#s(4_7sE_t$UM3L_DvC?#nH7M{L`nE1NEwE*)`*5QRtH}GN0Z7TW%5eI zN)6~mHyL)uR*2?$ldG&hI`j>iKYEH)ZzOj=y5Mt5GLwWc3tcqv+0*je6UW0-o95ex zbB5Nq$~N$$@%(k#YqEK7RAk|Or5_)t!Ot%d^nOTpd~e3%QT&B7W7;u{`Ru841HgAr;}=(``q_^V6enkjink0#I1>W*Y=Mz8ypn}eXg zYG!G7DwOmgl1XaI_n=XrZ;?rQ?*n~7FAKsOEg$_)p0~HZ?9@#RTNzn8`a`QgI(CBXiwZ*^OXHBYedCW z-_;7nP`wU#IMy?EZ^0%GZq)f(LYfqY^kf%Y5Gm5Woh!no>PVRe9~25w6L<1?mP?xo z$5NKJNnekND$`1hu(Yz04c3ULQOK1W#6>!9#>v^GkIoS{kJ&v(nJ)1zA^0U&acDX~ z$qhHl*U|2snz%g;<{~s@SkN&z?Z8{s!JbOUP-Z1K<&`Xbp$rs6*YW6|*1#gf#Pplf ziX)vdmWvfN3RdBl>b=&Pu)X{?#z|ueiJh?zRr-zKYPvseqQLlu)ELCGwJLWuRHp{QBoa;_OG=kXe-GZI7WT;HIpdiGVq28FO`$dOQuL5XRlNJsNa1CB->>j*E zpC%x5T$ai=pB5D?!e6``(ddmZ_RDw*wFZ#YVNOD(t-AME3^EieYz?_8|+`m!nA_Hz&b{!56bRC&To5VpcYPHG+~3|dSs z3}Z{2eS>Fef@fO(Z3JACNom|Y)$^_&(c9oV%()AgDfro>R>2_X zF|kle+%2g27035=OCJoEOi**YN>J~yx4+L(2tyGg|9e0EcdoI_`}07rv+wt@tb01?w`TN-@P9r`mG%3tJhqwPKMRj z)W3h6?``^^&%o>Gh^~W9&ZX$qd|BChPJlzQbWB4*r~{X-f(+}S0*_;z8(trG0Q1d0 zR`CIwFPjnc>NO#AnbB=PS|lb;bJU+R(QOZ)ia`QYG0xpY>sJNQ^I`LZrxY<@Ezepj#cWfU;tGtcALad*$Ju_&YgZQ zF(N$%k;y~p#v8CRf&Jc@tVyms51y36&oM0V?h``cSdJ)@poXSu=;ea}{gn7(ZnY4w zDBJx@{x(7U-B0JB#{TH1J5+3a@BNgSnO**DtL7W$Ds{+nZE{S}a}H}{rJ6HfM9Ai08q)Gd1f=-;ovbyl8}obLh&hC$0C zaN~LyRXbi9q6By?UVa$iW4!S%%?#P?sUT$G<$q)sN_x5v{uiMC<$3(ShA76uvpkVn zCSO3DWA1e@p0G4YO6m9}KptTvkJo76)P0M4-#2poZK7dXYT{B8 zx5r1<_N5Q{)4xFnwZnnNDR(WQVT^anE-KwkaZaS@O_3V-z7u56lT?~zEfChA|P`q zco#?Z`smJ*)532)msi4f^w|OyCd+X><;0xSOx0WG#_w;jzU#3FY)lSlS74`5>|4>K;m2%xIZ zholJy%_7yV>{scedl{-(DlR~JsL=sM_@~judG!?kUx5A((?e_%12aA+Moo@HTAdUc zArI+-*VhM+OyN({gHmPt7_S`s83iGr2=?WtyZnp_;sh5#e--C@B%` zuqHE7LFivs4!0#YQ#V18k6eeqI(AYA^TXs&MR_Aw?;p#`3&245<-VI9N;Z*@4>GLU zQ?Lrm8VIqR(|GiX2UGYe*Wz=1CO(YKVe7hd07=ZQ8!C7UFgXuBqR-_I#E-s9Voo_Z z@(a8Gqia|2wW*hQS&a3pd0w9gYlJR*a6pF0LT88M`mdc zdH_xrEWqi~jWZ%kAh&sUJ;+XOA*TmjRVxt!|KWO=g)RIyp#RhLxaBxyF&0>*(2_Bc zUFzn;b)ATa_=FJ6C&J*mW`Wwc-6&=eKDdV0VRYXPdoBcTF!t$XJZk7)+Q`-3=`Kn; z(<%r_HcDrgXn=mnqw>|oq)_UH$f{CZMDfV-=O0!Muf9@n-gjuqupILJdz&7cFi z$q0DDuRf@qumcf4(X5(hYuf$6WA?K=q(m`*{7nmxzlA!g-nEfgRfE(&w2?ha*Ll1@ z+|9^gNpYqKPCsXQ)-68lD;y^Ze}Me`{o{gzJ#GyLGb{2g>Q0O-&RXmP}Ux|>p4-F2yHe@fUPHNw>A+Z6PSRcnkOoj9|g-+nB{^AMh zpQV2H_nzK~m(2LCgZ}3oneHF9hnP-CsF>_vDXD`jpwK_@KTQJMO@R8XN&t73Ji?2@ z+SG3f)TU6V01xZ>;s0ZKaJ;s9e5mmM^>MJk_xzlw8wkaX;z9YgvXdswA-2?#ewa85 zQ&R=`&H}*h^^<)7LmU4a7isCGo1dyK$nFX{UxPkdRAv+jW!v|=MLPDKlNh|I_m;i| zXz5>nw{&ViOOJhT>9rfrH!GY`G8+J+3uowfS≀TiKW(zIja7J-V-o+725BDT2=s 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≠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


?(Z?RL9ggm9T_>NX2Me)Izhp%_+ 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�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�*{|BH?Y=-&Zbu% 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�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 hn`f`XQZWYa_pwZeO literal 0 HcmV?d00001 diff --git a/plugins/MjpegSource2.java b/plugins/MjpegSource2.java new file mode 100644 index 0000000..89ab9ef --- /dev/null +++ b/plugins/MjpegSource2.java @@ -0,0 +1,149 @@ + +import ch.psi.pshell.imaging.SourceBase; +import ch.psi.pshell.imaging.SourceConfig; +import ch.psi.pshell.imaging.Utils; +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.logging.Level; + +/** + * Image source receive frames from a mjpeg server. + */ +public class MjpegSource2 extends SourceBase { + + final String url; + final boolean flushOnUpdate; + + public MjpegSource2(String name, String url) { + this(name, url, false); + } + + public MjpegSource2(String name, String url, boolean flushOnUpdate) { + super(name, new SourceConfig()); + this.url = url; + this.flushOnUpdate = flushOnUpdate; + } + + InputStream stream; + + @Override + protected void doInitialize() throws IOException, InterruptedException { + super.doInitialize(); + URL aux = new URL(url); + stream = aux.openStream(); + if (!stream.markSupported()) { + stream = new BufferedInputStream(stream); + } + } + + Thread monitoredThread; + + @Override + protected void doSetMonitored(boolean value) { + if (value && (monitoredThread == null)) { + monitoredThread = new Thread(() -> { + try { + while (true) { + try { + doUpdate(); + Thread.sleep(1); + } catch (IOException ex) { + getLogger().log(Level.FINE, null, ex); + } + } + } catch (InterruptedException ex) { + return; + } + }); + monitoredThread.setDaemon(true); + monitoredThread.start(); + } else if (!value && (monitoredThread != null)) { + monitoredThread.interrupt(); + monitoredThread = null; + } + } + + final byte[] START_OF_FRAME = {(byte) 0xFF, (byte) 0xD8}; + final byte[] END_OF_FRAME = {(byte) 0xFF, (byte) 0xD9}; + final int MAX_FRAME_SIZE = 512 * 1024; + + @Override + protected void doUpdate() throws IOException, InterruptedException { + byte[] data = null; + if (stream != null) { + if (flushOnUpdate) { + flush(); + } + try { + data = readData(); + } catch (EOFException ex) { + //Try to reopen stream + doInitialize(); + data = readData(); + } + } + if (data == null) { + pushImage(null); + } else { + BufferedImage img = Utils.newImage(data); + pushImage(img); + } + } + byte[] readData() throws IOException { + if (stream != null) { + stream.mark(MAX_FRAME_SIZE); + int startOfFrame = waitBytes(START_OF_FRAME) - START_OF_FRAME.length; + if (startOfFrame >= 0) { + int endOfFrame = waitBytes(END_OF_FRAME); + if (endOfFrame >= 0) { + stream.reset(); + stream.skip(startOfFrame); + int length = endOfFrame ;//- END_OF_FRAME.length ; + byte[] data = new byte[length]; + stream.read(data, 0, length); + return data; + } + } + } + return null; + } + + int waitBytes(byte[] data) throws IOException { + int index = 0; + int dataPos = 0; + while (true) { + int ret = stream.read(); + if (ret < 0) { + throw new EOFException(); + } + byte value = (byte) ret; + if (value == data[dataPos]) { + dataPos++; + if (dataPos == data.length) { + return (index + 1); + } + } else { + dataPos = 0; + } + index++; + if (index >= MAX_FRAME_SIZE) { + return -1; + } + } + } + + public void flush() throws IOException { + //stream.skip(stream.available()); + //TODO: Skipping won't make the current image to be displayed + stream.close(); + stream = new URL(url).openStream(); + if (!stream.markSupported()) { + stream = new BufferedInputStream(stream); + } + } + +} diff --git a/plugins/PuckDetectionPanel.form b/plugins/PuckDetectionPanel.form new file mode 100644 index 0000000..8045142 --- /dev/null +++ b/plugins/PuckDetectionPanel.form @@ -0,0 +1,201 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + <Editor/> + <Renderer/> + </Column> + <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> + <Title/> + <Editor/> + <Renderer/> + </Column> + <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> + <Title/> + <Editor/> + <Renderer/> + </Column> + </TableColumnModel> + </Property> + <Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor"> + <TableHeader reorderingAllowed="true" resizingAllowed="true"/> + </Property> + </Properties> + </Component> + </SubComponents> + </Container> + <Component class="javax.swing.JButton" name="buttonConfigure"> + <Properties> + <Property name="text" type="java.lang.String" value="Configure"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonConfigureActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + <Component class="ch.psi.pshell.swing.DeviceStatePanel" name="deviceStatePanel1"> + <Properties> + <Property name="deviceName" type="java.lang.String" value="puck_detection"/> + </Properties> + </Component> + </SubComponents> +</Form> diff --git a/plugins/PuckDetectionPanel.java b/plugins/PuckDetectionPanel.java new file mode 100644 index 0000000..e07dde6 --- /dev/null +++ b/plugins/PuckDetectionPanel.java @@ -0,0 +1,281 @@ +import ch.psi.mxsc.Puck; +import ch.psi.mxsc.PuckDetection; +import ch.psi.mxsc.PuckState; +import ch.psi.mxsc.PuckState.SwitchState; +import ch.psi.pshell.core.Context; +import ch.psi.pshell.swing.DevicePanel; +import ch.psi.utils.swing.SwingUtils; + +/** + * + */ +public class PuckDetectionPanel extends DevicePanel { + + public PuckDetectionPanel() { + initComponents(); + int row = 0; + for (String segment : new String[]{"A", "B", "C", "D", "E", "F"}){ + for (int puck=1; puck<=5; puck++){ + table.getModel().setValueAt(segment+puck, row++, 0); + } + } + } + + @Override + public PuckDetection getDevice(){ + return (PuckDetection) super.getDevice(); + } + + + @Override + protected void onDeviceCacheChanged(Object value, Object former, long timestamp, boolean arg3) { + for (int row=0; row< 30; row++){ + String name = table.getModel().getValueAt(row, 0).toString(); + //int id = row+1; + try { + Puck puck = getDevice().getPuck(name); + int id = puck.getIndex() + 1; + PuckState state = getDevice().getPuckState(id); + table.getModel().setValueAt(state.online, row, 1); + table.getModel().setValueAt((SwitchState.Off == state.mecSwitch) ? "" : String.valueOf(state.mecSwitch), row, 2); + table.getModel().setValueAt((SwitchState.Off == state.indSwitch) ? "" : String.valueOf(state.indSwitch), row, 3); + table.getModel().setValueAt(puck.getDetection() == null ? "" : puck.getDetection(), row, 4); + table.getModel().setValueAt(puck.getPuckType() == null ? "" : puck.getPuckType(), row, 5); + } catch (Exception ex) { + ex.printStackTrace(); + table.getModel().setValueAt(false, row, 1); + table.getModel().setValueAt(false, row, 2); + table.getModel().setValueAt(false, row, 3); + table.getModel().setValueAt("", row, 4); + table.getModel().setValueAt("", row, 5); + } + } + } + + void execute(String statement, boolean showReturn){ + try { + Context.getInstance().evalLineBackgroundAsync(statement).handle((ret, ex) -> { + if (ex != null){ + showException((Exception)ex); + } else if (showReturn){ + SwingUtils.showMessage(this, "Return", String.valueOf(ret)); + } + return ret; + }); + } catch (Exception ex) { + showException(ex); + } + } + + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + jPanel3 = new javax.swing.JPanel(); + buttonPuckDetCheck = new javax.swing.JButton(); + buttonPuckDetStop = new javax.swing.JButton(); + buttonPuckDetStart = new javax.swing.JButton(); + jPanel1 = new javax.swing.JPanel(); + jScrollPane1 = new javax.swing.JScrollPane(); + table = new javax.swing.JTable(); + buttonConfigure = new javax.swing.JButton(); + deviceStatePanel1 = new ch.psi.pshell.swing.DeviceStatePanel(); + + jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Raspberry Pi ")); + + buttonPuckDetCheck.setText("Check"); + buttonPuckDetCheck.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPuckDetCheckActionPerformed(evt); + } + }); + + buttonPuckDetStop.setText("Stop"); + buttonPuckDetStop.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPuckDetStopActionPerformed(evt); + } + }); + + buttonPuckDetStart.setText("Start"); + buttonPuckDetStart.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPuckDetStartActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonPuckDetCheck) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 107, Short.MAX_VALUE) + .addComponent(buttonPuckDetStop) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 108, Short.MAX_VALUE) + .addComponent(buttonPuckDetStart) + .addContainerGap()) + ); + + jPanel3Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonPuckDetCheck, buttonPuckDetStart, buttonPuckDetStop}); + + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonPuckDetCheck) + .addComponent(buttonPuckDetStop) + .addComponent(buttonPuckDetStart)) + .addContainerGap()) + ); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Detection")); + + table.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null}, + {null, null, null, null, null, null} + }, + new String [] { + "Puck", "Online", "Mechanical", "Inductive", "Detection", "Image" + } + ) { + Class[] types = new Class [] { + java.lang.String.class, java.lang.Boolean.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class + }; + boolean[] canEdit = new boolean [] { + false, false, false, false, false, false + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return canEdit [columnIndex]; + } + }); + jScrollPane1.setViewportView(table); + + buttonConfigure.setText("Configure"); + buttonConfigure.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonConfigureActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(buttonConfigure) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 282, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonConfigure) + .addContainerGap()) + ); + + deviceStatePanel1.setDeviceName("puck_detection"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(deviceStatePanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(0, 0, 0) + .addComponent(deviceStatePanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) + ); + }// </editor-fold>//GEN-END:initComponents + + private void buttonPuckDetCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPuckDetCheckActionPerformed + execute("check_puck_detection()", true); + }//GEN-LAST:event_buttonPuckDetCheckActionPerformed + + private void buttonPuckDetStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPuckDetStopActionPerformed + execute("stop_puck_detection()", false); + }//GEN-LAST:event_buttonPuckDetStopActionPerformed + + private void buttonPuckDetStartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPuckDetStartActionPerformed + execute("start_puck_detection()", false); + }//GEN-LAST:event_buttonPuckDetStartActionPerformed + + private void buttonConfigureActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonConfigureActionPerformed + try { + if (table.getSelectedRow()<0){ + throw new Exception("Select a puck"); + } + String name = table.getModel().getValueAt(table.getSelectedRow(), 0).toString(); + Puck puck = getDevice().getPuck(name); + DevicePanel.showConfigEditor(this, puck, true, false); + } catch (Exception ex) { + showException(ex); + } + + }//GEN-LAST:event_buttonConfigureActionPerformed + + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonConfigure; + private javax.swing.JButton buttonPuckDetCheck; + private javax.swing.JButton buttonPuckDetStart; + private javax.swing.JButton buttonPuckDetStop; + private ch.psi.pshell.swing.DeviceStatePanel deviceStatePanel1; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel3; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JTable table; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/Recovery.form b/plugins/Recovery.form new file mode 100644 index 0000000..fa22373 --- /dev/null +++ b/plugins/Recovery.form @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + </AuxValues> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="-2" max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <Component id="ledKnownPosition" min="-2" max="-2" attributes="0"/> + <EmptySpace min="-2" max="-2" attributes="0"/> + <Component id="jLabel6" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/> + <Component id="textPosition" max="32767" attributes="0"/> + </Group> + <Group type="102" alignment="0" attributes="0"> + <Component id="ledValidSegment" min="-2" max="-2" attributes="0"/> + <EmptySpace min="-2" max="-2" attributes="0"/> + <Component id="jLabel7" min="-2" max="-2" attributes="0"/> + <EmptySpace min="-2" pref="16" max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace min="0" pref="103" max="32767" attributes="0"/> + <Component id="jLabel8" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="textDistance" min="-2" pref="86" max="-2" attributes="0"/> + </Group> + <Component id="textSegment" max="32767" attributes="0"/> + </Group> + </Group> + </Group> + <EmptySpace min="-2" pref="8" max="-2" attributes="0"/> + </Group> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="32767" attributes="0"/> + <Group type="103" groupAlignment="0" max="-2" attributes="0"> + <Component id="buttonRecover" pref="179" max="32767" attributes="0"/> + <Component id="buttonAbort" max="32767" attributes="0"/> + </Group> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="ledKnownPosition" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="jLabel6" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="textPosition" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="ledValidSegment" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="jLabel7" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="textSegment" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="jLabel8" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="textDistance" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace pref="61" max="32767" attributes="0"/> + <Component id="buttonRecover" min="-2" max="-2" attributes="0"/> + <EmptySpace type="separate" max="-2" attributes="0"/> + <Component id="buttonAbort" min="-2" max="-2" attributes="0"/> + <EmptySpace min="-2" pref="24" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="ch.psi.pshell.swing.Led" name="ledKnownPosition"> + </Component> + <Component class="javax.swing.JLabel" name="jLabel6"> + <Properties> + <Property name="text" type="java.lang.String" value="Known position"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledValidSegment"> + </Component> + <Component class="javax.swing.JLabel" name="jLabel7"> + <Properties> + <Property name="text" type="java.lang.String" value="Valid segment"/> + </Properties> + </Component> + <Component class="javax.swing.JButton" name="buttonRecover"> + <Properties> + <Property name="text" type="java.lang.String" value="Recover"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonRecoverActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="buttonAbort"> + <Properties> + <Property name="text" type="java.lang.String" value="Abort"/> + <Property name="enabled" type="boolean" value="false"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonAbortActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JTextField" name="textPosition"> + <Properties> + <Property name="editable" type="boolean" value="false"/> + </Properties> + </Component> + <Component class="javax.swing.JTextField" name="textSegment"> + <Properties> + <Property name="editable" type="boolean" value="false"/> + </Properties> + </Component> + <Component class="javax.swing.JTextField" name="textDistance"> + <Properties> + <Property name="editable" type="boolean" value="false"/> + <Property name="horizontalAlignment" type="int" value="11"/> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel8"> + <Properties> + <Property name="text" type="java.lang.String" value="Distance:"/> + </Properties> + </Component> + </SubComponents> +</Form> diff --git a/plugins/Recovery.java b/plugins/Recovery.java new file mode 100644 index 0000000..ee1e216 --- /dev/null +++ b/plugins/Recovery.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2014-2017 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.device.Device; +import ch.psi.pshell.ui.Panel; +import ch.psi.utils.State; +import ch.psi.utils.swing.SwingUtils; +import java.awt.Color; +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; + +/** + * + */ +public class Recovery extends Panel { + + public Recovery() { + initComponents(); + startTimer(2500, 200); + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + getContext().setGlobal("recovering", null); + } + + @Override + public void onStateChange(State state, State former) { + if (state==State.Ready){ + SwingUtilities.invokeLater(()->{onTimer();}); + } + textSegment.setEnabled(state == State.Ready); + textDistance.setEnabled(state == State.Ready); + textPosition.setEnabled(state == State.Ready); + updateButton(); + } + + void updateButton(){ + buttonRecover.setEnabled((getContext().getState().isNormal()) && + (textPosition.getText().trim().isEmpty()) && + (!textSegment.getText().trim().isEmpty()) && + !isRecovering() + ); + + } + + boolean isRecovering(){ + return "true".equals(getContext().getGlobal("recovering")); + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + + + @Override + protected void onTimer() { + System.out.println("."); + Device robot = getContext().getDevicePool().getByName("robot", Device.class); + if ((robot==null) || (!robot.getState().isNormal())){ + System.out.println("*"); + ledValidSegment.setColor(Color.BLACK); + textSegment.setText(""); + ledKnownPosition.setColor(Color.BLACK); + textPosition.setText(""); + } else { + if (getState().isNormal()){ + String point = null; + try{ + point = (String) eval("robot.get_current_point()", true); + ledKnownPosition.setColor((point == null) ? Color.RED : Color.GREEN); + textPosition.setText((point == null) ? "": point); + } catch (Exception ex) { + System.out.println(ex); + ledKnownPosition.setColor(Color.BLACK); + textPosition.setText(""); + } + List segment = null; + try{ + segment = (List) eval("get_current_segment()", true); + ledValidSegment.setColor((segment == null) ? Color.RED : Color.GREEN); + textSegment.setText((segment == null) ? "": segment.get(0) + "->" + segment.get(1) + " [" + segment.get(2) + "mm]"); + if ((segment == null)||(point!=null)){ + textDistance.setText(""); + } else { + try{ + Object distance = eval("get_current_distance()", true); + textDistance.setText((distance == null) ? "" : String.format("%1.2f",((Number)distance).doubleValue())); + } catch (Exception ex) { + textDistance.setText(""); + } + } + } catch (Exception ex) { + System.out.println(ex); + ledValidSegment.setColor(Color.BLACK); + textSegment.setText(""); + textDistance.setText(""); + } + try{ + Boolean is_in_dewar = (Boolean) eval("is_in_dewar()", true); + if ((segment==null) && is_in_dewar){ + ledValidSegment.setColor(Color.GREEN); + textSegment.setText("Inside Dewar"); + } + } catch (Exception ex) { + System.out.println(ex); + } + } + } + updateButton(); + } + + + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + ledKnownPosition = new ch.psi.pshell.swing.Led(); + jLabel6 = new javax.swing.JLabel(); + ledValidSegment = new ch.psi.pshell.swing.Led(); + jLabel7 = new javax.swing.JLabel(); + buttonRecover = new javax.swing.JButton(); + buttonAbort = new javax.swing.JButton(); + textPosition = new javax.swing.JTextField(); + textSegment = new javax.swing.JTextField(); + textDistance = new javax.swing.JTextField(); + jLabel8 = new javax.swing.JLabel(); + + jLabel6.setText("Known position"); + + jLabel7.setText("Valid segment"); + + buttonRecover.setText("Recover"); + buttonRecover.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonRecoverActionPerformed(evt); + } + }); + + buttonAbort.setText("Abort"); + buttonAbort.setEnabled(false); + buttonAbort.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonAbortActionPerformed(evt); + } + }); + + textPosition.setEditable(false); + + textSegment.setEditable(false); + + textDistance.setEditable(false); + textDistance.setHorizontalAlignment(javax.swing.JTextField.TRAILING); + + jLabel8.setText("Distance:"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(ledKnownPosition, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(textPosition)) + .addGroup(layout.createSequentialGroup() + .addComponent(ledValidSegment, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel7) + .addGap(16, 16, 16) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 103, Short.MAX_VALUE) + .addComponent(jLabel8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(textDistance, javax.swing.GroupLayout.PREFERRED_SIZE, 86, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(textSegment)))) + .addGap(8, 8, 8)) + .addGroup(layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(buttonRecover, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE) + .addComponent(buttonAbort, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(ledKnownPosition, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6) + .addComponent(textPosition, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(ledValidSegment, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel7) + .addComponent(textSegment, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel8) + .addComponent(textDistance, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 61, Short.MAX_VALUE) + .addComponent(buttonRecover) + .addGap(18, 18, 18) + .addComponent(buttonAbort) + .addGap(24, 24, 24)) + ); + }// </editor-fold>//GEN-END:initComponents + + private void buttonRecoverActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonRecoverActionPerformed + try{ + if (isRecovering()){ + getLogger().warning("Ongoing recovery "); + return; + } + buttonAbort.setEnabled(true); + getContext().setGlobal("recovering", "true"); + Device robot = getContext().getDevicePool().getByName("robot", Device.class); + abort(); + getContext().waitState(State.Ready, 5000); + eval("robot.stop_task()", false); + robot.waitReady(5000); + evalAsync("recover()", false).handle((ret, ex) -> { + if (ex != null){ + if (!getContext().isAborted()){ + showException((Exception)ex); + } + } else { + SwingUtils.showMessage(getTopLevel(), "Return", String.valueOf(ret)); + } + getContext().setGlobal("recovering", null); + buttonAbort.setEnabled(false); + return ret; + }); + } catch (Exception ex) { + showException(ex); + getContext().setGlobal("recovering", null); + buttonAbort.setEnabled(false); + } + }//GEN-LAST:event_buttonRecoverActionPerformed + + private void buttonAbortActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonAbortActionPerformed + try { + abort(); + try{ + eval("robot.disable()", true); + } catch (Exception ex){ + this.showException(ex); + } + try{ + eval("robot.stop_task()", true); + } catch (Exception ex){ + this.showException(ex); + } + try{ + eval("robot.reset_motion()", true); + } catch (Exception ex){ + this.showException(ex); + } + } catch (InterruptedException ex) { + //showException(ex); + } + }//GEN-LAST:event_buttonAbortActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonAbort; + private javax.swing.JButton buttonRecover; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private ch.psi.pshell.swing.Led ledKnownPosition; + private ch.psi.pshell.swing.Led ledValidSegment; + private javax.swing.JTextField textDistance; + private javax.swing.JTextField textPosition; + private javax.swing.JTextField textSegment; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/RobotModbus.java b/plugins/RobotModbus.java new file mode 100644 index 0000000..ca2e149 --- /dev/null +++ b/plugins/RobotModbus.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.device.*; +import ch.psi.pshell.modbus.*; + +/** + */ +public class RobotModbus extends ModbusTCP { + + public RobotModbus(String name, String server) { + super(name, server); + } + + public Object run() throws Exception{ + Thread.sleep(1000); + return ("Done run"); + } + +} diff --git a/plugins/RobotPanel.form b/plugins/RobotPanel.form new file mode 100644 index 0000000..bf9c0d6 --- /dev/null +++ b/plugins/RobotPanel.form @@ -0,0 +1,587 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + </AuxValues> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="jPanel1" max="32767" attributes="0"/> + <Component id="jPanel2" alignment="0" max="32767" attributes="0"/> + <Component id="jPanel3" alignment="0" max="32767" attributes="0"/> + <Component id="panelState" alignment="0" max="32767" attributes="0"/> + <Component id="jPanel4" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <Component id="jPanel1" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="jPanel4" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="jPanel2" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="jPanel3" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="panelState" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Container class="javax.swing.JPanel" name="jPanel1"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Power"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Component id="jLabel1" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledPowered" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="panelPowerCtr" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace min="-2" pref="4" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel1" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledPowered" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="panelPowerCtr" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="ch.psi.pshell.swing.Led" name="ledPowered"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel1"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Powered:"/> + </Properties> + </Component> + <Container class="javax.swing.JPanel" name="panelPowerCtr"> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="buttonEnable" min="-2" max="-2" attributes="0"/> + <EmptySpace pref="18" max="32767" attributes="0"/> + <Component id="buttonDisable" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Group type="103" alignment="2" groupAlignment="1" max="-2" attributes="0"> + <Component id="buttonDisable" alignment="1" max="32767" attributes="0"/> + <Component id="buttonEnable" alignment="1" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JButton" name="buttonDisable"> + <Properties> + <Property name="text" type="java.lang.String" value="Disable"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonDisableActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="buttonEnable"> + <Properties> + <Property name="text" type="java.lang.String" value="Enable"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonEnableActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + </SubComponents> + </Container> + <Container class="javax.swing.JPanel" name="jPanel2"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Motion"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <Component id="jLabel7" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledMoving" min="-2" max="-2" attributes="0"/> + </Group> + <Component id="jLabel2" alignment="0" min="-2" pref="52" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="14" max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" max="-2" attributes="0"> + <Component id="panelMotionCtr" max="32767" attributes="0"/> + <Component id="spinnerSpeed" max="32767" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="-2" pref="4" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel7" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledMoving" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="panelMotionCtr" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="jLabel2" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="spinnerSpeed" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JLabel" name="jLabel2"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Speed:"/> + </Properties> + </Component> + <Component class="javax.swing.JSpinner" name="spinnerSpeed"> + <Properties> + <Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor"> + <SpinnerModel initial="10" maximum="100" numberType="java.lang.Integer" stepSize="1" type="number"/> + </Property> + </Properties> + <Events> + <EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="spinnerSpeedStateChanged"/> + </Events> + <AuxValues> + <AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new ch.psi.utils.swing.HorizontalSpinner()"/> + </AuxValues> + </Component> + <Component class="javax.swing.JLabel" name="jLabel7"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Moving:"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledMoving"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Container class="javax.swing.JPanel" name="panelMotionCtr"> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="-2" pref="0" max="-2" attributes="0"/> + <Component id="butonStop" pref="45" max="32767" attributes="0"/> + <EmptySpace min="-2" max="-2" attributes="0"/> + <Component id="buttonPause" pref="46" max="32767" attributes="0"/> + <EmptySpace min="-2" max="-2" attributes="0"/> + <Component id="buttonResume" pref="47" max="32767" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="-2" pref="0" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="butonStop" alignment="2" max="32767" attributes="0"/> + <Component id="buttonPause" alignment="2" max="32767" attributes="0"/> + <Component id="buttonResume" alignment="2" max="32767" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="0" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JButton" name="butonStop"> + <Properties> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> + <Connection code="new javax.swing.ImageIcon(App.class.getResource("/ch/psi/pshell/ui/Stop.png"))" type="code"/> + </Property> + <Property name="text" type="java.lang.String" value=" "/> + <Property name="horizontalTextPosition" type="int" value="0"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="butonStopActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="buttonResume"> + <Properties> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> + <Connection code="new javax.swing.ImageIcon(App.class.getResource("/ch/psi/pshell/ui/Play.png"))" type="code"/> + </Property> + <Property name="text" type="java.lang.String" value=" "/> + <Property name="horizontalTextPosition" type="int" value="0"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonResumeActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="buttonPause"> + <Properties> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> + <Connection code="new javax.swing.ImageIcon(App.class.getResource("/ch/psi/pshell/ui/Pause.png"))" type="code"/> + </Property> + <Property name="text" type="java.lang.String" value=" "/> + <Property name="horizontalTextPosition" type="int" value="0"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonPauseActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + </SubComponents> + </Container> + <Container class="javax.swing.JPanel" name="jPanel3"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Status"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="-2" max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <Component id="jLabel8" linkSize="2" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledTask" min="-2" max="-2" attributes="0"/> + </Group> + <Group type="103" alignment="0" groupAlignment="1" attributes="0"> + <Group type="102" attributes="0"> + <Component id="jLabel4" linkSize="2" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledEmpty" min="-2" max="-2" attributes="0"/> + </Group> + <Group type="102" alignment="1" attributes="0"> + <Component id="jLabel3" linkSize="2" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledSettled" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + <Group type="102" attributes="0"> + <Component id="jLabel9" linkSize="2" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledPosition" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="textTask" max="32767" attributes="0"/> + <Component id="textPosition" max="32767" attributes="0"/> + </Group> + </Group> + <Group type="102" alignment="0" attributes="0"> + <Component id="jLabel6" linkSize="2" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledMode" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="textMode" max="32767" attributes="0"/> + </Group> + </Group> + <EmptySpace min="-2" max="-2" attributes="0"/> + <Component id="buttonAbort" min="-2" max="-2" attributes="0"/> + <EmptySpace min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="1" attributes="0"> + <EmptySpace min="-2" pref="4" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel3" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledSettled" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel4" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledEmpty" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="textMode" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledMode" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="jLabel6" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="buttonAbort" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="jLabel8" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledTask" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="textTask" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel9" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledPosition" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="textPosition" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JLabel" name="jLabel3"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Settled:"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledSettled"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel4"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Empty:"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledEmpty"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JTextField" name="textTask"> + <Properties> + <Property name="editable" type="boolean" value="false"/> + <Property name="horizontalAlignment" type="int" value="0"/> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel6"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Mode:"/> + </Properties> + </Component> + <Component class="javax.swing.JTextField" name="textMode"> + <Properties> + <Property name="editable" type="boolean" value="false"/> + <Property name="horizontalAlignment" type="int" value="0"/> + </Properties> + </Component> + <Component class="javax.swing.JButton" name="buttonAbort"> + <Properties> + <Property name="text" type="java.lang.String" value="Abort"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonAbortActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JLabel" name="jLabel8"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Task:"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledTask"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledMode"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JTextField" name="textPosition"> + <Properties> + <Property name="editable" type="boolean" value="false"/> + <Property name="horizontalAlignment" type="int" value="0"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledPosition"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel9"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Position:"/> + </Properties> + </Component> + </SubComponents> + </Container> + <Component class="ch.psi.pshell.swing.DeviceStatePanel" name="panelState"> + </Component> + <Container class="javax.swing.JPanel" name="jPanel4"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Tool"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Component id="jLabel5" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledOpen" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="panelPowerCtr1" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace min="-2" pref="4" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel5" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledOpen" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="panelPowerCtr1" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="-2" pref="2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="ch.psi.pshell.swing.Led" name="ledOpen"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel5"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Open:"/> + </Properties> + </Component> + <Container class="javax.swing.JPanel" name="panelPowerCtr1"> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="buttonOpen" linkSize="3" min="-2" max="-2" attributes="0"/> + <EmptySpace pref="18" max="32767" attributes="0"/> + <Component id="buttonClose" linkSize="3" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Group type="103" alignment="2" groupAlignment="1" max="-2" attributes="0"> + <Component id="buttonClose" alignment="1" max="32767" attributes="0"/> + <Component id="buttonOpen" alignment="1" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JButton" name="buttonClose"> + <Properties> + <Property name="text" type="java.lang.String" value=" Close "/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonCloseActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="buttonOpen"> + <Properties> + <Property name="text" type="java.lang.String" value=" Open "/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonOpenActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + </SubComponents> + </Container> + </SubComponents> +</Form> diff --git a/plugins/RobotPanel.java b/plugins/RobotPanel.java new file mode 100644 index 0000000..e841f57 --- /dev/null +++ b/plugins/RobotPanel.java @@ -0,0 +1,685 @@ + +import ch.psi.pshell.device.Device; +import ch.psi.pshell.ui.App; +import ch.psi.pshell.swing.DevicePanel; +import ch.psi.pshell.core.Context; +import ch.psi.utils.State; +import java.awt.Color; +import java.util.Map; +import javax.swing.JSpinner; +import javax.swing.JTextField; + +/** + * + */ +public class RobotPanel extends DevicePanel { + + /** + * Creates new form RobotPanel + */ + public RobotPanel() { + initComponents(); + ((JSpinner.DefaultEditor)spinnerSpeed.getEditor()).getTextField().setHorizontalAlignment(JTextField.CENTER); + } + + @Override + public void setDevice(Device device) { + super.setDevice(device); + if (device == null) { + + } + panelState.setDevice(device); + } + + @Override + protected void onDeviceStateChanged(State state, State former) { + boolean enabled = state.isNormal(); + spinnerSpeed.setEnabled(enabled); + buttonAbort.setEnabled(enabled); + if (!enabled){ + buttonEnable.setEnabled(enabled); + buttonDisable.setEnabled(enabled); + butonStop.setEnabled(enabled); + buttonPause.setEnabled(enabled); + buttonResume.setEnabled(enabled); + } + } + + boolean updating; + @Override + protected void onDeviceCacheChanged(Object value, Object former, long timestamp, boolean valueChange) { + updating = true; + try{ + Boolean powered = null; + Boolean empty = null; + Boolean settled = null; + Boolean moving = null; + Boolean open = null; + Integer speed = null; + String task = null; + String mode = null; + String position = null; + + if ((value != null) && (value instanceof Map)) { + Map status = (Map) value; + try { + powered = (Boolean) status.get("powered"); + } catch (Exception ex) { + } + try { + empty = (Boolean) status.get("empty"); + } catch (Exception ex) { + } + try { + settled = (Boolean) status.get("settled"); + } catch (Exception ex) { + } + try { + open = (Boolean) status.get("open"); + } catch (Exception ex) { + } + try { + speed = (Integer) status.get("speed"); + } catch (Exception ex) { + } + try { + position = (String) status.get("pos"); + if (position==null){ + position = ""; + } + } catch (Exception ex) { + } + try { + task = ((status.containsKey("task")) && (status.get("task") == null)) ? "" : ((String) status.get("task")).trim(); + } catch (Exception ex) { + System.out.println(ex); + ex.printStackTrace(); + } + try { + mode = ((String) status.get("mode")).trim(); + //mode = (String) Context.getInstance().evalLineBackground(getDevice().getName() + ".working_mode"); + } catch (Exception ex) { + } + try { + //if (status.get("status").equals("hold")){ + // moving = false; + //} else if (status.get("status").equals("move")){ + // moving = true; + //} + moving = Boolean.FALSE.equals(settled) || Boolean.FALSE.equals(empty); + } catch (Exception ex) { + } + } + + boolean remote = mode.equals("remote"); + boolean enabled = Boolean.TRUE.equals(powered); + buttonEnable.setEnabled((powered != null) && (powered == false)); + buttonDisable.setEnabled(enabled); + + butonStop.setEnabled(remote && Boolean.TRUE.equals(moving)); + buttonPause.setEnabled(remote && enabled && Boolean.TRUE.equals(moving) && Boolean.FALSE.equals(settled)); + buttonResume.setEnabled(remote && enabled && Boolean.TRUE.equals(moving) && Boolean.TRUE.equals(settled)); + + + ledPowered.setColor((powered == null) ? Color.GRAY : (powered ? Color.YELLOW : Color.DARK_GRAY)); + ledEmpty.setColor((empty == null) ? Color.GRAY : (empty ? Color.GREEN : Color.YELLOW)); + ledSettled.setColor((settled == null) ? Color.GRAY : (settled ? Color.GREEN : Color.YELLOW)); + ledOpen.setColor((open == null) ? Color.GRAY : (open ? Color.GREEN : Color.DARK_GRAY)); + ledMoving.setColor((moving == null) ? Color.GRAY : (moving ? Color.YELLOW : Color.DARK_GRAY)); + textTask.setText((task == null) ? "" : task); + ledTask.setColor((task == null) ? Color.GRAY : (task.isEmpty() ? Color.DARK_GRAY : Color.YELLOW)); + textPosition.setText((position == null) ? "" : position); + ledPosition.setColor((position == null) ? Color.GRAY : (position.isEmpty() ? Color.DARK_GRAY : Color.GREEN)); + //buttonAbort.setEnabled(!textTask.getText().isEmpty()); + spinnerSpeed.setEnabled(speed != null); + if (speed == null) { + spinnerSpeed.setValue(0); + } else { + spinnerSpeed.setValue(speed); + } + ledMode.setColor((mode == null) ? Color.BLACK : (remote ? Color.GREEN : Color.YELLOW)); + textMode.setText((mode == null) ? "" : mode); + + } finally{ + updating = false; + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + ledPowered = new ch.psi.pshell.swing.Led(); + jLabel1 = new javax.swing.JLabel(); + panelPowerCtr = new javax.swing.JPanel(); + buttonDisable = new javax.swing.JButton(); + buttonEnable = new javax.swing.JButton(); + jPanel2 = new javax.swing.JPanel(); + jLabel2 = new javax.swing.JLabel(); + spinnerSpeed = new ch.psi.utils.swing.HorizontalSpinner(); + jLabel7 = new javax.swing.JLabel(); + ledMoving = new ch.psi.pshell.swing.Led(); + panelMotionCtr = new javax.swing.JPanel(); + butonStop = new javax.swing.JButton(); + buttonResume = new javax.swing.JButton(); + buttonPause = new javax.swing.JButton(); + jPanel3 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + ledSettled = new ch.psi.pshell.swing.Led(); + jLabel4 = new javax.swing.JLabel(); + ledEmpty = new ch.psi.pshell.swing.Led(); + textTask = new javax.swing.JTextField(); + jLabel6 = new javax.swing.JLabel(); + textMode = new javax.swing.JTextField(); + buttonAbort = new javax.swing.JButton(); + jLabel8 = new javax.swing.JLabel(); + ledTask = new ch.psi.pshell.swing.Led(); + ledMode = new ch.psi.pshell.swing.Led(); + textPosition = new javax.swing.JTextField(); + ledPosition = new ch.psi.pshell.swing.Led(); + jLabel9 = new javax.swing.JLabel(); + panelState = new ch.psi.pshell.swing.DeviceStatePanel(); + jPanel4 = new javax.swing.JPanel(); + ledOpen = new ch.psi.pshell.swing.Led(); + jLabel5 = new javax.swing.JLabel(); + panelPowerCtr1 = new javax.swing.JPanel(); + buttonClose = new javax.swing.JButton(); + buttonOpen = new javax.swing.JButton(); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Power")); + + ledPowered.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel1.setText("Powered:"); + + buttonDisable.setText("Disable"); + buttonDisable.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonDisableActionPerformed(evt); + } + }); + + buttonEnable.setText("Enable"); + buttonEnable.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonEnableActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelPowerCtrLayout = new javax.swing.GroupLayout(panelPowerCtr); + panelPowerCtr.setLayout(panelPowerCtrLayout); + panelPowerCtrLayout.setHorizontalGroup( + panelPowerCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPowerCtrLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(buttonEnable) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 18, Short.MAX_VALUE) + .addComponent(buttonDisable)) + ); + panelPowerCtrLayout.setVerticalGroup( + panelPowerCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPowerCtrLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(panelPowerCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(buttonDisable, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonEnable)) + .addGap(0, 0, 0)) + ); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledPowered, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(panelPowerCtr, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel1) + .addComponent(ledPowered, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(panelPowerCtr, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2)) + ); + + jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("Motion")); + + jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel2.setText("Speed:"); + + spinnerSpeed.setModel(new javax.swing.SpinnerNumberModel(10, null, 100, 1)); + spinnerSpeed.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerSpeedStateChanged(evt); + } + }); + + jLabel7.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel7.setText("Moving:"); + + ledMoving.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + butonStop.setIcon(new javax.swing.ImageIcon(App.class.getResource("/ch/psi/pshell/ui/Stop.png"))); + butonStop.setText(" "); + butonStop.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + butonStop.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + butonStopActionPerformed(evt); + } + }); + + buttonResume.setIcon(new javax.swing.ImageIcon(App.class.getResource("/ch/psi/pshell/ui/Play.png"))); + buttonResume.setText(" "); + buttonResume.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonResume.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonResumeActionPerformed(evt); + } + }); + + buttonPause.setIcon(new javax.swing.ImageIcon(App.class.getResource("/ch/psi/pshell/ui/Pause.png"))); + buttonPause.setText(" "); + buttonPause.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonPause.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPauseActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelMotionCtrLayout = new javax.swing.GroupLayout(panelMotionCtr); + panelMotionCtr.setLayout(panelMotionCtrLayout); + panelMotionCtrLayout.setHorizontalGroup( + panelMotionCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelMotionCtrLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(butonStop, javax.swing.GroupLayout.DEFAULT_SIZE, 45, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonPause, javax.swing.GroupLayout.DEFAULT_SIZE, 46, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonResume, javax.swing.GroupLayout.DEFAULT_SIZE, 47, Short.MAX_VALUE) + .addGap(0, 0, 0)) + ); + panelMotionCtrLayout.setVerticalGroup( + panelMotionCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelMotionCtrLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(panelMotionCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(butonStop, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonPause, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonResume, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(0, 0, 0)) + ); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel7, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledMoving, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(14, 14, 14) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(panelMotionCtr, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerSpeed)) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel7) + .addComponent(ledMoving, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(panelMotionCtr, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(spinnerSpeed, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2)) + ); + + jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Status")); + + jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel3.setText("Settled:"); + + ledSettled.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel4.setText("Empty:"); + + ledEmpty.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + textTask.setEditable(false); + textTask.setHorizontalAlignment(javax.swing.JTextField.CENTER); + + jLabel6.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel6.setText("Mode:"); + + textMode.setEditable(false); + textMode.setHorizontalAlignment(javax.swing.JTextField.CENTER); + + buttonAbort.setText("Abort"); + buttonAbort.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonAbortActionPerformed(evt); + } + }); + + jLabel8.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel8.setText("Task:"); + + ledTask.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + ledMode.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + textPosition.setEditable(false); + textPosition.setHorizontalAlignment(javax.swing.JTextField.CENTER); + + ledPosition.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + jLabel9.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel9.setText("Position:"); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(jLabel8, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledTask, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledEmpty, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledSettled, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(jLabel9, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledPosition, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(textTask) + .addComponent(textPosition))) + .addGroup(jPanel3Layout.createSequentialGroup() + .addComponent(jLabel6, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledMode, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(textMode))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonAbort) + .addContainerGap()) + ); + + jPanel3Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jLabel3, jLabel4, jLabel6, jLabel8, jLabel9}); + + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel3) + .addComponent(ledSettled, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel4) + .addComponent(ledEmpty, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(textMode, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(ledMode, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6)) + .addGap(2, 2, 2) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonAbort) + .addComponent(jLabel8) + .addComponent(ledTask, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(textTask, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel9) + .addComponent(ledPosition, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(textPosition, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2)) + ); + + jPanel4.setBorder(javax.swing.BorderFactory.createTitledBorder("Tool")); + + ledOpen.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + jLabel5.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel5.setText("Open:"); + + buttonClose.setText(" Close "); + buttonClose.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonCloseActionPerformed(evt); + } + }); + + buttonOpen.setText(" Open "); + buttonOpen.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonOpenActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelPowerCtr1Layout = new javax.swing.GroupLayout(panelPowerCtr1); + panelPowerCtr1.setLayout(panelPowerCtr1Layout); + panelPowerCtr1Layout.setHorizontalGroup( + panelPowerCtr1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPowerCtr1Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(buttonOpen) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 18, Short.MAX_VALUE) + .addComponent(buttonClose)) + ); + + panelPowerCtr1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonClose, buttonOpen}); + + panelPowerCtr1Layout.setVerticalGroup( + panelPowerCtr1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPowerCtr1Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(panelPowerCtr1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(buttonClose, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonOpen)) + .addGap(0, 0, 0)) + ); + + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel5, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(panelPowerCtr1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel5) + .addComponent(ledOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(panelPowerCtr1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelState, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(panelState, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// </editor-fold>//GEN-END:initComponents + + private void buttonEnableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonEnableActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".enable()"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_buttonEnableActionPerformed + + private void buttonDisableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonDisableActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".disable()"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_buttonDisableActionPerformed + + private void spinnerSpeedStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerSpeedStateChanged + try{ + if (!updating){ + Context.getInstance().evalLineBackground(getDevice().getName() + ".set_monitor_speed(" + spinnerSpeed.getValue() + ")"); + } + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_spinnerSpeedStateChanged + + private void butonStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_butonStopActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".reset_motion()"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_butonStopActionPerformed + + private void buttonPauseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPauseActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".stop()"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_buttonPauseActionPerformed + + private void buttonResumeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonResumeActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".resume()"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_buttonResumeActionPerformed + + private void buttonAbortActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonAbortActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".stop_task()"); + //Context.getInstance().evalLineBackground(getDevice().getName() + ".task_kill('" + textTask.getText() + "')"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_buttonAbortActionPerformed + + private void buttonCloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonCloseActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".close_tool()"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_buttonCloseActionPerformed + + private void buttonOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonOpenActionPerformed + try{ + Context.getInstance().evalLineBackground(getDevice().getName() + ".open_tool()"); + } catch (Exception ex){ + this.showException(ex); + } + }//GEN-LAST:event_buttonOpenActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton butonStop; + private javax.swing.JButton buttonAbort; + private javax.swing.JButton buttonClose; + private javax.swing.JButton buttonDisable; + private javax.swing.JButton buttonEnable; + private javax.swing.JButton buttonOpen; + private javax.swing.JButton buttonPause; + private javax.swing.JButton buttonResume; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private javax.swing.JLabel jLabel9; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JPanel jPanel4; + private ch.psi.pshell.swing.Led ledEmpty; + private ch.psi.pshell.swing.Led ledMode; + private ch.psi.pshell.swing.Led ledMoving; + private ch.psi.pshell.swing.Led ledOpen; + private ch.psi.pshell.swing.Led ledPosition; + private ch.psi.pshell.swing.Led ledPowered; + private ch.psi.pshell.swing.Led ledSettled; + private ch.psi.pshell.swing.Led ledTask; + private javax.swing.JPanel panelMotionCtr; + private javax.swing.JPanel panelPowerCtr; + private javax.swing.JPanel panelPowerCtr1; + private ch.psi.pshell.swing.DeviceStatePanel panelState; + private javax.swing.JSpinner spinnerSpeed; + private javax.swing.JTextField textMode; + private javax.swing.JTextField textPosition; + private javax.swing.JTextField textTask; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/RobotTcp.java b/plugins/RobotTcp.java new file mode 100644 index 0000000..d3f4c33 --- /dev/null +++ b/plugins/RobotTcp.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.device.*; +import ch.psi.pshell.serial.*; + +/** + */ +public class RobotTcp extends TcpDevice { + + public RobotTcp(String name, String server) { + super(name, server); + } + + public Object run() throws Exception{ + Thread.sleep(1000); + return ("Done"); + } + +} diff --git a/plugins/SmartMagnetConfig.java b/plugins/SmartMagnetConfig.java new file mode 100644 index 0000000..2c6213c --- /dev/null +++ b/plugins/SmartMagnetConfig.java @@ -0,0 +1,14 @@ + +import ch.psi.pshell.device.DeviceConfig; + +/** + * + */ +public class SmartMagnetConfig extends DeviceConfig{ + public double holdingCurrent; + public double restingCurrent; + public double mountCurrent; + public double unmountCurrent; + public double remanenceCurrent; + +} diff --git a/plugins/SmartMagnetPanel.form b/plugins/SmartMagnetPanel.form new file mode 100644 index 0000000..432e7bd --- /dev/null +++ b/plugins/SmartMagnetPanel.form @@ -0,0 +1,284 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + </AuxValues> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="jPanel1" max="32767" attributes="0"/> + <Component id="panelState" alignment="0" max="32767" attributes="0"/> + <Component id="panelCurrent" max="32767" attributes="0"/> + <Component id="jPanel2" max="32767" attributes="0"/> + <Component id="jPanel3" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <Component id="jPanel1" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="panelCurrent" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="jPanel2" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="jPanel3" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="panelState" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Container class="javax.swing.JPanel" name="jPanel1"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Status"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <Component id="jLabel1" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledStatus" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="panelPowerCtr" min="-2" max="-2" attributes="0"/> + </Group> + <Group type="102" alignment="0" attributes="0"> + <Component id="jLabel5" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledMounted" min="-2" max="-2" attributes="0"/> + </Group> + <Group type="102" alignment="0" attributes="0"> + <Component id="jLabel10" min="-2" pref="52" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="panelCurrentRb" min="-2" pref="173" max="-2" attributes="0"/> + </Group> + </Group> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace min="-2" pref="4" max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel1" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledStatus" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="panelPowerCtr" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel5" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledMounted" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="jLabel10" min="-2" max="-2" attributes="0"/> + <Component id="panelCurrentRb" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="ch.psi.pshell.swing.Led" name="ledStatus"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel1"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Status:"/> + </Properties> + </Component> + <Container class="javax.swing.JPanel" name="panelPowerCtr"> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="150" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="23" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + </Layout> + </Container> + <Component class="javax.swing.JLabel" name="jLabel5"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Mounted:"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledMounted"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JLabel" name="jLabel10"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Current:"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.DeviceValuePanel" name="panelCurrentRb"> + <Properties> + <Property name="deviceName" type="java.lang.String" value="smc_current_rb"/> + </Properties> + </Component> + </SubComponents> + </Container> + <Component class="ch.psi.pshell.swing.DeviceStatePanel" name="panelState"> + </Component> + <Component class="ch.psi.pshell.swing.ProcessVariablePanel" name="panelCurrent"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Current"/> + </Border> + </Property> + <Property name="deviceName" type="java.lang.String" value="smc_current"/> + </Properties> + </Component> + <Container class="javax.swing.JPanel" name="jPanel2"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Supress"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Component id="jLabel2" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="ledSupressed" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + <Component id="buttonSupressOn" linkSize="1" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="buttonSupressOff" linkSize="1" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="1" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="2" attributes="0"> + <Component id="jLabel2" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="ledSupressed" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="buttonSupressOn" alignment="2" min="-2" max="-2" attributes="0"/> + <Component id="buttonSupressOff" alignment="2" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JLabel" name="jLabel2"> + <Properties> + <Property name="horizontalAlignment" type="int" value="11"/> + <Property name="text" type="java.lang.String" value="Supressed:"/> + </Properties> + </Component> + <Component class="ch.psi.pshell.swing.Led" name="ledSupressed"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="SansSerif" size="18" style="0"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JButton" name="buttonSupressOn"> + <Properties> + <Property name="text" type="java.lang.String" value="On"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonSupressOnActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="buttonSupressOff"> + <Properties> + <Property name="text" type="java.lang.String" value="Off"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonSupressOffActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + <Container class="javax.swing.JPanel" name="jPanel3"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Configuration"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="32767" attributes="0"/> + <Component id="buttonConfiguration" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="1" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Component id="buttonConfiguration" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JButton" name="buttonConfiguration"> + <Properties> + <Property name="text" type="java.lang.String" value="Configuration"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonConfigurationActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + </SubComponents> +</Form> diff --git a/plugins/SmartMagnetPanel.java b/plugins/SmartMagnetPanel.java new file mode 100644 index 0000000..589fb82 --- /dev/null +++ b/plugins/SmartMagnetPanel.java @@ -0,0 +1,320 @@ + +import ch.psi.pshell.device.Device; +import ch.psi.pshell.ui.App; +import ch.psi.pshell.swing.DevicePanel; +import ch.psi.pshell.core.Context; +import ch.psi.utils.State; +import java.awt.Color; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.script.ScriptException; + +/** + * + */ +public class SmartMagnetPanel extends DevicePanel { + + /** + * Creates new form RobotPanel + */ + public SmartMagnetPanel() { + initComponents(); + } + + @Override + public void setDevice(Device device) { + super.setDevice(device); + if (device == null) { + + } + panelState.setDevice(device); + this.startTimer(1000, 100); + } + + @Override + protected void onDeviceStateChanged(State state, State former) { + switch(state){ + case Paused: + ledSupressed.setColor(Color.ORANGE); + ledStatus.setColor(Color.GREEN); + ledMounted.setColor(Color.RED); + break; + case Ready: + ledSupressed.setColor(Color.BLACK); + ledStatus.setColor(Color.GREEN); + ledMounted.setColor(Color.GREEN); + break; + case Busy: + ledSupressed.setColor(Color.BLACK); + ledStatus.setColor(Color.GREEN); + ledMounted.setColor(Color.ORANGE); + break; + case Fault: + ledSupressed.setColor(Color.RED); + ledStatus.setColor(Color.RED); + ledMounted.setColor(Color.RED); + break; + default: + ledSupressed.setColor(Color.BLACK); + ledStatus.setColor(Color.BLACK); + ledMounted.setColor(Color.BLACK); + break; + } + buttonSupressOn.setEnabled((state==State.Ready) || (state == State.Busy)); + buttonSupressOff.setEnabled(state == State.Paused); + } + + @Override + protected void onDeviceCacheChanged(Object value, Object former, long timestamp, boolean valueChange) { + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + ledStatus = new ch.psi.pshell.swing.Led(); + jLabel1 = new javax.swing.JLabel(); + panelPowerCtr = new javax.swing.JPanel(); + jLabel5 = new javax.swing.JLabel(); + ledMounted = new ch.psi.pshell.swing.Led(); + jLabel10 = new javax.swing.JLabel(); + panelCurrentRb = new ch.psi.pshell.swing.DeviceValuePanel(); + panelState = new ch.psi.pshell.swing.DeviceStatePanel(); + panelCurrent = new ch.psi.pshell.swing.ProcessVariablePanel(); + jPanel2 = new javax.swing.JPanel(); + jLabel2 = new javax.swing.JLabel(); + ledSupressed = new ch.psi.pshell.swing.Led(); + buttonSupressOn = new javax.swing.JButton(); + buttonSupressOff = new javax.swing.JButton(); + jPanel3 = new javax.swing.JPanel(); + buttonConfiguration = new javax.swing.JButton(); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Status")); + + ledStatus.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel1.setText("Status:"); + + javax.swing.GroupLayout panelPowerCtrLayout = new javax.swing.GroupLayout(panelPowerCtr); + panelPowerCtr.setLayout(panelPowerCtrLayout); + panelPowerCtrLayout.setHorizontalGroup( + panelPowerCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 150, Short.MAX_VALUE) + ); + panelPowerCtrLayout.setVerticalGroup( + panelPowerCtrLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 23, Short.MAX_VALUE) + ); + + jLabel5.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel5.setText("Mounted:"); + + ledMounted.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + jLabel10.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel10.setText("Current:"); + + panelCurrentRb.setDeviceName("smc_current_rb"); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledStatus, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(panelPowerCtr, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jLabel5, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledMounted, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jLabel10, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(panelCurrentRb, javax.swing.GroupLayout.PREFERRED_SIZE, 173, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel1) + .addComponent(ledStatus, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(panelPowerCtr, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel5) + .addComponent(ledMounted, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel10) + .addComponent(panelCurrentRb, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + panelCurrent.setBorder(javax.swing.BorderFactory.createTitledBorder("Current")); + panelCurrent.setDeviceName("smc_current"); + + jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("Supress")); + + jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel2.setText("Supressed:"); + + ledSupressed.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N + + buttonSupressOn.setText("On"); + buttonSupressOn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonSupressOnActionPerformed(evt); + } + }); + + buttonSupressOff.setText("Off"); + buttonSupressOff.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonSupressOffActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ledSupressed, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonSupressOn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(buttonSupressOff) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + jPanel2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonSupressOff, buttonSupressOn}); + + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(jLabel2) + .addComponent(ledSupressed, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonSupressOn) + .addComponent(buttonSupressOff)) + .addContainerGap()) + ); + + jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Configuration")); + + buttonConfiguration.setText("Configuration"); + buttonConfiguration.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonConfigurationActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonConfiguration) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonConfiguration) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelState, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelCurrent, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(panelCurrent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(panelState, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// </editor-fold>//GEN-END:initComponents + + private void buttonSupressOnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonSupressOnActionPerformed + try { + Context.getInstance().evalLineBackground("smart_magnet.set_supress(True)"); + } catch (Exception ex) { + Logger.getLogger(SmartMagnetPanel.class.getName()).log(Level.SEVERE, null, ex); + } + }//GEN-LAST:event_buttonSupressOnActionPerformed + + private void buttonSupressOffActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonSupressOffActionPerformed + try { + Context.getInstance().evalLineBackground("smart_magnet.set_supress(False)"); + } catch (Exception ex) { + Logger.getLogger(SmartMagnetPanel.class.getName()).log(Level.SEVERE, null, ex); + } + }//GEN-LAST:event_buttonSupressOffActionPerformed + + private void buttonConfigurationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonConfigurationActionPerformed + try { + this.showConfigEditor(true, false); + } catch (Exception ex) { + Logger.getLogger(SmartMagnetPanel.class.getName()).log(Level.SEVERE, null, ex); + } + }//GEN-LAST:event_buttonConfigurationActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonConfiguration; + private javax.swing.JButton buttonSupressOff; + private javax.swing.JButton buttonSupressOn; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel10; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel5; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private ch.psi.pshell.swing.Led ledMounted; + private ch.psi.pshell.swing.Led ledStatus; + private ch.psi.pshell.swing.Led ledSupressed; + private ch.psi.pshell.swing.ProcessVariablePanel panelCurrent; + private ch.psi.pshell.swing.DeviceValuePanel panelCurrentRb; + private javax.swing.JPanel panelPowerCtr; + private ch.psi.pshell.swing.DeviceStatePanel panelState; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/TestZMQ.java b/plugins/TestZMQ.java new file mode 100644 index 0000000..cec2c40 --- /dev/null +++ b/plugins/TestZMQ.java @@ -0,0 +1,26 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +/** + * + * @author gac-S_Changer + */ +public class TestZMQ { + public static void main(String[] args) { + String server = "raspberrypi:5556"; + org.zeromq.ZMQ.Context context = org.zeromq.ZMQ.context(1); + org.zeromq.ZMQ.Socket subscriber = context.socket(org.zeromq.ZMQ.SUB); + subscriber.connect("tcp://" + server); + subscriber.subscribe("Status".getBytes()); + while (!Thread.currentThread().isInterrupted()) { + String type = subscriber.recvStr(); + String contents = subscriber.recvStr(); + System.out.println(type + " : " + contents); + } + subscriber.close(); + context.term(); + } +} diff --git a/plugins/WagoPanel.form b/plugins/WagoPanel.form new file mode 100644 index 0000000..9864ffa --- /dev/null +++ b/plugins/WagoPanel.form @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + </AuxValues> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="deviceStatePanel1" max="32767" attributes="0"/> + <Component id="panelSafety" max="32767" attributes="0"/> + <Component id="panelRobot1" max="32767" attributes="0"/> + <Component id="panelDewar" alignment="0" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <Component id="panelSafety" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="panelDewar" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="panelRobot1" min="-2" max="-2" attributes="0"/> + <EmptySpace min="0" pref="0" max="-2" attributes="0"/> + <Component id="deviceStatePanel1" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="ch.psi.pshell.swing.DeviceStatePanel" name="deviceStatePanel1"> + <Properties> + <Property name="deviceName" type="java.lang.String" value="wago"/> + </Properties> + </Component> + <Container class="javax.swing.JPanel" name="panelSafety"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Safety"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="32767" attributes="0"/> + <Component id="buttonReleasePsys" linkSize="2" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="buttonReleaseLocal" linkSize="2" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="buttonReleaseLocal" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="buttonReleasePsys" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JButton" name="buttonReleasePsys"> + <Properties> + <Property name="text" type="java.lang.String" value="Release PSYS"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonReleasePsysActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="buttonReleaseLocal"> + <Properties> + <Property name="text" type="java.lang.String" value="Release Local"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonReleaseLocalActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + <Container class="javax.swing.JPanel" name="panelRobot1"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Dryer"/> + </Border> + </Property> + </Properties> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="32767" attributes="0"/> + <Component id="buttonHeater" linkSize="3" min="-2" max="-2" attributes="0"/> + <EmptySpace type="unrelated" max="-2" attributes="0"/> + <Component id="buttonStream" linkSize="3" min="-2" max="-2" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="buttonHeater" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="buttonStream" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JToggleButton" name="buttonHeater"> + <Properties> + <Property name="text" type="java.lang.String" value="Heater"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonHeaterActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JToggleButton" name="buttonStream"> + <Properties> + <Property name="text" type="java.lang.String" value="Air Stream"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="buttonStreamActionPerformed"/> + </Events> + </Component> + </SubComponents> + </Container> + <Component class="ch.psi.pshell.swing.ProcessVariablePanel" name="panelDewar"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder justification="1" title="Dewar Light"/> + </Border> + </Property> + <Property name="deviceName" type="java.lang.String" value="led_level"/> + <Property name="showAdvanced" type="boolean" value="false"/> + <Property name="showButtons" type="boolean" value="false"/> + <Property name="showLimitButtons" type="boolean" value="false"/> + <Property name="showSlider" type="boolean" value="true"/> + <Property name="showStop" type="boolean" value="false"/> + </Properties> + </Component> + </SubComponents> +</Form> diff --git a/plugins/WagoPanel.java b/plugins/WagoPanel.java new file mode 100644 index 0000000..cacadcd --- /dev/null +++ b/plugins/WagoPanel.java @@ -0,0 +1,207 @@ +import ch.psi.mxsc.Controller; +import ch.psi.pshell.core.Context; +import ch.psi.pshell.swing.DevicePanel; +import java.util.concurrent.CompletableFuture; +import javax.swing.border.TitledBorder; + +/** + * + */ +public class WagoPanel extends DevicePanel { + + public WagoPanel() { + initComponents(); + this.startTimer(10000); + } + + + CompletableFuture future; + + @Override + public void onTimer(){ + if ((getDevice()!=null)){ + updateTitle(); + } + } + + void updateTitle(){ + Boolean roomTemp = (Controller.getInstance().isLedRoomTemp()); + if (roomTemp==null){ + ((TitledBorder)panelDewar.getBorder()).setTitle("Dewar Light"); + } else if (roomTemp==true){ + ((TitledBorder)panelDewar.getBorder()).setTitle("Dewar Light (room temperature)"); + } else { + ((TitledBorder)panelDewar.getBorder()).setTitle("Dewar Light (LN2)"); + } + } + + + void execute(String statement){ + try { + Context.getInstance().evalLineBackgroundAsync(statement).handle((ret, ex) -> { + if (WagoPanel.this.isShowing()){ + if (ex != null){ + showException((Exception)ex); + } + } + return ret; + }); + } catch (Exception ex) { + showException(ex); + } + } + + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + deviceStatePanel1 = new ch.psi.pshell.swing.DeviceStatePanel(); + panelSafety = new javax.swing.JPanel(); + buttonReleasePsys = new javax.swing.JButton(); + buttonReleaseLocal = new javax.swing.JButton(); + panelRobot1 = new javax.swing.JPanel(); + buttonHeater = new javax.swing.JToggleButton(); + buttonStream = new javax.swing.JToggleButton(); + panelDewar = new ch.psi.pshell.swing.ProcessVariablePanel(); + + deviceStatePanel1.setDeviceName("wago"); + + panelSafety.setBorder(javax.swing.BorderFactory.createTitledBorder("Safety")); + + buttonReleasePsys.setText("Release PSYS"); + buttonReleasePsys.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonReleasePsysActionPerformed(evt); + } + }); + + buttonReleaseLocal.setText("Release Local"); + buttonReleaseLocal.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonReleaseLocalActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelSafetyLayout = new javax.swing.GroupLayout(panelSafety); + panelSafety.setLayout(panelSafetyLayout); + panelSafetyLayout.setHorizontalGroup( + panelSafetyLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelSafetyLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonReleasePsys) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(buttonReleaseLocal) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + panelSafetyLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonReleaseLocal, buttonReleasePsys}); + + panelSafetyLayout.setVerticalGroup( + panelSafetyLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelSafetyLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelSafetyLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonReleaseLocal) + .addComponent(buttonReleasePsys)) + .addContainerGap()) + ); + + panelRobot1.setBorder(javax.swing.BorderFactory.createTitledBorder("Dryer")); + + buttonHeater.setText("Heater"); + buttonHeater.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonHeaterActionPerformed(evt); + } + }); + + buttonStream.setText("Air Stream"); + buttonStream.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonStreamActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelRobot1Layout = new javax.swing.GroupLayout(panelRobot1); + panelRobot1.setLayout(panelRobot1Layout); + panelRobot1Layout.setHorizontalGroup( + panelRobot1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelRobot1Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonHeater) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(buttonStream) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + panelRobot1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonHeater, buttonStream}); + + panelRobot1Layout.setVerticalGroup( + panelRobot1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelRobot1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(panelRobot1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonHeater) + .addComponent(buttonStream)) + .addContainerGap()) + ); + + panelDewar.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Dewar Light", javax.swing.border.TitledBorder.LEFT, javax.swing.border.TitledBorder.DEFAULT_POSITION)); + panelDewar.setDeviceName("led_level"); + panelDewar.setShowAdvanced(false); + panelDewar.setShowButtons(false); + panelDewar.setShowLimitButtons(false); + panelDewar.setShowSlider(true); + panelDewar.setShowStop(false); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(deviceStatePanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelSafety, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelRobot1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelDewar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(panelSafety, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(panelDewar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(panelRobot1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(deviceStatePanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// </editor-fold>//GEN-END:initComponents + + private void buttonReleasePsysActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonReleasePsysActionPerformed + execute("release_psys()"); + }//GEN-LAST:event_buttonReleasePsysActionPerformed + + private void buttonReleaseLocalActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonReleaseLocalActionPerformed + execute("release_local()"); + }//GEN-LAST:event_buttonReleaseLocalActionPerformed + + private void buttonHeaterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonHeaterActionPerformed + execute("set_heater(" + (buttonHeater.isSelected() ? "True": "False") + ")"); + }//GEN-LAST:event_buttonHeaterActionPerformed + + private void buttonStreamActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStreamActionPerformed + execute("set_air_stream(" + (buttonStream.isSelected()? "True": "False")+ ")"); + }//GEN-LAST:event_buttonStreamActionPerformed + + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JToggleButton buttonHeater; + private javax.swing.JButton buttonReleaseLocal; + private javax.swing.JButton buttonReleasePsys; + private javax.swing.JToggleButton buttonStream; + private ch.psi.pshell.swing.DeviceStatePanel deviceStatePanel1; + private ch.psi.pshell.swing.ProcessVariablePanel panelDewar; + private javax.swing.JPanel panelRobot1; + private javax.swing.JPanel panelSafety; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/gui.form b/plugins/gui.form new file mode 100644 index 0000000..5775937 --- /dev/null +++ b/plugins/gui.form @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + </AuxValues> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace max="32767" attributes="0"/> + <Component id="renderer1" min="-2" pref="154" max="-2" attributes="0"/> + <EmptySpace min="-2" pref="82" max="-2" attributes="0"/> + </Group> + <Group type="102" alignment="0" attributes="0"> + <EmptySpace min="-2" pref="51" max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="jButton1" min="-2" max="-2" attributes="0"/> + <Component id="linePlotJFree1" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace pref="250" max="32767" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <EmptySpace min="-2" pref="24" max="-2" attributes="0"/> + <Component id="jButton1" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="renderer1" min="-2" pref="102" max="-2" attributes="0"/> + <EmptySpace pref="20" max="32767" attributes="0"/> + <Component id="linePlotJFree1" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + </Group> + </Group> + </DimensionLayout> + </Layout> + <SubComponents> + <Component class="javax.swing.JButton" name="jButton1"> + <Properties> + <Property name="text" type="java.lang.String" value="Abort"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton1ActionPerformed"/> + </Events> + </Component> + <Component class="ch.psi.pshell.imaging.Renderer" name="renderer1"> + </Component> + <Component class="ch.psi.pshell.plot.LinePlotJFree" name="linePlotJFree1"> + </Component> + </SubComponents> +</Form> diff --git a/plugins/gui.java b/plugins/gui.java new file mode 100644 index 0000000..abeb59b --- /dev/null +++ b/plugins/gui.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.ui.Panel; +import ch.psi.utils.State; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + */ +public class gui extends Panel { + + public gui() { + initComponents(); + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + + } + + @Override + public void onStateChange(State state, State former) { + + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + + //Callback to perform update - in event thread + @Override + protected void doUpdate() { + } + + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + + jButton1 = new javax.swing.JButton(); + renderer1 = new ch.psi.pshell.imaging.Renderer(); + linePlotJFree1 = new ch.psi.pshell.plot.LinePlotJFree(); + + jButton1.setText("Abort"); + jButton1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1ActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(renderer1, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(82, 82, 82)) + .addGroup(layout.createSequentialGroup() + .addGap(51, 51, 51) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jButton1) + .addComponent(linePlotJFree1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(250, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(24, 24, 24) + .addComponent(jButton1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(renderer1, javax.swing.GroupLayout.PREFERRED_SIZE, 102, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 20, Short.MAX_VALUE) + .addComponent(linePlotJFree1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + }// </editor-fold>//GEN-END:initComponents + + private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + try { + abort(); + } catch (InterruptedException ex) { + Logger.getLogger(gui.class.getName()).log(Level.SEVERE, null, ex); + } + }//GEN-LAST:event_jButton1ActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private ch.psi.pshell.plot.LinePlotJFree linePlotJFree1; + private ch.psi.pshell.imaging.Renderer renderer1; + // End of variables declaration//GEN-END:variables +} diff --git a/script/LN2_Monitoring.scd b/script/LN2_Monitoring.scd new file mode 100644 index 0000000..d2ab014 --- /dev/null +++ b/script/LN2_Monitoring.scd @@ -0,0 +1,15 @@ +[ + [ [ true, "phase_separator_level", "Device", 1, 1, "102,204,255" ], + [ true, "filling_phase_separator", "Device", 1, 1, "51,255,255" ], + [ true, "filling_dewar", "Device", 1, 1, "0,0,102" ], + [ true, "dewar_level", "Device", 1, 1, "0,51,153" ], + [ true, "rim_heater_temp", "Device", 1, 1, "255,0,51" ] ], + [ [ "1", 0.0, 100.0, null, null, 1000000.0, false, null ], + [ "2", null, null, null, null, null, null, null ], + [ "3", null, null, null, null, null, null, null ], + [ "4", null, null, null, null, null, null, null ], + [ "5", null, null, null, null, null, null, null ] ], + [ [ ] ], + [ [ "", 20000, 100 ], + [ "", "" ] ] +] \ No newline at end of file diff --git a/script/LevelMonitoring.scd b/script/LevelMonitoring.scd new file mode 100644 index 0000000..ae328cc --- /dev/null +++ b/script/LevelMonitoring.scd @@ -0,0 +1,12 @@ +[ + [ [ true, "phase_separator_level", "Device", 1, 1, "255,0,0" ], + [ true, "dewar_level", "Device", 1, 1, "0,0,255" ] ], + [ [ "1", 0.0, 100.0, null, null, 100000.0, false, null ], + [ "2", null, null, null, null, null, null, null ], + [ "3", null, null, null, null, null, null, null ], + [ "4", null, null, null, null, null, null, null ], + [ "5", null, null, null, null, null, null, null ] ], + [ [ ] ], + [ [ "", 20000, 100 ], + [ "", "" ] ] +] \ No newline at end of file diff --git a/script/calibration/BinarySearchYZ.py b/script/calibration/BinarySearchYZ.py new file mode 100644 index 0000000..42db379 --- /dev/null +++ b/script/calibration/BinarySearchYZ.py @@ -0,0 +1,29 @@ +import plotutils +import math + + +#STRATEGY = "Normal" +#STRATEGY = "Boundary" +#STRATEGY = "FullNeighborhood" +RANGE = [-5.0, 5.0] +STEP_SIZE = 0.1 +LATENCY = 0.05 + + +robot.enable() +move_to_laser() + + +robot.set_motors_enabled(True) +current_y = robot_y.getPosition() +current_z = robot_z.getPosition() + + +r = bsearch([robot_y, robot_z], laser_distance,[RANGE[0], RANGE[0]], [RANGE[1], RANGE[1]], [STEP_SIZE,STEP_SIZE], relative = True, maximum=True, strategy = STRATEGY, latency = LATENCY, title = "Binary Search YZ") + + +print r.print() +opt_y, opt_z= r.getOptimalPosition() +offset_y, offset_z = opt_y - current_y, opt_z - current_z + +print "offset_y: ", offset_y, " offset_z: ", offset_z \ No newline at end of file diff --git a/script/calibration/HillClimbingXZ.py b/script/calibration/HillClimbingXZ.py new file mode 100644 index 0000000..2fcab26 --- /dev/null +++ b/script/calibration/HillClimbingXZ.py @@ -0,0 +1,45 @@ +import plotutils +import math + + +#STRATEGY = "Normal" +STRATEGY = "Boundary" +#STRATEGY = "FullNeighborhood" +RANGE = [-5.0, 5.0] +INITIAL_STEP = 1.0 +STEP_SIZE = 0.05 +LATENCY = 0.05 +NOISE_FILTER = 1 + + +robot.enable() +move_to_laser() + + +robot.set_motors_enabled(True) + +current_x = robot_x.getPosition() +current_z = robot_z.getPosition() + + + + +class Distance(Readable): + def read(self): + ret = ue.readable.read() + ret = 0.0 if math.isnan(ret) else ret + return ret + +laser_distance=Distance() +start = time.time() +r = hsearch([robot_x, robot_z],laser_distance, [RANGE[0], RANGE[0]], [RANGE[1], RANGE[1]], [INITIAL_STEP,INITIAL_STEP], [STEP_SIZE,STEP_SIZE], NOISE_FILTER, relative = True, maximum=True, latency = LATENCY, title = "Hill Climbing XZ") + + + +print r.print() +opt_x, opt_z= r.getOptimalPosition() +offset_x, offset_z = opt_x - current_x, opt_z - current_z + +print "offset_x: ", offset_x, " offset_z: ", offset_z + + diff --git a/script/calibration/ScanRZ.py b/script/calibration/ScanRZ.py new file mode 100644 index 0000000..55d3bba --- /dev/null +++ b/script/calibration/ScanRZ.py @@ -0,0 +1,31 @@ +#Imports +import plotutils +from mathutils import fit_gaussian, Gaussian + +#Parameters +RANGE = [-120.0,120.0] +STEP = 5.0 +LATENCY = 0.005 +RELATIVE = False + +#Enabling and checking +#enable_motion() +#system_check() +robot.enable() + + +#Body +robot.set_tool(TOOL_DEFAULT) +move_to_laser() +#robot.set_joint_motors_enabled(True) +robot.set_motors_enabled(True) +robot_rz.move(0.0) +robot.set_motors_enabled(True) +ret = lscan(robot_rz, ue.readable, RANGE[0], RANGE[1], STEP, latency = LATENCY, relative = RELATIVE, range = "auto", title = "Scan2") + + +#Cleanup + + + + diff --git a/script/calibration/ScanX.py b/script/calibration/ScanX.py new file mode 100644 index 0000000..d10c505 --- /dev/null +++ b/script/calibration/ScanX.py @@ -0,0 +1,101 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +FIT = True + +d = robot.get_distance_to_pnt("pLaser") +if d<0: + raise Exception ("Error calculating distance to laser: " + str(d)) + +if d>20: + raise Exception ("Should be near the laser position to perform the scan") + +RANGE = [-5.0, 4.0] #[-1.5, 1.5] +STEP = 0.02 +Z_OFFSET = 0 #-1.0 +LATENCY = 0.025 +BORDER_SIZE = 0.15 + +robot.enable() +robot.set_motors_enabled(True) +current_positon = robot_x.getPosition() +robot_z.moveRel(Z_OFFSET) + +print "Moving to scan start position: " , RANGE[0] +robot.set_motors_enabled(True) +robot_x.moveRel( RANGE[0]) +robot.set_motors_enabled(True) +print "Starting scan X" + +RANGE = [0, (RANGE[1] - RANGE[0] )] + +robot.setPolling(25) + +try: + ret = lscan(robot_x, ue.readable, RANGE[0], RANGE[1], STEP, latency = LATENCY, relative = True) +finally: + robot.setPolling(DEFAULT_ROBOT_POLLING) + +d = ret.getReadable(0) + +first_index = -1 +last_index = -1 +for i in range(len(d)): + if not math.isnan(d[i]): + if first_index<0: + first_index = i + last_index = i + +if first_index == -1 or last_index < first_index: + raise Exception("Invalid range") + + +remove = int(max(BORDER_SIZE, STEP) / STEP) + +_range = [first_index+remove, last_index-remove] +if _range[1] <= _range[0]: + raise Exception("Invalid range: " + str(_range)) + + +center_index = int((_range[0] + _range[1])/2) +center_positon = ret.getPositions(0)[center_index] + + + + +y = ret.getReadable(0)[_range[0] : _range[1]] +x = ret.getPositions(0)[_range[0]: _range[1]] + +#Clear NaNs +first_value=ret.getReadable(0)[first_index] +y = [(first_value if math.isnan(v) else v) for v in y] + + + +if FIT: + x = enforce_monotonic(x) + offset=100 + (normalization, mean_val, sigma) = fit_gaussian([offset-v for v in y], x) + closest_x = mean_val + closest_y = 100 -normalization +else: + closest_x = x[y.index(min(y))] + closest_y = y[y.index(min(y))] + + + +if closest_x is None or closest_x <= ret.getPositions(0)[first_index] or closest_x >= ret.getPositions(0)[last_index]: + raise Exception("Invalid Fit") + + + +center_offset = center_positon-closest_y +#center_offset = current_positon-closest_y + + + +p=get_plots()[0] +p.addMarker(closest_x, p.AxisId.X, str(closest_x), Color.GREEN) + +robot.set_motors_enabled(True) +robot_x.move(closest_x) diff --git a/script/calibration/ScanY.py b/script/calibration/ScanY.py new file mode 100644 index 0000000..cf93b9a --- /dev/null +++ b/script/calibration/ScanY.py @@ -0,0 +1,99 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +FIT = True + +d = robot.get_distance_to_pnt("pLaser") +if d<0: + raise Exception ("Error calculating distance to laser: " + str(d)) + +if d>20: + raise Exception ("Should be near the laser position to perform the scan") + +RANGE = [-7.0, 4.0] #[-1.5, 1.5] +STEP = 0.02 +Z_OFFSET = 0 #-1.0 +LATENCY = 0.025 +BORDER_SIZE = 0.15 + +robot.enable() +robot.set_motors_enabled(True) +current_positon = robot_y.getPosition() +robot_z.moveRel(Z_OFFSET) + + +print "Moving to scan start position: " , RANGE[0] +robot.set_motors_enabled(True) +robot_y.moveRel( RANGE[0]) +robot.set_motors_enabled(True) +print "Starting scan Y" + +RANGE = [0, (RANGE[1] - RANGE[0] )] + +robot.setPolling(25) +try: + ret = lscan(robot_y, ue.readable, RANGE[0], RANGE[1], STEP, latency = LATENCY, relative = True) +finally: + robot.setPolling(DEFAULT_ROBOT_POLLING) + +d = ret.getReadable(0) + +first_index = -1 +last_index = -1 +for i in range(len(d)): + if not math.isnan(d[i]): + if first_index<0: + first_index = i + last_index = i + +if first_index == -1 or last_index < first_index: + raise Exception("Invalid range") + + +remove = int(max(BORDER_SIZE, STEP) / STEP) + +_range = [first_index+remove, last_index-remove] +if _range[1] <= _range[0]: + raise Exception("Invalid range: " + str(_range)) + + +center_index = int((_range[0] + _range[1])/2) +center_positon = ret.getPositions(0)[center_index] + + + + +y = ret.getReadable(0)[_range[0] : _range[1]] +x = ret.getPositions(0)[_range[0]: _range[1]] + +#Clear NaNs +first_value=ret.getReadable(0)[first_index] +y = [(first_value if math.isnan(v) else v) for v in y] + + +if FIT: + x = enforce_monotonic(x) + offset=100 + (normalization, mean_val, sigma) = fit_gaussian([offset-v for v in y], x) + closest_y = mean_val + closest_x = 100 -normalization +else: + closest_y = x[y.index(min(y))] + closest_x = y[y.index(min(y))] + + +if closest_y is None or closest_y <= ret.getPositions(0)[first_index] or closest_y >= ret.getPositions(0)[last_index]: + raise Exception("Invalid Fit") + + + +center_offset = center_positon-closest_y +#center_offset = current_positon-closest_y + + + +p=get_plots()[0] +p.addMarker(closest_y, p.AxisId.X, str(closest_y), Color.GREEN) + +robot.set_motors_enabled(True) +robot_y.move(closest_y) diff --git a/script/calibration/ScanYZ.py b/script/calibration/ScanYZ.py new file mode 100644 index 0000000..bee63e6 --- /dev/null +++ b/script/calibration/ScanYZ.py @@ -0,0 +1,93 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +SINGLE_PASS = False +if SINGLE_PASS: + STEP_SIZE = 0.2 +else: + STEP_SIZE = 1.0 +STEP_Z_FINAL = 0.1 + +RANGE = [-5.0, 5.0] +LATENCY = 0.05 + +Z_FINAL_OFFSET = 0.0 + + +SINGLE_PASS = True +STEP_SIZE = 0.1 + + +robot.enable() +move_to_laser() + +step_y = STEP_SIZE +step_z = STEP_SIZE +range_y = [RANGE[0], RANGE[1]] +range_z = [RANGE[0], RANGE[1]] + +robot.set_motors_enabled(True) +current_y = robot_y.getPosition() +current_z = robot_z.getPosition() + +print "Current pos y,z" , current_y, ",", current_z +ret = ascan([robot_y, robot_z], ue.readable, [range_y[0], range_z[0]], [range_y[1], range_z[1]], [step_y,step_z], latency = LATENCY, relative = True , zigzag=False, title = "Scan XY") +data = ret.getData(0)[0] +#plot(Convert.transpose(data), title="Data") + +integ = [] +for x in data: integ.append(sum( [ (0.0 if (math.isnan(y)) else y) for y in x])) + +xdata= frange(range_y[0], range_y[1], step_y , False, True) +p = plot(integ, title = "Fit", xdata=xdata)[0] + + +max_x_index = integ.index(max(integ)) +max_x = xdata[max_x_index] +try: + (normalization, mean_val, sigma) = fit_gaussian(integ, xdata) +except: + raise Exception("Invalid Fit") +gaussian = Gaussian(normalization, mean_val, sigma) +xdata= frange(range_y[0], range_y[1], step_y/100.0 , False, True) +plot_function(p, gaussian, "Fit", xdata, show_points = False, show_lines = True, color = Color.BLUE) + +#So +if abs(mean_val - max_x) > 1.0: + raise Exception("Invalid Y detection") +y_offset = mean_val +center_y = current_y + y_offset + +print "Y offset = ", y_offset + +robot_y.move(center_y) +if SINGLE_PASS: + z_scan_data = data[max_x_index] +else: + step_z = STEP_Z_FINAL + ret2 = lscan(robot_z, ue.readable, range_z[0], range_z[1], step_z, latency = LATENCY, relative = True , zigzag=False) + z_scan_data = ret2.getData(0)[0] + +max_z_index= z_scan_data.index(max(z_scan_data)) +last_z_index = 0 +for i in range(len(z_scan_data)): + if not math.isnan(z_scan_data[i]): + last_z_index = i +#Shape is cone: z is inceraseing. For proper detection last Z must be furthest +if abs(max_z_index - last_z_index) * step_z > 1.0: + raise Exception("Invalid Z detection") + +if SINGLE_PASS: + max_z = ret.getPositions(1)[len(data[0]) * max_x_index + last_z_index] +else: + max_z = ret2.getPositions(0)[last_z_index] + +z_offset = max_z - current_z + Z_FINAL_OFFSET + +robot_z.move(max_z + Z_FINAL_OFFSET) + +print "Z offset = ", z_offset + + +#Updating tool: +#update_tool(None, x_offset=x_offset, z_offset=z_offset) diff --git a/script/calibration/ToolCalibration.py b/script/calibration/ToolCalibration.py new file mode 100644 index 0000000..602403b --- /dev/null +++ b/script/calibration/ToolCalibration.py @@ -0,0 +1,60 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + + +robot.assert_tool(TOOL_CALIBRATION) +robot.set_motors_enabled(True) +robot.set_joint_motors_enabled(True) + + +initial_pos = robot.get_cartesian_pos() + +robot.enable() +move_to_laser() + +robot.align() + + +run("calibration/ScanYZ") + +robot.set_motors_enabled(True) + + +first_y = robot_y.take() +first_z = robot_z.take() +first_y = ue.take() +first_j6 = robot_j6.take() +if first_y is None: + raise Exception("Invalid YZ scan values in first scan") + + +robot.set_joint_motors_enabled(True) +if first_j6>0: + robot_j6.moveRel(-180.0, -1) +else: + robot_j6.moveRel(180.0, -1) + + +robot.set_motors_enabled(True) +run("calibration/ScanYZ") + +robot.set_motors_enabled(True) + +second_y = robot_y.take() +second_z = robot_z.take() +second_y = ue.take() +second_j6 = robot_j6.take() +if second_y is None: + raise Exception("Invalid XZ scan values in first scan") + + +#Updates the tool +xoff = (first_x - second_x)/2 +yoff = (first_y - second_y)/2 +t=robot.get_tool_trsf(TOOL_DEFAULT) +t[0]=xoff +t[1]=-yoff +robot.set_tool_trsf(t, TOOL_DEFAULT) + + + diff --git a/script/calibration/ToolCalibration2.py b/script/calibration/ToolCalibration2.py new file mode 100644 index 0000000..e9b71ae --- /dev/null +++ b/script/calibration/ToolCalibration2.py @@ -0,0 +1,86 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + + +#robot.assert_tool(TOOL_CALIBRATION) +#cal_tool = TOOL_DEFAULT +cal_tool = TOOL_CALIBRATION + +robot.set_tool(cal_tool) +robot.enable() +move_to_laser() + + +robot.set_motors_enabled(True) +robot.set_joint_motors_enabled(True) +initial_pos = robot.get_cartesian_pos() + +robot.enable() +move_to_laser() + +#robot.align() + + +run("calibration/ScanY") + +pos1 = robot.get_cartesian_pos() +x1, y1 = closest_x, closest_y + +print "Closest 1: ", [x1, y1] +print "Position 1: ", pos1 + + +pj6 = robot_j6.position +if pj6>0: + robot_j6.move(pj6 - 180.0) +else: + robot_j6.move(pj6 + 180.0) + + +run("calibration/ScanY") + + +x2, y2 = closest_x, closest_y + +print "Closest 2: ", [x2, y2] + + + +off_x = x1 - x2 + +#robot.set_motors_enabled(True) +#robot_x.moveRel(off_x, -1) + +#For composing cannot use tcp_p, need another auxiliary point. tcp_t is also destroyed. +robot.set_pnt(robot.get_cartesian_pos(), "pTemp") +robot.set_trsf([off_x, 0,0,0,0,0]) +c=robot.compose("pTemp", FRAME_TABLE, "tcp_t" ) +robot.set_pnt(c, "pTemp") +robot.movel("pTemp", cal_tool, DESC_SCAN, sync=True) + + +pos2 = robot.get_cartesian_pos() +print pos2 + +print "Position 2: ", pos2 + + + +#Updates the tool +xoff = (pos2[0]-pos1[0])/2 +yoff = (pos2[1]-pos1[1])/2 + +#print "Offset: ", [xoff, yoff] + +t=robot.get_tool_trsf(TOOL_DEFAULT) +t[0]=xoff +t[1]=-yoff +print "Offset: ", [t[0], t[1]] +robot.set_tool_trsf(t, TOOL_DEFAULT) + +robot.set_tool(TOOL_DEFAULT) +d = robot.get_distance_to_pnt("pLaser") +if d<POSITION_TOLERANCE: + print "Moving calibrated tool to laser" +else: + print "Cannot move calibrated tool to laser: toog big offset" diff --git a/script/calibration/ToolCalibration3.py b/script/calibration/ToolCalibration3.py new file mode 100644 index 0000000..969707d --- /dev/null +++ b/script/calibration/ToolCalibration3.py @@ -0,0 +1,83 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +cal_tool = TOOL_CALIBRATION + +robot.set_tool(cal_tool) + +robot.enable() +move_to_laser() + + +robot.set_motors_enabled(True) +robot.set_joint_motors_enabled(True) +initial_pos = robot.get_cartesian_pos() + +#robot.align() +try: + robot.set_frame(FRAME_TABLE) + run("calibration/ScanX") +finally: + robot.set_default_frame() + +pos1 = robot.get_cartesian_pos() +x1, l1 = closest_x, closest_y + +print "Scan 1 result: ", [x1, l1] +print "Position 1: ", pos1 + + +pj6 = robot_j6.position +if pj6>0: + robot_j6.move(pj6 - 180.0) +else: + robot_j6.move(pj6 + 180.0) + + +try: + robot.set_frame(FRAME_TABLE) + run("calibration/ScanX") +finally: + robot.set_default_frame() + +pos2 =robot.get_cartesian_pos() +x2, l2 = closest_x, closest_y + +print "Scan 2 result: ", [x2, l2] +print "Position 2: ", pos1 + +off_l = l2 - l1 +print "Offset l: ", off_l + +#For composing cannot use tcp_p, need another auxiliary point. tcp_t is also destroyed. +robot.set_pnt(robot.get_cartesian_pos(), "pTemp") +robot.set_trsf([0, -off_l, 0, 0, 0, 0]) +c=robot.compose("pTemp", FRAME_TABLE, "tcp_t" ) +robot.set_pnt(c, "pTemp") +robot.movel("pTemp", cal_tool, DESC_SCAN, sync=True) + + +pos3 = robot.get_cartesian_pos() +print "Position 3: ", pos3 + + +#Updates the tool +t=robot.get_tool_trsf(TOOL_DEFAULT) +print "Former tool: " + str(t) + +xoff = (pos3[0]-pos1[0])/2 +yoff = (pos3[1]-pos1[1])/2 +xrot = math.degrees(math.atan(yoff/t[2])) +yrot = math.degrees(math.atan(xoff/t[2])) +t[0]=xoff +t[1]=-yoff +print "Calibrated tool: " + str(t) +robot.set_tool_trsf(t, TOOL_DEFAULT) + +robot.set_tool(TOOL_DEFAULT) +d = robot.get_distance_to_pnt("pLaser") +if d<POSITION_TOLERANCE: + print "Moving calibrated tool to laser" + #move_to_laser() +else: + print "Cannot move calibrated tool to laser: too big offset" diff --git a/script/calibration/ToolCalibration6d.py b/script/calibration/ToolCalibration6d.py new file mode 100644 index 0000000..97bbc6f --- /dev/null +++ b/script/calibration/ToolCalibration6d.py @@ -0,0 +1,79 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +cal_tool = TOOL_CALIBRATION + +robot.set_tool(cal_tool) + +robot.enable() +move_to_laser("pPark") + + +robot.set_motors_enabled(True) +robot.set_joint_motors_enabled(True) +initial_pos = robot.get_cartesian_pos() + +#robot.align() +run("calibration/ScanX") + +pos1 = robot.get_cartesian_pos() +x1, l1 = closest_x, closest_y + +print "Scan 1 result: ", [x1, l1] +print "Position 1: ", pos1 + + +pj6 = robot_j6.position +if pj6>0: + robot_j6.move(pj6 - 180.0) +else: + robot_j6.move(pj6 + 180.0) + + +run("calibration/ScanX") + + +pos2 =robot.get_cartesian_pos() +x2, l2 = closest_x, closest_y + +print "Scan 2 result: ", [x2, l2] +print "Position 2: ", pos1 + +off_l = l2 - l1 +print "Offset l: ", off_l + + +#For composing cannot use tcp_p, need another auxiliary point. tcp_t is also destroyed. +robot.set_pnt(robot.get_cartesian_pos(), "pTemp") +robot.set_trsf([0, -off_l, 0, 0, 0, 0]) +c=robot.compose("pTemp", FRAME_DEFAULT, "tcp_t" ) +robot.set_pnt(c, "pTemp") +robot.movel("pTemp", cal_tool, DESC_SCAN, sync=True) + +pos3 = robot.get_cartesian_pos() +print "Position 3: ", pos3 + + +#Updates the tool +t=robot.get_tool_trsf(TOOL_DEFAULT) +print "Former tool: " + str(t) + +xoff = (pos3[0]-pos1[0])/2 +yoff = (pos3[1]-pos1[1])/2 +xrot = math.degrees(math.atan(yoff/t[2])) +yrot = math.degrees(math.atan(xoff/t[2])) +t[0]=xoff +t[1]=-yoff +print "Calibrated tool: " + str(t) +robot.set_tool_trsf(t, TOOL_DEFAULT) + +robot.set_tool(TOOL_DEFAULT) +d = robot.get_distance_to_pnt("pLaser") +if d<POSITION_TOLERANCE: + print "Moving calibrated tool to laser" + #move_to_laser() +else: + print "Cannot move calibrated tool to laser: too big offset" + + + diff --git a/script/calibration/ToolCalibration6s.py b/script/calibration/ToolCalibration6s.py new file mode 100644 index 0000000..7c213a5 --- /dev/null +++ b/script/calibration/ToolCalibration6s.py @@ -0,0 +1,79 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +cal_tool = TOOL_CALIBRATION + +robot.set_tool(cal_tool) + +robot.enable() +move_to_laser("pPark") + + +robot.set_motors_enabled(True) +robot.set_joint_motors_enabled(True) +initial_pos = robot.get_cartesian_pos() + +#robot.align() +run("calibration/ScanY") + +pos1 = robot.get_cartesian_pos() +l1, y1 = closest_x, closest_y + +print "Scan 1 result: ", [l1, y1] +print "Position 1: ", pos1 + + +pj6 = robot_j6.position +if pj6>0: + robot_j6.move(pj6 - 180.0) +else: + robot_j6.move(pj6 + 180.0) + +run("calibration/ScanY") + +pos2 =robot.get_cartesian_pos() +l2, y2 = closest_x, closest_y + +print "Scan 2 result: ", [l2, y2] +print "Position 2: ", pos1 + +off_l = l2 - l1 +print "Offset l: ", off_l + +#For composing cannot use tcp_p, need another auxiliary point. tcp_t is also destroyed. +robot.set_pnt(robot.get_cartesian_pos(), "pTemp") +robot.set_trsf([-off_l, 0, 0, 0, 0, 0]) +c=robot.compose("pTemp", FRAME_DEFAULT, "tcp_t" ) +robot.set_pnt(c, "pTemp") +robot.movel("pTemp", cal_tool, DESC_SCAN, sync=True) + + +pos3 = robot.get_cartesian_pos() +print "Position 3: ", pos3 + + +#Updates the tool +t=robot.get_tool_trsf(TOOL_DEFAULT) +print "Former tool: " + str(t) + +xoff = (pos3[0]-pos1[0])/2 +yoff = (pos3[1]-pos1[1])/2 +xrot = math.degrees(math.atan(yoff/t[2])) +yrot = math.degrees(math.atan(xoff/t[2])) +t[0]=xoff #X +t[1]=-yoff #Y +#t[3]= xropt #RX +#t[4]= yropt #RY + +print "Calibrated tool: " + str(t) + + +robot.set_tool_trsf(t, TOOL_DEFAULT) + +robot.set_tool(TOOL_DEFAULT) +d = robot.get_distance_to_pnt("pLaser") +if d<POSITION_TOLERANCE: + print "Moving calibrated tool to laser" + #move_to_laser() +else: + print "Cannot move calibrated tool to laser: too big offset" diff --git a/script/client/PShellClient.py b/script/client/PShellClient.py new file mode 100644 index 0000000..a40b1df --- /dev/null +++ b/script/client/PShellClient.py @@ -0,0 +1,369 @@ +import threading +import time +import sys +import requests +import json + +try: + from urllib import quote # Python 2 +except ImportError: + from urllib.parse import quote # Python 3 + +try: + from sseclient import SSEClient +except: + SSEClient = None + + +class PShellClient: + def __init__(self, url): + self.url = url + self.sse_event_loop_thread = None + self.subscribed_events = None + self.event_callback = None + + def _get_response(self, response, is_json=True): + if response.status_code != 200: + raise Exception(response.text) + return json.loads(response.text) if is_json else response.text + + def _get_binary_response(self, response): + if response.status_code != 200: + raise Exception(response.text) + return response.raw.read() + + def get_version(self): + """Return application version. + + Args: + + Returns: + String with application version. + + """ + return self._get_response(requests.get(url=self.url+"/version"), False) + + def get_config(self): + """Return application configuration. + + Args: + + Returns: + Dictionary. + """ + return self._get_response(requests.get(url=self.url+"/config")) + + def get_state(self): + """Return application state. + + Args: + + Returns: + String: Invalid, Initializing,Ready, Paused, Busy, Disabled, Closing, Fault, Offline + """ + return self._get_response(requests.get(url=self.url+"/state")) + + def get_logs(self): + """Return application logs. + + Args: + + Returns: + List of logs. + Format of each log: [date, time, origin, level, description] + + """ + return self._get_response(requests.get(url=self.url+"/logs")) + + def get_history(self, index): + """Access console command history. + + Args: + index(int): Index of history entry (0 is the most recent) + + Returns: + History entry + + """ + return self._get_response(requests.get(url=self.url+"/history/"+str(index)), False) + + def get_script(self, path): + """Return script. + + Args: + path(str): Script path (absolute or relative to script folder) + + Returns: + String with file contents. + + """ + return self._get_response(requests.get(url=self.url+"/script/"+str(path)), False) + + def get_devices(self): + """Return global devices. + + Args: + + Returns: + List of devices. + Format of each device record: [name, type, state, value, age] + + """ + return self._get_response(requests.get(url=self.url+"/devices")) + + def abort(self, command_id=None): + """Abort execution of command + + Args: + command_id(optional, int): id of the command to be aborted. + if None (default), aborts the foreground execution. + + Returns: + + """ + if command_id is None: + requests.get(url=self.url+"/abort") + else: + return requests.get(url=self.url+"/abort/"+str(command_id)) + + def reinit(self): + """Reinitialize the software. + + Args: + + Returns: + + """ + requests.get(url=self.url+"/reinit") + + def stop(self): + """Stop all devices implementing the 'Stoppable' interface. + + Args: + + Returns: + + """ + requests.get(url=self.url+"/stop") + + def update(self): + """Update all global devices. + + Args: + + Returns: + + """ + requests.get(url=self.url+"/update") + + def eval(self,statement): + """Evaluates a statement in the interpreter. + If the statement finishes by '&', it is executed in background. + Otherwise statement is executed in foreground (exclusive). + + Args: + statement(str): input statement + + Returns: + String containing the console return. + If an exception is produces in the interpretor, it is re-thrown here. + """ + statement = quote(statement) + return self._get_response(requests.get(url=self.url+"/eval/"+statement), False) + + def run(self,script, pars=None, background=False): + """Executes script in the interpreter. + + Args: + script(str): name of the script (absolute or relative to the script base folder). Extension may be omitted. + pars(optional, list or dict): if a list is given, it sets sys.argv for the script. + If a dict is given, it sets global variable for the script. + background(optional, bool): if True script is executed in background. + + Returns: + Return value of the script. + If an exception is produces in the interpretor, it is re-thrown here. + """ + return self._get_response(requests.put(url=self.url+"/run", json={"script":script, "pars":pars, "background":background, "async":False })) + + def start_eval(self,statement): + """Starts evaluation of a statement in the interpreter. + If the statement finishes by '&', it is executed in background. + Otherwise statement is executed in foreground (exclusive). + + Args: + statement(str): input statement + + Returns: + Command id (int), which is used to retrieve command execution status/result (get_result). + """ + statement = quote(statement) + return int(self._get_response(requests.get(url=self.url+"/evalAsync/"+statement), False)) + + def start_run(self,script, pars=None, background=False): + """Starts execution of a script in the interpreter. + + Args: + script(str): name of the script (absolute or relative to the script base folder). Extension may be omitted. + pars(optional, list or dict): if a list is given, it sets sys.argv for the script. + If a dict is given, it sets global variable for the script. + background(optional, bool): if True script is executed in background. + + Returns: + Command id (int), which is used to retrieve command execution status/result (get_result). + """ + return int(self._get_response(requests.put(url=self.url+"/run", json={"script":script, "pars":pars, "background":background, "async":True }))) + + def get_result(self, command_id=-1): + """Gets status/result of a command executed asynchronously (start_eval and start_run). + + Args: + command_id(optional, int): command id. If equals to -1 (default) return status/result of the foreground task. + + Returns: + Dictionary with the fields: 'id' (int): command id + 'status' (str): unlaunched, invalid, removed, running, aborted, failed or completed. + 'exception' (str): if status equals 'failed', holds exception string. + 'return' (obj): if status equals 'completed', holds return value of script (start_run) + or console return (start_eval) + """ + return self._get_response(requests.get(url=self.url+"/result/"+str(command_id))) + + def help(self, input = "<builtins>"): + """Returns help or auto-completion strings. + + Args: + input(optional, str): - ":" for control commands + - "<builtins>" for builtin functions + - "devices" for device names + - builtin function name for function help + - else contains entry for auto-completion + + Returns: + List + + """ + return self._get_response(requests.get(url=self.url+"/autocompletion/" + input)) + + def get_contents(self, path=None): + """Returns contents of data path. + + Args: + path(optional, str): Path to data relative to data home path. + - Folder + - File + - File (data root) | internal path + - internal path (on currently open data root) + + Returns: + List of contents + + """ + return self._get_response(requests.get(url=self.url+ "/contents" + ("" if path is None else ( "/"+path))), False) + + def get_data(self, path, type="txt"): + """Returns data on a given path. + + Args: + path(str): Path to data relative to data home path. + - File (data root) | internal path + - internal path (on currently open data root) + type(optional, str): txt, "json", "bin", "bs" + + Returns: + Data accordind to selected format/. + + """ + if type == "json": + return self._get_response(requests.get(url=self.url+ "/data-json/"+path), True) + elif type == "bin": + return self._get_binary_response(requests.get(url=self.url+"/data-bin/"+path, stream=True)) + elif type == "bs": + from collections import OrderedDict + bs = self._get_binary_response(requests.get(url=self.url+"/data-bs/"+path, stream=True)) + index=0 + msg = [] + for i in range(4): + size =int.from_bytes(bs[index:index+4], byteorder='big', signed=False) + index=index+4 + msg.append(bs[index:index+size]) + index=index+size + [main_header, data_header, data, timestamp] = msg + main_header = json.loads(main_header, object_pairs_hook=OrderedDict) + data_header = json.loads(data_header, object_pairs_hook=OrderedDict) + channel = data_header["channels"][0] + channel["encoding"] = "<" if channel.get("encoding", "little") else ">" + from bsread.data.helpers import get_channel_reader + channel_value_reader = get_channel_reader(channel) + return channel_value_reader(data) + + return self._get_response(requests.get(url=self.url+ "/data" + ("" if path is None else ( "/"+path))), False) + + def print_logs(self): + for l in self.get_logs(): + print ("%s %s %-20s %-8s %s" % tuple(l)) + + def print_devices(self): + for l in self.get_devices(): + print ("%-16s %-32s %-10s %-32s %s" % tuple(l)) + + def print_help(self, input = "<builtins>"): + for l in self.help(input): + print (l) + + #Events + def _sse_event_loop_task(self): + try: + while True: + try: + messages = SSEClient(self.url+"/events") + for msg in messages: + if (self.subscribed_events is None) or (msg.event in self.subscribed_events): + try: + value = json.loads(msg.data) + except: + value = str(msg.data) + self.event_callback(msg.event, value) + except IOError as e: + #print(e) + pass + except: + print("Error:", sys.exc_info()[1]) + #raise + finally: + print ("Exit SSE loop task") + self.sse_event_loop_thread = None + + + def start_sse_event_loop_task(self, subscribed_events = None, event_callback = None): + """ + Initializes server event loop task. + Args: + subscribed_events: list of event names to substribe to. If None subscribes to all. + event_callback: callback function. If None, self.on_event is called instead. + + Usage example: + def on_event(name, value): + if name == "state": + print ("State changed: ", value) + elif name == "record": + print ("Received scan record: ", value) + + pc.start_sse_event_loop_task(["state", "record"], on_event) + + """ + self.event_callback = event_callback if event_callback is not None else self.on_event + self.subscribed_events = subscribed_events + if SSEClient is not None: + if self.sse_event_loop_thread is None: + self.sse_event_loop_thread = threading.Thread(target=self._sse_event_loop_task, \ + args = (), \ + kwargs={}, \ + daemon=True) + self.sse_event_loop_thread.start() + else: + raise Exception ("sseclient library is not instlled: server events are not available") + + def on_event(self, name, value): + pass + \ No newline at end of file diff --git a/script/client/TellClient.py b/script/client/TellClient.py new file mode 100644 index 0000000..256dbdd --- /dev/null +++ b/script/client/TellClient.py @@ -0,0 +1,184 @@ +from PShellClient import PShellClient +import json +import time +import sys + + + +class TellClient(PShellClient): + def __init__(self, url): + PShellClient.__init__(self, url) + self.start_sse_event_loop_task(["state", "shell"]) + self.state = self.get_state() + self.debug=False + + def on_event(self, name, value): + if name == "state": + self.state = value + print ("State: ", value) + elif name == "shell": + if self.debug: + print ("> ", value) + + def get_state(self): + self.state = PShellClient.get_state(self) + return self.state + + def wait_ready(self): + count = 0 + #Monitors event but polls every second just n case an event is missed + while (True): + if self.state != "Busy": + break + time.sleep(0.01) + count = count + 1 + if count>=100: + count=0 + self.get_state() + if self.state != "Ready": + raise Exception("Invalid state: " + str(self.state)) + + def set_in_mount_position(self, value): + self.eval("in_mount_position = " + str(value) +"&") + + def is_in_mount_position(self): + return self.eval("in_mount_position&").lower()=="true" + + def get_samples_info(self): + return json.loads(self.eval("get_samples_info()&")) + + def set_samples_info(self, info): + #c.run("data/set_samples_info", pars= [info,], background=True) + self.eval("set_samples_info(" + json.dumps(info) + ")&") + + def start_cmd(self, cmd, *argv): + cmd = cmd + "(" + for a in argv: + cmd = cmd + (("'" + a + "'") if type(a) is str else str(a) ) + ", " + cmd = cmd + ")" + ret = self.start_eval(cmd) + self.get_state() + return ret + + def wait_cmd(self, cmd): + self.wait_ready() + result = self.get_result(cmd) + #print (result) + if result["exception"] is not None: + raise Exception(result["exception"] ) + return result["return"] + + def mount(self, segment, puck, sample, force=False, read_dm=False, auto_unmount=False): + #return self.run("motion/mount", pars= [segment,puck, sample, force, read_dm], background=True) + return self.start_cmd("mount", segment, puck, sample, force, read_dm, auto_unmount) + + def unmount(self, segment = None, puck = None, sample = None, force=False): + return self.start_cmd("unmount", segment, puck, sample, force) + + def scan_pin(self, segment, puck, sample, force=False): + return self.start_cmd("scan_pin", segment, puck, sample, force) + + def scan_puck(self, segment, puck, force=False): + return self.start_cmd("scan_puck", segment, puck, force) + + def dry(self, heat_time=30.0, speed=0.5, wait_cold = 30.0): + return self.start_cmd("dry", heat_time, speed, wait_cold) + + def move_cold(self): + return self.start_cmd("move_cold") + + def trash(self): + return self.start_cmd("trash") + + def abort_cmd(self): + self.abort() + self.eval("robot.stop_task()&") + + def set_gonio_mount_position(homing = False): + if homing: + self.eval("home_fast_table()") + self.eval("set_mount_position()") + + def get_mounted_sample(self): + ret = self.eval("get_setting('mounted_sample_position')&").strip() + return None if len(ret)==0 else ret + + def get_system_check(self): + try: + ret = self.eval("system_check()&") + except Exception as ex: + return ex + return "Ok" + + def get_robot_state(self): + return self.eval("robot.state&") + + + def get_robot_status(self): + status = self.eval("robot.take()&") + return status + + def get_detected_pucks(self): + return self.eval("get_detected_pucks()&") + + def set_pin_offset(self, value): + self.eval("set_pin_offset(" + str(value)+ ")&") + + def get_pin_offset(self): + return self.eval("get_pin_offset()&") + + def print_info(self): + print ("State: " + str(self.get_state())) + print ("Mounted sample: " + str(self.get_mounted_sample())) + print ("System check: " + str(self.get_system_check())) + print ("Robot state: " + str(self.get_robot_state())) + print ("Robot status: " + str(self.get_robot_status())) + print ("Detected pucks: " + str(self.get_detected_pucks())) + print ("Pin offset: " + str(self.get_pin_offset())) + print ("Mount position: " + str(self.is_in_mount_position())) + print ("") + +if __name__ == "__main__": + tell = TellClient("http://Alexandres-MBP.psi.ch:8080") + tell.print_info() + + info = [ + { + "userName": "User", + "dewarName": "Dewar", + "puckName": "Puck", + "puckBarcode": "XXX0001", + "puckType": "Minispine", + "puckAddress": "", + "sampleName": "Sample", + "sampleBarcode": "YYY0001", + "samplePosition": "1", + "sampleStatus": "Present", + "sampleMountCount": "0" , + }, + ] + print (tell.get_samples_info()) + tell.set_samples_info(info) + print (tell.get_samples_info()) + + tell.abort_cmd() + + cmd = tell.move_cold() + print (tell.wait_cmd(cmd)) + + cmd = tell.trash() + print (tell.wait_cmd(cmd)) + + cmd = tell.scan_pin("A", 1, 1) + print (tell.wait_cmd(cmd)) + + cmd = tell.scan_puck("A", 1, 1) + print (tell.wait_cmd(cmd)) + + cmd = tell.mount("A", 1, 1) + print (tell.wait_cmd(cmd)) + + print ("Mounted sample: " + str(tell.get_mounted_sample())) + cmd = tell.unmount() + print (tell.wait_cmd(cmd)) + print ("Mounted sample: " + str(tell.get_mounted_sample())) diff --git a/script/client/sseclient.py b/script/client/sseclient.py new file mode 100644 index 0000000..3c42858 --- /dev/null +++ b/script/client/sseclient.py @@ -0,0 +1,163 @@ +import codecs +import re +import time +import warnings + +import six + +import requests + + +# Technically, we should support streams that mix line endings. This regex, +# however, assumes that a system will provide consistent line endings. +end_of_field = re.compile(r'\r\n\r\n|\r\r|\n\n') + +class SSEClient(object): + def __init__(self, url, last_id=None, retry=3000, session=None, chunk_size=1024, **kwargs): + self.url = url + self.last_id = last_id + self.retry = retry + self.chunk_size = chunk_size + + # Optional support for passing in a requests.Session() + self.session = session + + # Any extra kwargs will be fed into the requests.get call later. + self.requests_kwargs = kwargs + + # The SSE spec requires making requests with Cache-Control: nocache + if 'headers' not in self.requests_kwargs: + self.requests_kwargs['headers'] = {} + self.requests_kwargs['headers']['Cache-Control'] = 'no-cache' + + # The 'Accept' header is not required, but explicit > implicit + self.requests_kwargs['headers']['Accept'] = 'text/event-stream' + + # Keep data here as it streams in + self.buf = u'' + + self._connect() + + def _connect(self): + if self.last_id: + self.requests_kwargs['headers']['Last-Event-ID'] = self.last_id + + # Use session if set. Otherwise fall back to requests module. + requester = self.session or requests + self.resp = requester.get(self.url, stream=True, **self.requests_kwargs) + self.resp_iterator = self.resp.iter_content(chunk_size=self.chunk_size) + + # TODO: Ensure we're handling redirects. Might also stick the 'origin' + # attribute on Events like the Javascript spec requires. + self.resp.raise_for_status() + + def _event_complete(self): + return re.search(end_of_field, self.buf) is not None + + def __iter__(self): + return self + + def __next__(self): + decoder = codecs.getincrementaldecoder( + self.resp.encoding)(errors='replace') + while not self._event_complete(): + try: + next_chunk = next(self.resp_iterator) + if not next_chunk: + raise EOFError() + self.buf += decoder.decode(next_chunk) + + except (StopIteration, requests.RequestException, EOFError) as e: + time.sleep(self.retry / 1000.0) + self._connect() + + # The SSE spec only supports resuming from a whole message, so + # if we have half a message we should throw it out. + head, sep, tail = self.buf.rpartition('\n') + self.buf = head + sep + continue + + # Split the complete event (up to the end_of_field) into event_string, + # and retain anything after the current complete event in self.buf + # for next time. + (event_string, self.buf) = re.split(end_of_field, self.buf, maxsplit=1) + msg = Event.parse(event_string) + + # If the server requests a specific retry delay, we need to honor it. + if msg.retry: + self.retry = msg.retry + + # last_id should only be set if included in the message. It's not + # forgotten if a message omits it. + if msg.id: + self.last_id = msg.id + + return msg + + if six.PY2: + next = __next__ + + +class Event(object): + + sse_line_pattern = re.compile('(?P<name>[^:]*):?( ?(?P<value>.*))?') + + def __init__(self, data='', event='message', id=None, retry=None): + self.data = data + self.event = event + self.id = id + self.retry = retry + + def dump(self): + lines = [] + if self.id: + lines.append('id: %s' % self.id) + + # Only include an event line if it's not the default already. + if self.event != 'message': + lines.append('event: %s' % self.event) + + if self.retry: + lines.append('retry: %s' % self.retry) + + lines.extend('data: %s' % d for d in self.data.split('\n')) + return '\n'.join(lines) + '\n\n' + + @classmethod + def parse(cls, raw): + """ + Given a possibly-multiline string representing an SSE message, parse it + and return a Event object. + """ + msg = cls() + for line in raw.splitlines(): + m = cls.sse_line_pattern.match(line) + if m is None: + # Malformed line. Discard but warn. + warnings.warn('Invalid SSE line: "%s"' % line, SyntaxWarning) + continue + + name = m.group('name') + if name == '': + # line began with a ":", so is a comment. Ignore + continue + value = m.group('value') + + if name == 'data': + # If we already have some data, then join to it with a newline. + # Else this is it. + if msg.data: + msg.data = '%s\n%s' % (msg.data, value) + else: + msg.data = value + elif name == 'event': + msg.event = value + elif name == 'id': + msg.id = value + elif name == 'retry': + msg.retry = int(value) + + return msg + + def __str__(self): + return self.data diff --git a/script/client/tell.py b/script/client/tell.py new file mode 100644 index 0000000..56c70b4 --- /dev/null +++ b/script/client/tell.py @@ -0,0 +1,79 @@ +import sys +import os +import code +import readline +import rlcompleter +import atexit + +from TellClient import TellClient + +tell = TellClient("http://PC12288:8080") + +def info(): + tell.print_info() + +def help(): + print ("Commands: \n\thelp()\n\tinfo()\n\tmount(segment, puck, sample)\n\tunmount() \n\tdry() " \ + "\n\tscan(segment, puck, sample=None) \n\tmove_cold() \n\ttrash() \n\tabort() " \ + "\n\tset_pin_offset(value)") + +def assert_transfer_allowed(): + if not tell.is_in_mount_position(): + raise Exception("Gonio is not in mount position") + +def mount(segment, puck, sample): + assert_transfer_allowed() + cmd = tell.mount(segment, puck, sample, True, True, True) + print (tell.wait_cmd(cmd)) + +def unmount(): + assert_transfer_allowed() + cmd=tell.unmount(force=True) + print (tell.wait_cmd(cmd)) + +def move_cold(): + cmd=tell.move_cold() + print (tell.wait_cmd(cmd)) + +def trash(): + cmd=tell.trash() + print (tell.wait_cmd(cmd)) + +def dry(): + cmd=tell.dry() + print (tell.wait_cmd(cmd)) + +def scan(segment, puck, sample=None): + if sample is None: + cmd=tell.scan_puck(segment, puck, True) + else: + cmd=tell.scan_pin(segment, puck, sample, True) + print (tell.wait_cmd(cmd)) + +def abort(): + tell.abort_cmd() + +def set_pin_offset(value): + tell.set_pin_offset(value) + +info() +help() + +#for line in sys.stdin: +# tell.print_info() +#print ("", end='\r> ') + +historyPath = os.path.expanduser("./.history") +def save_history(historyPath=historyPath): + readline.write_history_file(historyPath) +if os.path.exists(historyPath): + readline.read_history_file(historyPath) +atexit.register(save_history) + +#vars = globals(); vars.update(locals()) +vars =locals() +readline.set_completer(rlcompleter.Completer(vars).complete) +readline.parse_and_bind("tab: complete") + +code.interact(banner = "", local=vars) + diff --git a/script/data/get_samples_info.py b/script/data/get_samples_info.py new file mode 100644 index 0000000..00a5bc3 --- /dev/null +++ b/script/data/get_samples_info.py @@ -0,0 +1,4 @@ + + + +set_return(get_samples_info(as_json=False)) \ No newline at end of file diff --git a/script/data/pucks.py b/script/data/pucks.py new file mode 100644 index 0000000..3228c34 --- /dev/null +++ b/script/data/pucks.py @@ -0,0 +1,51 @@ +def get_puck_names(): + return [str(a)+str(b) for a in BLOCKS for b in range(1,6)] + + +def get_puck_info(): + ret = [] + #for puck in get_puck_names(): + for puck in BasePlate.pucks: + ret.append({"address" : str(puck.name), "status": str(puck.status), "barcode" : str(puck.id)}) + return ret + + +def get_puck_obj(address): + return BasePlate.getChild(address) + + +def get_puck_obj_by_id(puck_id): + for puck in BasePlate.pucks: + if puck.id == puck_id: + return puck + return None + + +def set_puck_info(puck_info): + #print puck_info + for i in puck_info: + p=get_puck_obj(i["address"]) + if p is not None: + if (p.status == 'Present') and ( i["status"] =='Present'): + if i['barcode'] and (i['barcode'] != str(None)): + #print "Setting ", p, " to ", i['barcode'] + p.id = i['barcode'] + +def clear_puck_info(): + save_puck_info([]) + +def save_puck_info(): + data = get_puck_info() + output_file = open( get_context().setup.expandPath("{context}/pucks_info.json") , "w") + output_file.write(json.dumps(data)) + output_file.close() + +def restore_puck_info(): + try: + inputfile = open(get_context().setup.expandPath("{context}/pucks_info.json"), "r") + info = json.loads(inputfile.read()) + except: + print >> sys.stderr, "Error reading pucks info file: " + str(sys.exc_info()[1]) + info = [] + set_puck_info(info) + \ No newline at end of file diff --git a/script/data/reports.py b/script/data/reports.py new file mode 100644 index 0000000..629b414 --- /dev/null +++ b/script/data/reports.py @@ -0,0 +1,39 @@ +from statsutils import * + + +def report(start, end): + """ + Data format for start and end: "dd/mm/yy" or "dd/mm/yy hh:mm:ss.mmm" + + """ + cmds = ["mount%", "unmount%", "dry%", "recover%", "trash%", "robot_recover%", "scan%", "homing%"] + + conn = get_stats_connection() + try: + print_stats(cmds, start, end) + + for cmd in cmds: + print_cmd_stats (cmd, start, end) + + + finally: + conn.close() + +def print_recs(cmd,start, end, result=("%%")): + """ + Data format for start and end: "dd/mm/yy" or "dd/mm/yy hh:mm:ss.mmm" + Result: "error", "abort, "success". Defaulr for all + + """ + + conn = get_stats_connection() + try: + print_cmd_records(cmd,start, end, result) + finally: + conn.close() + + +#Print all records: +# + +#report("01/03/19","01/04/19") diff --git a/script/data/samples.py b/script/data/samples.py new file mode 100644 index 0000000..7110d57 --- /dev/null +++ b/script/data/samples.py @@ -0,0 +1,350 @@ +import json +import re +import org.python.core.PyDictionary as PyDictionary + +SAMPLE_INFO_KEYS = ["userName", "dewarName", "puckName", "puckBarcode", "puckType", "puckAddress", + "sampleName", "samplePosition", "sampleBarcode", "sampleStatus", "sampleMountCount"] +samples_info = [] + + +def set_samples_info(info): + global samples_info + if (is_string(info)): + info = json.loads(info) + if not is_list(info): + raise Exception("Sample info must be a list (given object type is " + str(type(info)) + ")") + #Sanitize list + remove = [] + for sample in info: + try: + if set(sample.keys()) != set(SAMPLE_INFO_KEYS): + raise Exception() + except: + remove.append(sample) + for el in remove: + info.remove(el) + print "Invalid samples info element: " + str(el) + samples_info = info + save_samples_info() + #Trust beamline on assignments, so update puck info + update_puck_table() + + +def clear_samples_info(): + set_samples_info([]) + +def save_samples_info(): + data = get_samples_info(True) + output_file = open( get_context().setup.expandPath("{context}/samples_info.json") , "w") + output_file.write(data) + output_file.close() + get_context().sendEvent("samples_updated", True) + save_puck_info() + +def restore_samples_info(): + restore_puck_info() + try: + inputfile = open(get_context().setup.expandPath("{context}/samples_info.json"), "r") + info = inputfile.read() + except: + print >> sys.stderr, "Error reading sample info file: " + str(sys.exc_info()[1]) + info = [] + set_samples_info(info) + +def get_samples_info(as_json=True): + global samples_info + return json.dumps(to_list(samples_info)) if as_json else samples_info + +def has_puck_datamatrix(datamatrix): + if samples_info is not None: + for si in samples_info: + if si["puckBarcode"] == datamatrix: + return True + return False + +def add_puck_datamatrix(barcode, address = "", dewar = "Unknown", user = "Unknown", puck = "Unknown", type = "unipuck", sample_prefix = "Sample"): + if has_puck_datamatrix(barcode): + raise Exception("Datamatrix already defined: " + str(barcode)) + for s in range(1,17): + info = \ + { "userName": user, \ + "dewarName": dewar, \ + "puckName": puck, \ + "puckBarcode": address if barcode is None else barcode, \ + "puckType": type, \ + "puckAddress": address,\ + "sampleName": sample_prefix + " " + str(s), \ + "samplePosition": str(s),\ + "sampleStatus": "Unknown", \ + "sampleMountCount": "0", + } + samples_info.append(info) + save_samples_info() + +def remove_puck_datamatrix(barcode): + remove = [] + for si in samples_info: + if si["puckBarcode"] == barcode: + remove.append(si) + for el in remove: samples_info.remove(el) + save_samples_info() + + +barcode_clean_re = re.compile("[^a-zA-Z0-9]") + +def is_same_datamatrix(table, reading): + if table == reading: + return True + table2 = barcode_clean_re.sub("", table.upper()) + reading2 = barcode_clean_re.sub("", reading.upper()) + if table2 == reading2: + log("Assigning aproximative datamatrix " + reading ) + return True + return False + + +def set_puck_datamatrix(puck, datamatrix): + if puck is None: + puck = "" + if datamatrix is None: + datamatrix = "" + if samples_info is not None: + for si in samples_info: + #if si["puckBarcode"] == datamatrix: + if is_same_datamatrix(si["puckBarcode"],datamatrix): + si["puckAddress"] = puck + elif si["puckAddress"] == puck: + si["puckAddress"] = "" + save_samples_info() + +def reset_puck_datamatrix(puck = None): + if samples_info is not None: + for si in samples_info: + if (si["puckAddress"] == puck) or (puck is None): + si["puckAddress"] = "" + save_samples_info() + for p in BasePlate.getChildren(): + if (p.name == puck) or (puck is None): + p.id = None + +def get_puck_datamatrix(): + ret = {} + for si in samples_info: + if si["puckBarcode"] is not None and si["puckBarcode"]!="": + ret[si["puckBarcode"]] = si["puckAddress"] + return ret + +def get_puck_address(barcode): + try: + return get_puck_datamatrix()[barcode] + except: + return None + + +def update_puck_table(): + dms = get_puck_datamatrix() + for barcode in dms.keys(): + address = dms[barcode] + puck = get_puck_obj(address) + if puck is not None: + puck.id = barcode + + +#Sample mount/unmount + +def same_address(puck_add_1, sample_pos_1, puck_add_2, sample_pos_2): + if str(puck_add_1) != str(puck_add_2): + return False + try: + sample_pos_1 = int(sample_pos_1) + except: + return False + try: + sample_pos_2 = int(sample_pos_2) + except: + return False + return sample_pos_1 == sample_pos_2 + + +def update_samples_info_sample_mount(puck_address, sample_position, sample_detected, sample_id): + try: + if (samples_info is not None) and (puck_address is not None): + for si in samples_info: + if same_address( si["puckAddress"], si["samplePosition"], puck_address, sample_position): + if sample_detected: + if si["sampleStatus"] != "Mounted": + si["sampleStatus"] = "Mounted" + try: + mount_count = int(si["sampleMountCount"]) + except: + mount_count = 0 + si["sampleMountCount"] = mount_count + 1 + else: + si["sampleStatus"] = "Unknown" + + if sample_id is not None: + si["sampleBarcode"] = sample_id + + else: + if si["sampleStatus"] == "Mounted": + si["sampleStatus"] = "HasBeenMounted" + save_samples_info() + except: + pass + + +def update_samples_info_sample_unmount(puck_address, sample_position): + try: + if (samples_info is not None) and (puck_address is not None): + for si in samples_info: + if same_address( si["puckAddress"], si["samplePosition"], puck_address, sample_position): + si["sampleStatus"] = "HasBeenMounted" + save_samples_info() + return + except: + pass + +def update_samples_info_sample_scan(puck_address, sample_position, sample_detected, sample_id): + try: + if (samples_info is not None) and (puck_address is not None): + for si in samples_info: + if same_address( si["puckAddress"], si["samplePosition"], puck_address, sample_position): + if sample_detected: + if si["sampleStatus"] == "Unknown": + si["sampleStatus"] = "Present" + else: + if si["sampleStatus"] == "Present": + si["sampleStatus"] = "Unknown" + if sample_id is not None: + si["sampleBarcode"] = sample_id + save_samples_info() + return + except: + pass + + + +test_sample_data = [ \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0001", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "MySample 1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0001", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "MySample 2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0001", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "MySample 3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0001", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "MySample 4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0001", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "MySample 5", \ + "sampleBarcode": "", \ + "samplePosition": 5,\ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0002", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "MySample 1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0002", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "MySample 2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0002", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "MySample 3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0002", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "MySample 4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": "Alexandre", \ + "dewarName": "TEST", \ + "puckName": "My puck", \ + "puckBarcode": "AAA0002", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "MySample 5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + ] + \ No newline at end of file diff --git a/script/data/set_samples_info.py b/script/data/set_samples_info.py new file mode 100644 index 0000000..bc7e803 --- /dev/null +++ b/script/data/set_samples_info.py @@ -0,0 +1,4 @@ + + + +set_samples_info(args[0]) \ No newline at end of file diff --git a/script/devices/BarcodeReader.py b/script/devices/BarcodeReader.py new file mode 100644 index 0000000..48b84ca --- /dev/null +++ b/script/devices/BarcodeReader.py @@ -0,0 +1,83 @@ + + +class BarcodeReader(DeviceBase): + + def __init__(self, name, microscan, microscan_cmd): + DeviceBase.__init__(self, name) + self.microscan = microscan + self.microscan_cmd = microscan_cmd + + + def doInitialize(self): + self.disable() + self.readout = None + self.processing = False + self.task_callable=None + + def enable(self): + self.microscan_cmd.write("<H>") + self.setState(State.Ready) + + def disable(self): + self.microscan_cmd.write("<I>") + self.setState(State.Disabled) + + def get(self,timeout=1.0): + self.state.assertReady() + try: + self.setState(State.Busy) + self.microscan.flush() + ret = self.microscan.waitString(int(timeout * 1000)) + if ret is not None: + ret = ret.strip() + self.setCache(ret, None) + return ret + except: + self.setCache(None, None) + return None + finally: + if self.state == State.Busy: + self.setState(State.Ready) + + def doUpdate(self): + self.get() + + + def read(self,timeout=1.0): + if self.processing: + raise Exception("Ongoing read operation") + self.processing = True + try: + initial = self.state + if initial == State.Disabled: + self.enable() + try: + return self.get(timeout) + finally: + if initial == State.Disabled: + self.disable() + finally: + self.processing = False + + def _read_task(self, timeout): + global readout + self.readout = self.read(timeout) + return self.readout + + def start_read(self, timeout=1.0): + self.readout = None + self.task_callable = fork((self._read_task, (timeout,))) + + def get_readout(self): + return self.readout + + def wait_readout(self): + if self.task_callable is not None: + join(self.task_callable) + self.task_callable = None + return self.readout + + +add_device(BarcodeReader("barcode_reader", mscan_pin, mscan_pin_cmd), force = True) +add_device(BarcodeReader("barcode_reader_puck", mscan_puck, mscan_puck_cmd), force = True) + \ No newline at end of file diff --git a/script/devices/Gonio.py b/script/devices/Gonio.py new file mode 100644 index 0000000..0f264f5 --- /dev/null +++ b/script/devices/Gonio.py @@ -0,0 +1,43 @@ + + +def home_fast_table(): + caput ("SAR-EXPMX1:ASYN.AOUT", "enable plc 1") + +def get_fx_pos(): + return caget("SAR-EXPMX:MOT_FX.RBV", 'f') + +def set_fx_pos(pos): + return caput("SAR-EXPMX:MOT_FX.VAL", float(pos)) + + +def get_fy_pos(): + return caget("SAR-EXPMX:MOT_FY.RBV", 'f') + +def set_fy_pos(pos): + return caput("SAR-EXPMX:MOT_FY.VAL", float(pos)) + +def get_ry_pos(): + return caget("SAR-EXPMX:MOT_ROT_Y.RBV", 'f') + +def set_ry_pos(pos): + return caput("SAR-EXPMX:MOT_ROT_Y.VAL", float(pos)) + +def get_cz_pos(): + return caget("SAR-EXPMX:MOT_CZ.RBV", 'f') + +def set_cz_pos(pos): + return caput("SAR-EXPMX:MOT_CZ.VAL", float(pos)) + +def get_cx_pos(): + return caget("SAR-EXPMX:MOT_CX.RBV", 'f') + +def set_cx_pos(pos): + return caput("SAR-EXPMX:MOT_CX.VAL", float(pos)) + + +def set_mount_position(): + set_fx_pos(0.0) + set_fy_pos(0.0) + set_ry_pos(0.0) + set_cz_pos(0.0) + set_cx_pos(0.0) \ No newline at end of file diff --git a/script/devices/Hexiposi.py b/script/devices/Hexiposi.py new file mode 100644 index 0000000..e787e56 --- /dev/null +++ b/script/devices/Hexiposi.py @@ -0,0 +1,175 @@ +import ch.psi.pshell.device.DiscretePositionerBase as DiscretePositionerBase +import requests +import json + +class Hexiposi(DiscretePositionerBase): + def __init__(self, name, url): + DiscretePositionerBase.__init__(self, name, ["A","B","C","D","E","F"]) + self.PORT_SET=8002 + self.PORT_GET=8002 + if not url.startswith("http://"): + url = "http://" + url + if not url.endswith(":"): + url = url + ":" + self.url_set = url + str (self.PORT_SET)+ "/TellWeb/" + self.url_get = url + str (self.PORT_GET)+ "/TellWeb/" + self.moved = True + self.homing_state = State.Disabled + self.rback = self.UNKNOWN_POSITION + self.timeout = 3.0 + self.offline = False + + def doInitialize(self): + super(Hexiposi, self).doInitialize() + self.val = self.doReadReadback() + + def get_response(self, response): + if (response.status_code!=200): + raise Exception (response.text) + return json.loads(response.text) + + def get_status(self): + try: + self.status = self.get_response(requests.get(url=self.url_get+"get", timeout=self.timeout)) + self.estop = self.status["estop"] + self.homed = self.status["homed"] + self.error = self.status["errorCode"] + self.remote = self.status["mode"] == "remote" + self.moving = self.status["errorCode"] + self.pos = self.status["position"] + self.moving = self.status["moving"] + self.offset = self.status["offset"] + self.dpos = self.status["discretePosition"] + if (self.homed==False): rback = self.UNKNOWN_POSITION + elif self.dpos == 1: rback = "B" # "A" + elif self.dpos == 2: rback = "C" # "B" + elif self.dpos == 4: rback = "D" # "C" + elif self.dpos == 8: rback = "E" # "D" + elif self.dpos == 16: rback = "F" # "E" + elif self.dpos == 32: rback = "A" # "F" + else: rback = self.UNKNOWN_POSITION + if (rback == self.UNKNOWN_POSITION) or (rback != self.rback): + self.moved = True + self.rback = rback + self.offline = False + return self.status + except: + self.offline = True + self.updateState() + raise + + def set_deadband(self, value = 0.1): #degrees + ret = self.get_response(requests.get(url=self.url_set+"setDeadband?deadband=" + str(value), timeout=self.timeout)) + if ret["deadbandOutput"] == value: + return value + raise Excepiton("Error setting deadband: " + str(ret)) + + def move_pos(self, pos): + return self.get_response(requests.get(url=self.url_set+"set?pos=" + str(pos), timeout=self.timeout)) + + def move_home(self): + ret = self.get_response(requests.get(url=self.url_set+"set?home=1", timeout=self.timeout)) + try: + self.waitState(self.homing_state,1200) + except: + pass + return ret + + def stop_move(self): + return self.get_response(requests.get(url=self.url_set+"set?stop=1", timeout=self.timeout)) + + def set_offset(self, offset): + return self.get_response(requests.get(url=self.url_set+"setOffset?offset="+str(offset), timeout=self.timeout)) + + def doUpdate(self): + self.get_status() + super(Hexiposi, self).doUpdate() + + + def doStop(self): + self.stop_move() + + def doRead(self): + return str(self.val) + + def doReadReadback(self): + self.get_status() + return self.rback + + def doWrite(self, val): + #val = ord(val) - ord('A') +1 + val = ord(val) - ord('B') +1 + if val==0: val=6 #A + if val<1 or val>6: + raise Exception("Invalid value: " + str(val)) + moving = val != self.val + self.val = val + self.move_pos(self.val) + #Workaround as state does not changes immediatelly + if moving: + #try: + # self.waitState(State.Busy,1200) + #except: + # print sys.exc_info()[1] + start = time.time() + while self.state != State.Busy: + if time.time() - start > 1.5: + print "Timeout waiting Hexiposi busy" + break + self.update() + + def is_in_position(self, pos): + return self.take() == pos + + + def assert_in_position(self, pos): + if not is_in_position(pos): + raise Exception ("Hexiposi is not in position") + + def assert_homed(self): + if self.homed == False: + raise Exception ("Hexiposi is not homed") + + def assert_in_known_position(self): + self.get_status() + if self.rback == 'Unknown': + raise Exception("Hexiposi is in an unknown position, please home.") + + #def isReady(self): + # self.get_status() + # return self.moving == False + + def updateState(self): + if self.isSimulated(): + self.setState(State.Ready) + elif self.offline: + self.setState(State.Offline) + elif self.homed == False: + self.setState(self.homing_state) + elif self.moving: + self.setState(State.Busy) + else: + self.setState(State.Ready) + + +#http://myriox06da:8002/TellWeb/get +dev = Hexiposi("hexiposi", "myriotell6d") + +#If no Rotation Lid is mounted set it to simulated +#dev.setSimulated() + +add_device(dev, True) +hexiposi.polling=1000 +#print dev.url +#print dev.get_status() + +class hexiposi_position(ReadonlyRegisterBase): + def doRead(self): + try: + return float(hexiposi.pos) + except: + return float("nan") + +add_device(hexiposi_position(), True) +hexiposi_position.polling = 1000 +hexiposi.set_deadband(1.0) diff --git a/script/devices/LaserDistance.py b/script/devices/LaserDistance.py new file mode 100644 index 0000000..086a508 --- /dev/null +++ b/script/devices/LaserDistance.py @@ -0,0 +1,27 @@ +class LaserDistance(ReadonlyRegisterBase): + def __init__(self): + ReadonlyRegisterBase.__init__(self, "laser_distance") + + def doRead(self): + ret = ue.readable.read() + ret = None if ret is None else (0.0 if math.isnan(ret) else ret) + return ret + +class ListenerAI (DeviceListener): + def onValueChanged(self, device, value, former): + if laser_distance is not None: + value = None if value is None else (0.0 if math.isnan(value) else value) + laser_distance.setCache(value, None) + + +for l in ue.listeners: + if Nameable.getShortClassName(l.getClass()) == "ListenerAI": + ue.removeListener(l) + + +listenerAI = ListenerAI() +ue.addListener(listenerAI) + +laser_distance=LaserDistance() +add_device(laser_distance, True) +laser_distance.update() diff --git a/script/devices/LedCtrl.py b/script/devices/LedCtrl.py new file mode 100644 index 0000000..da4e51d --- /dev/null +++ b/script/devices/LedCtrl.py @@ -0,0 +1,46 @@ +import ch.psi.pshell.device.DiscretePositionerBase as DiscretePositionerBase + + +class LedPositioner(DiscretePositionerBase): + def __init__(self): + DiscretePositionerBase.__init__(self, "led_ctrl", ["On", "Off"]) + self.setState(State.Ready) + self.val = self.doReadReadback() + + def doRead(self): + return self.val + + def doReadReadback(self): + return "On" if get_led_state() else "Off" + + def doWrite(self, val): + self.val = val + if self.val == "On": + set_led_state(True) + else: + set_led_state(False) + + +add_device(LedPositioner(), True) +led_ctrl.polling = 1000 + + + + +import ch.psi.pshell.device.ProcessVariableConfig as ProcessVariableConfig +class LedLevel(ProcessVariableBase): + def __init__(self, name): + ProcessVariableBase.__init__(self, name, ProcessVariableConfig()) + + def doRead(self): + return get_led_level() + + def doWrite(self, val): + return set_led_level(val) + +led_level = LedLevel("led_level") +led_level.config.minValue = 0.0 +led_level.config.maxValue = 100.0 +led_level.config.unit = "%" +add_device(led_level, True) +led_level.polling = 1000 \ No newline at end of file diff --git a/script/devices/OneWire.py b/script/devices/OneWire.py new file mode 100644 index 0000000..20e590f --- /dev/null +++ b/script/devices/OneWire.py @@ -0,0 +1,134 @@ +import traceback +from datetime import datetime + +class Detector(ReadonlyRegisterBase): + def __init__(self, name): + ReadonlyRegisterBase.__init__(self, name) + self.reset() + + def set_inputs(self, inputs): + self.inputs = inputs + if self.take() != inputs.values(): + self.setCache(inputs.values(), None) + if self.getParent()!=None: + self.getParent().updateCache() + if (len(self.take()) == 0): + self.setState(State.Offline) + else: + self.setState(State.Ready) + + def set_input(self, index, val): + self.inputs[index] = val + self.set_inputs(self.inputs) + + def reset(self): + self.id = None + self.sn = None + self.status = None + self.type = None + self.set_inputs({}) + +class Esera(TcpDevice): + def __init__(self, name, server, timeout = 1000, retries = 1): + TcpDevice.__init__(self, name, server) + self.setMode(self.Mode.FullDuplex) + self.detectors = [] + for i in range(30): + self.detectors.append(Detector("Detector " + str(i+1))) + self.setChildren(self.detectors) + self.completed_initializatiod = False + self.debug = False + + def start(self): + self.getLogger().info("Starting controller") + self.write("set,sys,run,1\n") + + def stop(self): + self.getLogger().info("Stopping controller") + self.write("set,sys,run,0\n") + + def list(self): + self.write("get,owb,listall1\n") + + def req_data(self): + self.write("get,sys,data\n") + + def doInitialize(self): + super(Esera, self).doInitialize() + self.init_timestamp = time.time() + try: + self.setState(State.Ready) #So can communicate + for det in self.detectors: + det.reset() + self.list() + time.sleep(1.0) + self.check_started() + self.req_data() + except: + print >> sys.stderr, traceback.format_exc() + self.getLogger().warning(traceback.format_exc()) + raise + + def doUpdate(self): + self.check_started() + self.req_data() + + def updateCache(self): + #print "Update" + cache = [] + for det in self.detectors: + cache.append(det.take()) + self.setCache(cache, None) + + + def check_started(self): + if not self.completed_initializatiod: + init = True + for det in self.detectors: + if det.id == None: + init = False + break + if init: + self.completed_initializatiod = True + print("Completed initialization") + self.getLogger().info("Completed initialization") + self.start() + + def onString(self, msg): + if self.debug: + print datetime.now() , " - " , msg + tokens = msg.split("|") + if len(tokens)>1: + try: + if msg[:3] == "LST": + #LST|1_OWD1|3AF361270000009E|S_0|DS2413| + if tokens[1] > 1: + index = int(tokens[1].split("_")[1][3:]) - 1 + if index < len(self.detectors): + det = self.detectors[index] + det.id = tokens[1] + det.sn= tokens[2] if len(tokens)>2 else None + det.status = int(tokens[3][2:]) if len(tokens)>3 else None + det.type = tokens[4] if len(tokens)>4 else None + if det.status!= 0: + det.set_inputs({}) + else: + for det in self.detectors: + if det.id is not None and msg.startswith(det.id): + det_id = int(tokens[0][len(det.id)+1:]) + det.set_input(det_id, int(tokens[1])) + except: + print >> sys.stderr, traceback.format_exc() + self.getLogger().log(traceback.format_exc()) + + + + + + + +add_device(Esera("onewire", "129.129.126.83:5000"), force = True) +onewire.setPolling(500) +add_device(onewire.detectors[0], force = True) +add_device(onewire.detectors[1], force = True) +add_device(onewire.detectors[2], force = True) diff --git a/script/devices/RobotModbus.py b/script/devices/RobotModbus.py new file mode 100644 index 0000000..cd8abaf --- /dev/null +++ b/script/devices/RobotModbus.py @@ -0,0 +1,46 @@ +class RobotModbus(DeviceBase): + def __init__(self, name): + DeviceBase.__init__(self, name) + robot_req.write(0) + + def execute(self, command, *argv): + if robot_req.read() != 0: + raise Exception("Ongoing command") + if robot_ack.read() != 0: + raise Exception("Robot is not ready") + robot_cmd.write(command) + args = [0] * robot_args.size + index = 0 + for arg in argv: + args[index] = arg + index = index + 1 + if index == robot_args.size: + raise Exception("Invalid number of arguments") + robot_args.write(to_array(args, 'i')) + try: + self.request() + err = robot_ack.take() + if err == 1: + ret = robot_ret.read() + return ret + if err == 2: + raise Exception("Invalid command: " + str(command)) + raise Exception("Unknown error: " + str(err)) + finally: + self.cancel_request() + + def request(self): + robot_req.write(1) + while robot_ack.read() == 0: + time.sleep(0.001) + + def cancel_request(self): + robot_req.write(0) + while robot_ack.read() != 0: + time.sleep(0.001) + + def mount(self, puck, sample): + return self.execute('1', '1', puck, sample) + +add_device(RobotModbus("robot_mb"), force = True) + diff --git a/script/devices/RobotMotors.py b/script/devices/RobotMotors.py new file mode 100644 index 0000000..07ba862 --- /dev/null +++ b/script/devices/RobotMotors.py @@ -0,0 +1,62 @@ + +import ch.psi.pshell.device.PositionerConfig as PositionerConfig + + + +ROBOT_MOTORS = ["x" , "y", "z", "rx", "ry", "rz"] + + +class RobotCartesianMotor (PositionerBase): + def __init__(self, robot, index): + PositionerBase.__init__(self, robot.name + "_" + ROBOT_MOTORS[index], PositionerConfig()) + self.index = index + self.robot = robot + + #ATTENTION: Always initialize cartesian motors before scanning (or call robot.set_motors_enabled(True)) + def doInitialize(self): + self.setCache(self.doRead(), None) + + def doRead(self): + return float("nan") if self.robot.cartesian_destination is None else float(self.robot.cartesian_destination[self.index]) + + def doWrite(self, value): + if self.robot.cartesian_destination is not None: + #print "Move " + ROBOT_MOTORS[self.index] + " to " + str(value) + self.robot.cartesian_destination[self.index] = float(value) + self.robot.set_pnt(robot.cartesian_destination , "tcp_p") + self.robot.movel("tcp_p", self.robot.tool , DESC_SCAN) + + def doReadReadback(self): + return float("nan") if self.robot.cartesian_pos is None else float(self.robot.cartesian_pos[self.index]) + + + + +ROBOT_JOINT_MOTORS = ["j1" , "j2", "j3", "j4", "j5", "j6"] + + +class RobotJointMotor (PositionerBase): + def __init__(self, robot, index): + PositionerBase.__init__(self, robot.name + "_" + ROBOT_JOINT_MOTORS[index], PositionerConfig()) + self.index = index + self.robot = robot + + def doInitialize(self): + self.setpoint = self.doReadReadback() + self.setCache(self.doRead(), None) + + def doRead(self): + return self.setpoint + + def doWrite(self, value): + #print "Move " + ROBOT_JOINT_MOTORS[self.index] + " to " + str(value) + self.setpoint = value + joint = self.robot.herej() + joint[self.index] = value + self.robot.set_jnt(joint, name="tcp_j") + self.robot.movej("tcp_j", self.robot.tool , DESC_SCAN) + + def doReadReadback(self): + return self.robot.herej()[self.index] if self.robot.joint_pos is None else float(self.robot.joint_pos[self.index]) + + \ No newline at end of file diff --git a/script/devices/RobotSC.py b/script/devices/RobotSC.py new file mode 100644 index 0000000..3f2b727 --- /dev/null +++ b/script/devices/RobotSC.py @@ -0,0 +1,358 @@ +TOOL_CALIBRATION = "tCalib" +TOOL_SUNA= "tSuna" +TOOL_DEFAULT= TOOL_SUNA + +FRAME_TABLE = "fTable" + +DESC_FAST = "mFast" +DESC_SLOW = "mSlow" +DESC_SCAN = "mScan" +DESC_DEFAULT = DESC_FAST + +AUX_SEGMENT = "X" + + +DEFAULT_ROBOT_POLLING = 500 +TASK_WAIT_ROBOT_POLLING = 50 + + +run("devices/RobotTCP") + + +simulation = False + +joint_forces = False + +class RobotSC(RobotTCP): + def __init__(self, name, server, timeout = 1000, retries = 1): + RobotTCP.__init__(self, name, server, timeout, retries) + self.set_tasks(["getDewar", "putDewar", "putGonio", "getGonio", "recover", "moveDewar", "moveCold", "movePark", "moveGonio","moveHeater", "moveScanner","moveHome", "moveAux"]) + self.set_known_points(["pPark", "pGonioA", "pDewar", "pGonioG", "pScan", "pHeater", "pHeat", "pHeatB", "pLaser","pHelium", "pHome", "pCold", "pAux"]) + self.setPolling(DEFAULT_ROBOT_POLLING) + self.last_command_timestamp = None + self.last_command_position = None + #self.setSimulated() + + def move_dewar(self): + self.start_task('moveDewar') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_dewar() + self.last_command_position = "dewar" + self.last_command_timestamp = time.time() + + def move_cold(self): + self.start_task('moveCold') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_cold() + self.last_command_position = "cold" + self.last_command_timestamp = time.time() + + def move_home(self): + self.start_task('moveHome') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_home() + self.last_command_position = "home" + self.last_command_timestamp = time.time() + + def get_dewar(self, segment, puck, sample): + segment = self.toSegmentNumber(segment) + self.start_task('getDewar',segment, puck, sample, is_room_temp()) + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_dewar() + self.last_command_position = "dewar" + self.last_command_timestamp = time.time() + + def put_dewar(self, segment, puck, sample): + segment = self.toSegmentNumber(segment) + self.assert_dewar() + self.start_task('putDewar',segment, puck, sample, is_room_temp()) + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + #self.assert_dewar() + self.assert_cold() + self.last_command_position = "dewar" + self.last_command_timestamp = time.time() + + def put_gonio(self): + assert_detector_safe() + pin_offset = get_pin_offset() + pin_angle_offset = get_pin_angle_offset() + print "Pin offset = " + str(pin_offset) + self.start_task('putGonio', pin_offset) + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_gonio() + self.last_command_position = "gonio" + self.last_command_timestamp = time.time() + + def get_gonio(self): + assert_detector_safe() + pin_offset = get_pin_offset() + print "Pin offset = " + str(pin_offset) + self.start_task('getGonio', pin_offset) + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_gonio() + self.last_command_position = "gonio" + self.last_command_timestamp = time.time() + + def get_aux(self, sample): + self.assert_aux() + self.start_task('getAuxiliary', sample) + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_aux() + self.last_command_position = "aux" + self.last_command_timestamp = time.time() + + def put_aux(self, sample): + self.assert_aux() + self.start_task('putAuxiliary', sample) + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_aux() + self.last_command_position = "aux" + self.last_command_timestamp = time.time() + + def move_scanner(self): + self.start_task('moveScanner') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_scanner() + self.last_command_position = "scanner" + self.last_command_timestamp = time.time() + + def move_laser(self): + self.start_task('moveScanner') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_laser() + self.last_command_position = "scanner" + self.last_command_timestamp = time.time() + + #def do_scan(self): + # self.start_task('doScan') + # self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + # self.assert_scan_stop() + + def move_gonio(self): + assert_detector_safe() + self.start_task('moveGonio') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_gonio() + self.last_command_position = "gonio" + self.last_command_timestamp = time.time() + + + def move_park(self): + self.start_task('movePark') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_park() + self.last_command_position = "park" + self.last_command_timestamp = time.time() + + def move_heater(self, speed=-1, to_bottom=True): + self.start_task('moveHeater', speed, to_bottom) + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + if to_bottom: + self.assert_heater_bottom() + else: + self.assert_heater() + self.last_command_position = "heater" + self.last_command_timestamp = time.time() + + + def robot_recover(self): + self.start_task('recover') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_home() + self.last_command_position = "home" + self.last_command_timestamp = time.time() + + def move_aux(self): + self.start_task('moveAux') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_aux() + self.last_command_position = "aux" + self.last_command_timestamp = time.time() + + def get_calibration_tool(self): + self.start_task('getCalTool') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_scanner() + self.last_command_position = "scanner" + self.last_command_timestamp = time.time() + + def put_calibration_tool(self): + self.start_task('putCalTool') + self.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + self.assert_scanner() + self.command_timestamps["scanner"] = time.time() + + + + def toSegmentNumber(self, segment): + if is_string(segment): + segment = ord(segment.upper()) - ord('A') +1 + return segment + + + def on_event(self,ev): + #print "EVT: " + ev + pass + def on_change_working_mode(self, working_mode): + if get_device("hexiposi") is not None: + hexiposi.moved = True #Force image processing on first sample + + def doUpdate(self): + #start = time.time() + RobotTCP.doUpdate(self) + global simulation + if not simulation: + if joint_forces: + if self.state != State.Offline: + self.get_joint_forces() + for dev in [jf1, jf2, jf3, jf4,jf5, jf6, jfc]: + dev.update() + #print time.time() -start + + def start_task(self, program, *args, **kwargs): + #TODO: Check safe position + return RobotTCP.start_task(self, program, *args, **kwargs) + + def stop_task(self): + RobotTCP.stop_task(self) + #TODO: Restore safe position + + + def set_remote_mode(self): + robot.set_profile("remote") + + def set_local(self): + robot.set_profile("default") + + def is_park(self): + return self.is_in_point("pPark") + + def is_cold(self): + return self.is_in_point("pCold") + + def is_home(self): + return self.is_in_point("pHome") + + def is_dewar(self): + return self.is_in_point("pDewar") + + def is_heater(self): + return self.is_in_point("pHeat") + + def is_heater_home(self): + return self.is_in_point("pHeater") + + def is_heater_bottom(self): + return self.is_in_point("pHeatB") + + def is_gonio(self): + return self.is_in_point("pGonioA") + + def is_helium(self): + return self.is_in_point("pHelium") + + def is_scanner(self): + return self.is_in_point("pScan") + + def is_aux(self): + return self.is_in_point("pAux") + + def is_laser(self): + return self.is_in_point("pLaser") + + def is_cleared(self): + #return self.is_home() or self.is_park() or self.is_dewar() or self.is_dewar_home() + return self.get_current_point() is not None + + def assert_heater_home(self): + self.assert_in_point("pHeater") + + def assert_cold(self): + self.assert_in_point("pCold") + + def assert_heater(self): + self.assert_in_point("pHeat") + + def assert_heater_bottom(self): + self.assert_in_point("pHeatB") + + def assert_park(self): + self.assert_in_point("pPark") + + def assert_home(self): + self.assert_in_point("pHome") + + def assert_dewar(self): + self.assert_in_point("pDewar") + + def assert_gonio(self): + self.assert_in_point("pGonioA") + + def assert_helium(self): + self.assert_in_point("pHelium") + + def assert_scanner(self): + self.assert_in_point("pScan") + + def assert_aux(self): + self.assert_in_point("pAux") + + def assert_laser(self): + self.assert_in_point("pLaser") + + def assert_cleared(self): + if not self.is_cleared(): + raise Exception("Robot not in cleared position") + + def wait_ready(self): + robot.waitState(State.Ready, 1000) #robot.state.assertReady() + +if simulation: + add_device(RobotSC("robot","localhost:1000"),force = True) +else: + add_device(RobotSC("robot", "TellRobot6D:1000"), force = True) + #add_device(RobotSC("robot", "129.129.110.110:1000"), force = True) + +#robot.latency = 0.005 +robot.set_default_desc(DESC_DEFAULT) +robot.default_speed = 20 +robot.set_frame(FRAME_DEFAULT) +robot.set_tool(TOOL_DEFAULT) +robot.setPolling(DEFAULT_ROBOT_POLLING) + +robot.get_current_point() #TODO: REMOVE WHEN CURRENT POINT REPORTED BY POLLING MESSAGE + +class jf1(ReadonlyRegisterBase): + def doRead(self): + return None if robot.joint_forces == None else robot.joint_forces[0] +class jf2(ReadonlyRegisterBase): + def doRead(self): + return None if robot.joint_forces == None else robot.joint_forces[1] +class jf3(ReadonlyRegisterBase): + def doRead(self): + return None if robot.joint_forces == None else robot.joint_forces[2] +class jf4(ReadonlyRegisterBase): + def doRead(self): + return None if robot.joint_forces == None else robot.joint_forces[3] +class jf5(ReadonlyRegisterBase): + def doRead(self): + return None if robot.joint_forces == None else robot.joint_forces[4] +class jf6(ReadonlyRegisterBase): + def doRead(self): + return None if robot.joint_forces == None else robot.joint_forces[5] +class jfc(ReadonlyRegisterBase): + def doRead(self): + if robot.joint_forces == None: + return float('NaN') + if robot.powered == False: + return float('NaN') + return (robot.joint_forces[1]+74)/4 + (robot.joint_forces[2]+30)/4 + (robot.joint_forces[4]-0.8)/0.2 + +if joint_forces: + add_device(jf1(), force = True) + add_device(jf2(), force = True) + add_device(jf3(), force = True) + add_device(jf4(), force = True) + add_device(jf5(), force = True) + add_device(jf6(), force = True) + add_device(jfc(), force = True) \ No newline at end of file diff --git a/script/devices/RobotTCP.py b/script/devices/RobotTCP.py new file mode 100644 index 0000000..4727d68 --- /dev/null +++ b/script/devices/RobotTCP.py @@ -0,0 +1,919 @@ +import threading + +FRAME_DEFAULT = "world" +FLANGE = "flange" + +MAX_NUMBER_PARAMETERS = 20 + +run("devices/RobotMotors") + +class RobotTCP(TcpDevice, Stoppable): + def __init__(self, name, server, timeout = 1000, retries = 1): + TcpDevice.__init__(self, name, server) + self.timeout = timeout + self.retries = retries + self.header = None + self.trailer = "\n" + self.array_separator = '|' + self.cmd_separator = ' ' + self.msg_id = 0 + self.working_mode = "invalid" + self.status = "invalid" + self.powered = None + self.settled = None + self.empty = None + self.working_mode = None + self.status = None + self.lock = threading.Lock() + self.joint_forces = None + self.current_task = None + self.current_task_ret = None + self.high_level_tasks = [] + self.known_points = [] + self.current_points = [] + self.cartesian_destination = None + #self.flange_pos = [None] * 6 + self.cartesian_pos = [None] * 6 + self.joint_pos = [None] * 6 + self.cartesian_motors_enabled = False + self.cartesian_motors = [] + self.joint_motors_enabled = False + self.joint_motors = [] + self.tool = None + self.default_desc = None + self.tool_open = None + #self.tool_trsf = [0.0] * 6 + self.frame = FRAME_DEFAULT + self.polling_interval = 0.01 + self.reset = True + self.default_tolerance = 5 + self.default_speed = 100 + self.latency = 0 + self.last_msg_timestamp = 0 + + self.task_start_retries = 3 + self.exception_on_task_start_failure = True #Tasks may start and be finished when checked + + + def doInitialize(self): + super(RobotTCP, self).doInitialize() + self.reset = True + + def set_tool(self, tool): + self.tool = tool + #self.tool_trsf = self.get_tool_trsf() + self.evaluate("tcp_curtool=" + tool) + if self.cartesian_motors_enabled: + self.update() + self.set_motors_enabled(True) + self.is_tool_open() + + def get_tool(self): + return self.tool + + def set_frame(self, frame): + self.frame = frame + self.evaluate("tcp_curframe=" + frame) + if self.cartesian_motors_enabled: + self.update() + self.set_motors_enabled(True) + self.waitCacheChange(5000) + + def get_frame(self): + return self.frame + + def set_default_frame(self): + self.set_frame(FRAME_DEFAULT) + + def assert_tool(self, tool=None): + if tool is None: + if self.tool is None: + raise Exception("Tool is undefined") + elif self.tool != tool: + raise Exception("Invalid tool: " + self.tool) + + def set_default_desc(self,default_desc): + self.default_desc=default_desc + + def get_default_desc(self): + return self.default_desc + + def set_tasks(self,tasks): + self.high_level_tasks=tasks + + def get_tasks(self): + return self.high_level_tasks + + def set_known_points(self, points): + self.known_points=points + + def get_known_points(self): + return self.known_points + + def get_current_points(self, tolerance = None): + ret = self.is_in_points(*self.known_points, tolerance = tolerance) + current_points = [] + for i in range(len(ret)): + if ret[i] == True: + current_points.append(self.known_points[i]) + return current_points + + def get_current_point(self, tolerance = None): + current_points = self.get_current_points(tolerance) + if (current_points is not None) and ( len(current_points) >0): + return current_points[0] + return None + + def get_current_points_cached(self): + return self.current_points + + def get_current_point_cached(self): + if (self.current_points is not None) and (len (self.current_points) >0): + return self.current_points[0] + return None + + def assert_in_known_point(self, tolerance = None): + if self.get_current_point(tolerance) is None: + raise Exception ("Robot not in known point") + + def _sendReceive(self, msg_id, msg = "", timeout = None): + if self.latency >0: + timespan = time.time() - self.last_msg_timestamp + if timespan<self.latency: + time.sleep(self.latency-timespan) + tx = self.header if (self.header != None) else "" + tx = tx + msg_id + " " + msg + if (len(tx)>150): + raise Exception("Exceeded maximum message size") + self.getLogger().finer("TX = '" + str(tx)+ "'") + if (self.trailer != None): tx = tx + self.trailer + if self.isSimulated(): + return "" + rx = self.sendReceive(tx, msg_id, self.trailer , 0, self.timeout if timeout is None else timeout, self.retries) + self.last_msg_timestamp = time.time() + rx=rx[:-1] #Remove 0A + self.getLogger().finer("RX = '" + str(rx) + "'") + if rx[:3] != msg_id: + if (time.time()-start) >= timeout: + raise Exception("Received invalid message id: " + str(rx[:3]) + " - expecting:" + msg_id ) + if len(rx)<4: + raise Exception("Invalid message size: " + str(len(rx)) ) + if rx[3] == "*": + raise Exception(rx[4:]) + return rx[4:] + + def call(self, msg, timeout = None): + self.lock.acquire() + try: + id = "%03d" % self.msg_id + self.msg_id = (self.msg_id+1)%1000 + return self._sendReceive(id, msg, timeout) + finally: + self.lock.release() + + def execute(self, command, *args, **kwargs): + timeout = None if (kwargs is None) or (not kwargs.has_key("timeout")) else kwargs["timeout"] + msg = str(command) + if len(args)>MAX_NUMBER_PARAMETERS: + raise Exception("Exceeded maximum number of parameters") + for i in range(len(args)): + msg += (self.cmd_separator if (i==0) else self.array_separator) + str(args[i]) + rx = self.call(msg, timeout) + if rx.count(self.array_separator)>0: + return rx.split(self.array_separator) + return rx + + def evaluate(self, cmd, timeout=None): + ret = self.execute('eval', cmd, timeout=timeout) + if is_string(ret): + if ret.strip() != "": raise Exception(ret) + + def get_var(self, name): + return self.execute('get_var', name) + + #Makes app crash + #def get_str(self, name='s'): + # return self.execute('get_str', name) + + def get_arr(self, name, size): + return self.execute('get_arr', name, size) + + def get_bool(self, name = "tcp_b"): + return True if (self.execute('get_bool', name).strip() == '1') else False + + def get_int(self, name ="tcp_n"): + return int(self.get_var(name)) + + def get_float(self, name ="tcp_n"): + return float(self.get_var(name)) + + def get_int_arr(self, size, name="tcp_a"): + # not working. A Jython bug in PyUnicaode? + #return [int(x) for x in self.get_arr("tcp_a", size)] + ret = [] + a=self.get_arr(name, size) + for i in range(size): + ret.append(int(a[i])) + return ret + + def get_float_arr(self, size, name="tcp_a"): + #return [float(x) for x in self.get_arr("tcp_a", size)] + a=self.get_arr(name, size) + ret = []; + for i in range(size): ret.append(float(a[i])); + return ret + + def get_trsf(self, name="tcp_t"): + a = self.execute('get_trf', name) + ret = [] + for i in range(6): ret.append(float(a[i])) + return ret + + def set_trsf(self, l, name="tcp_t"): + self.evaluate(name + "={" + str(l[0]) + ","+ str(l[1]) + ","+ str(l[2]) + ","+ str(l[3]) + ","+ str(l[4]) + ","+ str(l[5]) + "}") + + def get_jnt(self, name="tcp_j"): + a = self.execute('get_jnt', name) + ret = [] + for i in range(6): ret.append(float(a[i])) + return ret + + def set_jnt(self, l, name="tcp_j"): + self.evaluate(name + "={" + str(l[0]) + ","+ str(l[1]) + ","+ str(l[2]) + ","+ str(l[3]) + ","+ str(l[4]) + ","+ str(l[5]) + "}") + + def get_pnt(self, name="tcp_p"): + #a = self.execute('get_pnt', name) + #ret = [] + #for i in range(6): ret.append(float(a[i])) + #return ret + return self.get_trsf(name+".trsf") + + #trsf = (x,y,z,rx,ry,rz) + #TODO: config = (shoulder, elbow, wrist) + def set_pnt(self, trsf, name="tcp_p", config=None): + self.set_trsf(trsf, name+".trsf") + + def get_tool_trsf(self, name=None): + if name is None: + self.assert_tool() + name = self.tool + return self.get_trsf(name+".trsf") + + def set_tool_trsf(self, trsf, name=None): + if name is None: + self.assert_tool() + name = self.tool + self.set_trsf(trsf, name+".trsf") + + def eval_int(self, cmd): + if self.isSimulated(): + return 0 + self.evaluate("tcp_n=" + cmd) + return self.get_int() + + def eval_float(self, cmd): + if self.isSimulated(): + return 0.0 + self.evaluate("tcp_n=" + cmd) + return self.get_float() + + def eval_bool(self, cmd): + if self.isSimulated(): + return False + self.evaluate("tcp_b=" + cmd) + return self.get_bool() + + #def eval_str(self, cmd): + # self.evaluate("s=" + cmd) + # return self.get_str() + + def eval_jnt(self, cmd): + self.evaluate("tcp_j=" + cmd) + return self.get_jnt() + + def eval_trf(self, cmd): + self.evaluate("tcp_t=" + cmd) + return self.get_trsf() + + def eval_pnt(self, cmd): + self.evaluate("tcp_p=" + cmd) + return self.get_pnt() + + + #Robot control + def is_powered(self): + self.powered = self.eval_bool("isPowered()") + return self.powered + + def enable(self): + if not self.is_powered(): + self.evaluate("enablePower()") + time.sleep(1.0) + if not self.is_powered(): + raise Exception("Cannot enable power") + + #waits for power to be actually cut off + def disable(self): + self.evaluate("disablePower()", timeout=5000) + + def get_monitor_speed(self): + self.speed = self.eval_int("getMonitorSpeed()") + return self.speed + + def set_monitor_speed(self, speed): + ret = self.eval_int("setMonitorSpeed(" + str(speed) + ")") + if (ret==-1): raise Exception("The robot is not in remote working mode") + if (ret==-2): raise Exception("The monitor speed is under the control of the operator") + if (ret==-3): raise Exception("The specified speed is not supported") + + def set_default_speed(self): + set_monitor_speed(self.default_speed) + + def is_calibrated(self): + return self.eval_bool("isCalibrated()") + + def save_program(self): + ret = self.execute('save', timeout=5000) + if str(ret) != "0": raise Exception("Error saving program: " + str(ret)) + + def _update_working_mode(self, mode, status): + cur_mode = self.working_mode + if mode==1: + self.working_mode = "manual" + self.status = "hold" if status==6 else "move" + elif mode==2: + self.working_mode = "test" + self.status = "hold" if status==3 else "move" + elif mode==3: + self.working_mode = "local" + self.status = "hold" if status==2 else "move" + elif mode==4: + self.working_mode = "remote" + self.status = "hold" if status==2 else "move" + else: + self.working_mode = "invalid" + self.status = "invalid" + if self.working_mode != cur_mode: + try: + self.on_change_working_mode(self.working_mode) + except: + pass + + def read_working_mode(self): + try: + mode = self.eval_int("workingMode(tcp_a)") + status = int(self.get_var("tcp_a[0]")) + self._update_working_mode(mode, status) + self._update_state() + except: + self.working_mode = "invalid" + self.status = "invalid" + return self.working_mode + + def get_emergency_stop_sts(self): + st = self.eval_int("esStatus()") + if (st== 1): return "active" + if (st== 2): return "activated" + return "off" + + def get_safety_fault_signal(self): + fault = self.eval_bool("safetyFault(s)") + #if (fault): + # return get_str() + return fault + + #Motion control + def stop(self): + self.evaluate("stopMove()") + + def resume(self): + self.evaluate("restartMove()") + + def reset_motion(self, joint=None, timeout=None): + #TODO: in new robot robot.resetMotion() is freezing controller + #self.evaluate("resetMotion()" if (joint is None) else ("resetMotion(" + joint + ")")) + if joint is None: + self.execute('reset', timeout=timeout) + else: + self.execute('reset', str(joint), timeout=timeout) + + def is_empty(self): + self.empty = self.eval_bool("isEmpty()") + self._update_state() + return self.empty + + def is_settled(self): + self.settled = self.eval_bool("isSettled()") + self._update_state() + return self.settled + + def get_move_id(self): + return self.eval_int("getMoveId()") + + def set_move_id(self, id): + return self.evaluate("setMoveId(" + str(id) + " )") + + def get_joint_forces(self): + try: + self.evaluate("getJointForce(tcp_a)") + self.joint_forces = self.get_float_arr(6) + return self.joint_forces + except: + self.joint_forces = None + raise + + def movej(self, joint_or_point, tool=None, desc=None, sync=False): + if desc is None: desc = self.default_desc + if tool is None: tool = self.tool + #If joint_or_point is a list assumes ir is a point + if not is_string(joint_or_point): + robot.set_pnt(joint_or_point , "tcp_p") + joint_or_point = "tcp_p" + + #TODO: in new robot movel and movej is freezing controller + #ret = self.eval_int("movej(" + joint_or_point + ", " + tool + ", " + desc +")") + ret = int(self.execute('movej',joint_or_point, tool, desc)) + + if sync: + self.wait_end_of_move() + return ret + + def movel(self, point, tool=None, desc=None, sync=False): + if desc is None: desc = self.default_desc + if tool is None: tool = self.tool + if not is_string(point): + robot.set_pnt(point , "tcp_p") + point = "tcp_p" + #TODO: in new robot movel and movej is freezing controller + #ret = self.eval_int("movel(" + point + ", " + tool + ", " + desc +")") + ret = int(self.execute('movel',point, tool, desc)) + + if sync: + self.wait_end_of_move() + return ret + + def movec(self, point_interm, point_target, tool=None, desc=None, sync=False): + if desc is None: desc = self.default_desc + if tool is None: tool = self.tool + + #TODO: in new robot movel and movej is freezing controller + #ret = self.eval_int("movec(" + point_interm + ", " + point_target + ", " + tool + ", " + desc +")") + ret = int(self.execute('movec', point_interm, point_target, tool, desc)) + + if sync: + self.wait_end_of_move() + return ret + + def wait_end_of_move(self): + time.sleep(0.05) + self.update() + #time.sleep(0.01) + #self.waitCacheChange(-1) + self.waitReady(-1) + #self.waitState(State.Ready, -1) + + + #Tool - synchronized as can freeze communication + """ + def open_tool(self, tool=None, timeout=3000): + #This function can timeout since it synchronizes move. Checking state before otherwise it can freeze the communication. + self.waitState(State.Ready, -1) + if tool is None: tool = self.tool + return self.evaluate("open(" + tool + " )", timeout=timeout) + + def close_tool(self, tool=None, timeout=3000): + #This function can timeout since it synchronizes move. Checking state before otherwise it can freeze the communication. + self.waitState(State.Ready, -1) + if tool is None: tool = self.tool + return self.evaluate("close(" + tool + " )", timeout=timeout) + """ + #Tool - Not synchronized calls: atention to open/close only when state is Ready + def open_tool(self, tool=None): + if tool is None: tool = self.tool + self.evaluate(tool + ".gripper=true") + self.tool_open = True + + def close_tool(self, tool=None): + if tool is None: tool = self.tool + self.evaluate(tool + ".gripper=false") + self.tool_open = False + + def is_tool_open(self, tool=None): + if tool is None: tool = self.tool + self.tool_open = robot.eval_bool(tool + ".gripper") + return self.tool_open + + + #Arm position + def herej(self): + return self.eval_jnt("herej()") + + def distance_t(self, trsf1, trsf2): + return self.eval_float("distance(" + trsf1 + ", " + trsf2 + ")") + + def distance_p(self, pnt1, pnt2): + return self.eval_float("distance(" + pnt1 + ", " + pnt2 + ")") + + def compose(self, pnt, frame = None, trsf = "tcp_t"): + if frame is None: frame = self.frame + return self.eval_pnt("compose(" + pnt + ", " + frame + ", " + trsf + ")") + + def here(self, tool=None, frame=None): + if tool is None: tool = self.tool + if frame is None: frame = self.frame + return self.eval_pnt("here(" + tool + ", " + frame + ")") + + def joint_to_point(self, tool=None, frame=None, joint="tcp_j"): + if tool is None: tool = self.tool + if frame is None: frame = self.frame + return self.eval_pnt("jointToPoint(" + tool + ", " + frame + ", " + joint +")") + + def point_to_joint(self, tool=None, initial_joint="tcp_j", point="tcp_p"): + if tool is None: tool = self.tool + if self.eval_bool("pointToJoint(" + tool + ", " + initial_joint + ", " + point +", j)"): + return self.get_jnt() + + def position(self, point, frame=None): + if frame is None: frame = self.frame + return self.eval_trf("position(" + point + ", " + frame + ")") + + #Profile + def get_profile(self): + return self.execute('get_profile', timeout=2000) + + def set_profile(self, name, pwd = ""): + self.execute('set_profile', str(name), str(pwd), timeout=5000) + + + #Task control + def task_create(self, program, *args, **kwargs): + program = str(program) + priority = 10 if (kwargs is None) or (not kwargs.has_key("priority")) or (kwargs["priority"] is None) else kwargs["priority"] + name = str(program if (kwargs is None) or (not kwargs.has_key("name")) else kwargs["name"]) + + if self.get_task_status(name)[0] != -1: + raise Exception("Task already exists: " + name) + + if priority<1 or priority > 100: + raise Exception("Invalid priority: " + str(priority)) + + cmd = program + '(' + for i in range(len(args)): + val = args[i] + if type(val) == bool: + if val == True: val = "true" + elif val == False: val = "false" + cmd += str(val) + (',' if (i<(len(args)-1)) else '') + cmd+=')' + + #TODO: in new robot exec taskCreate is freezing controller + #REMOVE if bug is fixed + self.execute('task_create',name, str(priority), program, *args) + #self.evaluate('taskCreate "' + name + '", ' + str(priority) + ', ' + cmd) + + + + def task_suspend(self, name): + self.evaluate('taskSuspend("' + str(name)+ '")') + + #waits until the task is ready for restart + def task_resume(self, name): + self.evaluate('taskResume("' + str(name)+ '",0)', timeout = 2000) + + #waits for the task to be actually killed + def task_kill(self, name): + #self.evaluate('taskKill("' + str(name)+ '")', timeout = 5000) + self.execute('kill', str(name), timeout=5000) + + def get_task_status(self, name): + if self.isSimulated(): + return (-1,"Stopped") + code = self.eval_int('taskStatus("' + str(name)+ '")') + #TODO: String assignments in $exec causes application to freeze + #status = self + if code== -1: status = "Stopped" + elif code== 0: status = "Paused" + elif code== 1: status = "Running" + #else: status = self.execute('get_help', code) + else: status = "Error" + return (code,status) + + def get_tasks_status(self, *pars): + ret = self.execute("task_sts", *pars) + ret = ret[0:len(pars)] + for i in range(len(ret)): + try: + ret[i] = int(ret[i]) + except: + ret[i] = None + return ret + + def _update_state(self): + #self.setState(State.Busy if self.status=="move" else State.Ready) + if self.state==State.Offline: + print "Communication resumed" + if self.reset or (self.state==State.Offline): + self.check_task() + if self.current_task is not None: + print "Ongoing task: " + self.current_task + if (not self.settled) or (self.current_task is not None): self.setState(State.Busy) + elif not self.empty: self.setState(State.Paused) + else: self.setState(State.Ready) + + def doUpdate(self): + try: + start = time.time() + cur_task = self.current_task #Can change in background so cache it + if self.isSimulated(): + sts = [1, 1,"1", 100, "1", "1", 0, 0, \ + 0, 0, 0, 0, 0, 0, \ + 0, 0, 0, 0, 0, 0, \ + ] + else: + sts = self.execute("get_status", cur_task) + self._update_working_mode(int(sts[0]), int(sts[1])) + self.powered = sts[2] == "1" + self.speed = int(sts[3]) + self.empty = sts[4] == "1" + self.settled = sts[5] == "1" + + #TODO: add tool open + if cur_task is not None: + if int(sts[6]) < 0: + log("Task "+ str(cur_task) + " finished with code: " + str(sts[7]), False) + try: + self.current_task, self.current_task_ret = None, int(sts[7]) + except: + self.current_task, self.current_task_ret = None, None + for i in range(6): + self.joint_pos[i] = float(sts[8 + i]) + for i in range(6): + self.cartesian_pos[i] = float(sts[14 + i]) + + ev_index = 20 #7 + ev = sts[ev_index] if len(sts)>ev_index else "" + if len(ev.strip()) >0: + self.getLogger().info(ev) + self.on_event(ev) + + self._update_state() + self.reset = False + self.setCache({"powered": self.powered, + "speed": self.speed, + "empty": self.empty, + "settled": self.settled, + "task": cur_task, + "mode": self.working_mode, + "status": self.status, + "open": self.tool_open, + "pos": self.get_current_point_cached() if self.state==State.Ready else None #TODO: make it calculated in robot by polling funtion + }, None) + + if self.cartesian_motors_enabled: + for m in self.cartesian_motors: + m.readback.update() + + if self.joint_motors_enabled: + for m in self.joint_motors: + m.readback.update() + + except: + if self.state != State.Offline: + print >> sys.stderr, "Update error: " + str(sys.exc_info()[1]) + self.setState(State.Offline) + + #Cartesian space + """ + def get_cartesian_pos(self): + self.assert_tool() + return self.eval_pnt("jointToPoint(" + self.tool + ", " + self.frame + ", herej())") + #self.set_jnt(self.herej()) + #return self.joint_to_point(tool, frame) + """ + + + #TODO: in new robot robot.evaluate("tcp_p=jointToPoint(tSuna, world, herej())") is freezing controller (cannot call herej directly) + #We can consider atomic because tcp_j is only accessed in comTcp + def get_cartesian_pos(self, tool=None, frame=None): + if tool is None: + self.assert_tool() + tool = self.tool + if frame is None: + frame = self.frame + #Do not work + #self.evaluate("tcp_j=herej(); tcp_p=jointToPoint(" + tool + ", " + frame + ", tcp_j)") + #return self.get_pnt() + a = self.execute('get_pos', tool, frame) + ret = [] + for i in range(6): ret.append(float(a[i])) + return ret + + + def get_flange_pos(self, frame=None): + return get_cartesian_pos(FLANGE, frame) + + + def get_cartesian_destination(self, tool=None, frame=None): + if tool is None: + self.assert_tool() + tool = self.tool + if frame is None: + frame = self.frame + return self.here(tool, frame) + + def get_distance_to_pnt(self, name): + #self.here(self.tool, self.frame) #??? + self.set_pnt(self.get_cartesian_pos() ) + return self.distance_p("tcp_p", name) + + def is_in_point(self, p, tolerance = None): #Tolerance in mm + if (tolerance is None) and p in self.known_points: + #If checking a known point with default tolerance, updates the position cache + return p in self.get_current_points() + tolerance = self.default_tolerance if tolerance == None else tolerance + d = self.get_distance_to_pnt(p) + if d<0: + raise Exception ("Error calculating distance to " + p + " : " + str(d)) + return d<tolerance + + def get_distance_to_pnts(self, *pars): + ret = self.execute("dist_pnt", *pars) + ret = ret[0:len(pars)] + for i in range(len(ret)): + try: + ret[i] = float(ret[i]) + except: + ret[i] = None + return ret + + def is_in_points(self, *pars, **kwargs): #Tolerance in mm + tolerance = self.default_tolerance if (kwargs is None) or (not kwargs.has_key("tolerance")) or (kwargs["tolerance"] is None) else kwargs["tolerance"] + ret = self.get_distance_to_pnts(*pars) + for i in range(len(ret)): + if ret[i]<0: + ret[i] = None + else: + ret[i] = ret[i]<tolerance + if (tolerance == self.default_tolerance) and (set(self.known_points).issubset(set(pars))): #Only update cache if tolerance is default + current_points = [] + for i in range(len(ret)): + if ret[i] == True: + current_points.append(self.known_points[i]) + self.current_points = current_points + return ret + + def assert_in_point(self, p, tolerance = None): #Tolerance in mm + if not self.is_in_point(p, tolerance): + raise Exception ("Not in position " + p) + + + #Cartesian peudo-motors + def set_motors_enabled(self, value): + if value !=self.cartesian_motors_enabled: + self.cartesian_motors_enabled = value + if value: + for i in range(6): + self.cartesian_motors.append(RobotCartesianMotor(self, i)) + add_device(self.cartesian_motors[i], True) + self.cartesian_destination = self.get_cartesian_destination() + else: + for m in self.cartesian_motors: + remove_device(m) + self.cartesian_motors = [] + self.cartesian_destination = None + else: + if value: + self.cartesian_destination = self.get_cartesian_destination() + for m in self.cartesian_motors: + m.initialize() + + + #Cartesian peudo-motors + def set_joint_motors_enabled(self, value): + if value !=self.joint_motors_enabled: + self.joint_motors_enabled = value + if value: + for i in range(6): + self.joint_motors.append(RobotJointMotor(self, i)) + add_device(self.joint_motors[i], True) + else: + for m in self.joint_motors: + remove_device(m) + self.joint_motors = [] + else: + if value: + for m in self.joint_motors: + m.initialize() + #Alignment + def align(self, point = None, frame = None, desc = None): + if frame is None: frame = self.frame + self.assert_tool() + if desc is None: desc = self.default_desc + if point is None: + self.set_pnt(self.get_cartesian_pos() ) + else: + self.set_pnt(point) + np=self.eval_trf('align(tcp_p.trsf, ' + frame + '.trsf)') + self.set_pnt(np) + self.movej("tcp_p", self.tool, desc) + #TODO: The first command is not executed (but receive a move id) + self.movej("tcp_p", self.tool, desc, sync = True) + return np + + #High-level, exclusive motion task. + def start_task(self, program, *args, **kwargs): + tasks = [t for t in self.high_level_tasks] + if (self.current_task is not None) and (not self.current_task in tasks): + tasks.append(self.current_task) + if not program in tasks: + tasks.append(program) + #for task in tasks: + # if self.get_task_status(task)[0]>=0: + # raise Exception("Ongoing high-level task: " + task) + ts = self.get_tasks_status(*tasks) + for i in range(len(ts)): + if ts[i] > 0: + raise Exception("Ongoing high-level task: " + tasks[i]) + + self.clear_task_ret() + + for i in range(self.task_start_retries): + self.task_create(program, *args, **kwargs) + (code, status) = self.get_task_status(program) + if code>=0: break + if i < self.task_start_retries-1: + ret = self.get_task_ret(False) + if ret == 0: #Did't start + log("Retrying starting : " + str(program) + str(args) + " - status: " + str(status) + " (" + str(code) + ")", False) + print "Retrying starting : " + str(program) + str(args) + " - status: " + str(status) + " (" + str(code) + ")" + else : + print "Retrying aborted : " + str(program) + str(args) + " - ret: " + str(ret) + " - status: " + str(status) + " (" + str(code) + ")" + break + else: + log("Failed starting : " + str(program) + str(args), False) + print "Failed starting : " + str(program) + str(args) + if self.exception_on_task_start_failure: + raise Exception("Cannot start task: " + program + str(args)) + + log("Task started: " + str(program) + str(args) + " - status: " + str(status) + " (" + str(code) + ")", False) + self.current_task, self.current_task_ret = program, None + self.update() + #self._update_state() + + #TODO: remove + if self.current_task is None: + log("Task finished in first polling : " + str(program) , False) + print "Task finished in first polling : " + str(program) + + return code + + def stop_task(self): + tasks = [t for t in self.high_level_tasks] + if (self.current_task is not None) and (not self.current_task in tasks): + tasks.append(self.current_task) + for task in tasks: + #if self.get_task_status(task)[0]>=0: + self.task_kill(task) + self.reset_motion() + + def get_task_ret(self, cached = True): + return self.current_task_ret if cached else self.eval_int("tcp_ret") + + def clear_task_ret(self): + return self.evaluate("tcp_ret=0") + + def get_task(self): + return self.current_task + + def check_task(self): + if self.current_task is None: + for task in self.high_level_tasks: + if self.get_task_status(task)[0]>=0: + self.current_task, self.current_task_ret = task, None + log("Task detected: " + str(self.current_task), False) + return self.current_task + + def wait_task_finished (self, polling = None): + cur_polling = self.polling + if polling is not None: + self.polling = polling + try: + while self.get_task() != None: + time.sleep(self.polling_interval) + finally: + if polling is not None: + self.polling = cur_polling + ret = self.get_task_ret() + return ret + + def assert_no_task(self): + task = self.check_task() + if task != None: + raise Exception("Ongoing task: " + task) + + def on_event(self,ev): + pass + + def on_change_working_mode(self, working_mode): + pass + \ No newline at end of file diff --git a/script/devices/SmartMagnet.py b/script/devices/SmartMagnet.py new file mode 100644 index 0000000..0736fdf --- /dev/null +++ b/script/devices/SmartMagnet.py @@ -0,0 +1,129 @@ +class SmartMagnet(DeviceBase): + def __init__(self, name): + #DeviceBase.__init__(self, name, get_context().pluginManager.getDynamicClass("SmartMagnetConfig")()) + DeviceBase.__init__(self, name, DeviceConfig({ + "holdingCurrent":0.0, + "restingCurrent":0.0, + "mountCurrent":0.0, + "unmountCurrent":0.0, + "reverseCurrent":0.0, + "reverseTime":0.0, + })) + + def doInitialize(self): + super(SmartMagnet, self).doInitialize() + self.get_current() + + def set_current(self, current): + self.setCache(current, None) + smc_current.write(current) + + def get_current(self): + cur = smc_current.read() + self.setCache(cur, None) + return cur + + def get_current_rb(self): + self.assert_status() + return smc_current_rb.read() + + def get_status(self): + return smc_magnet_status.read() + + def assert_status(self): + pass + #if self.get_status() == False: + # raise Exception("Smart Magnet is in faulty status.") + + def is_mounted(self): + self.assert_status() + m1 = smc_mounted_1.read() + m2 = smc_mounted_2.read() + if m2==m1: + raise Exception("Smart Magnet has invalid detection.") + return m2 + + def set_supress(self, value): + smc_sup_det.write(value) + + def get_supress(self): + return smc_sup_det.read() + + def check_mounted(self, idle_time =1.0, timeout = -1, interval = 0.01): + self.assert_status() + start = time.time() + last = None + while True: + try: + det = self.is_mounted() + except: + det = None + if det != last: + settling_timestamp = time.time() + last = det + else: + if det is not None: + if (time.time()-settling_timestamp > idle_time): + return det + if timeout >= 0: + if (time.time() - start) > timeout: + raise Exception("Timeout waiting for Smart Magnet detection.") + time.sleep(interval) + + + def doUpdate(self): + try: + if self.get_supress(): + self.setState(State.Paused) + elif self.is_mounted(): + self.setState(State.Busy) + else: + self.setState(State.Ready) + except: + self.setState(State.Fault) + + def set_holding_current(self): + self.set_current(self.config.getFieldValue("holdingCurrent")) + + def set_resting_current(self): + self.set_current(self.config.getFieldValue("restingCurrent")) + + def set_mount_current(self): + self.set_current(self.config.getFieldValue("mountCurrent")) + + def set_unmount_current(self): + self.set_current(self.config.getFieldValue("unmountCurrent")) + + def set_reverse_current(self): + self.set_current(self.config.getFieldValue("reverseCurrent")) + + def set_default_current(self): + if self.is_mounted(): + self.set_holding_current() + else: + self.set_resting_current() + + def is_resting_current(self): + is_resting = 2.0 > abs( float(self.get_current()) - float(self.config.getFieldValue("restingCurrent")) ) + return is_resting + + + def apply_reverse(self): + reverse_wait = float(self.config.getFieldValue("reverseTime")) + if reverse_wait >0: + self.set_reverse_current() + time.sleep(reverse_wait) + + #Setting resting current to better detect sample + def apply_resting(self): + if not self.is_resting_current(): + self.set_resting_current() + + + + +add_device(SmartMagnet("smart_magnet"), force = True) + +smart_magnet.polling = 1000 + +smart_magnet.set_default_current() diff --git a/script/devices/Wago.py b/script/devices/Wago.py new file mode 100644 index 0000000..4aae8be --- /dev/null +++ b/script/devices/Wago.py @@ -0,0 +1,175 @@ +LED_LEVEL_ROOM_TEMPERATURE = 0.4 +LED_LEVEL_LN2 = 1.0 + + +################################################################################################### +# Leds +################################################################################################### + +def set_led_level(level): + level = max(min(float(level),100.0),0.0) + set_setting("led_level", level) + led_ctrl_1.write(led_ctrl_1.config.maxValue * level / 100.0) + led_ctrl_2.write(led_ctrl_2.config.maxValue * level / 100.0) + led_ctrl_3.write(led_ctrl_3.config.maxValue * level / 100.0) + +def get_led_level(): + level = get_setting("led_level") + return float(0 if level is None else level) + +def set_led_state(value): + """ + Turn leds on and off + """ + if value: + set_led_level(100.0) + else: + set_led_level(0.0) + +def get_led_state(): + """ + Returns led state (on/off) + """ + return led_ctrl_1.read() > 0 + +#TODO: The range should be set automatically reading LN2 sensor. +def set_led_range(room_temp = True): + """ + Led range should be limitted in room temperature + """ + max_val = LED_LEVEL_ROOM_TEMPERATURE if room_temp else LED_LEVEL_LN2 + led_ctrl_1.config.maxValue = max_val + led_ctrl_1.config.save() + led_ctrl_2.config.maxValue = max_val + led_ctrl_2.config.save() + led_ctrl_3.config.maxValue = max_val + led_ctrl_3.config.save() + led_ctrl_1.initialize() + led_ctrl_2.initialize() + led_ctrl_3.initialize() + if led_ctrl_1.read() > max_val: + led_ctrl_1.write(max_val) + if led_ctrl_2.read() > max_val: + led_ctrl_2.write(max_val) + if led_ctrl_3.read() > max_val: + led_ctrl_3.write(max_val) + set_led_level(get_led_level()) + + +def is_led_room_temp(): + return led_ctrl_1.config.maxValue <= LED_LEVEL_ROOM_TEMPERATURE + + + + +################################################################################################### +# Safety release +################################################################################################### + +def release_local(): + """ + Release local safety + """ + release_local_safety.write(False) + time.sleep(0.01) + release_local_safety.write(True) + time.sleep(0.01) + release_local_safety.write(False) + +def release_psys(): + """ + Release psys safety + """ + if is_manual_mode(): + raise Exception ("Cannot release PSYS in manual mode") + release_psys_safety.write(False) + time.sleep(0.01) + release_psys_safety.write(True) + time.sleep(0.01) + release_psys_safety.write(False) + + + +################################################################################################### +# Drier +################################################################################################### +MAX_HEATER_TIME = 120000 + +def set_air_stream(state): + """ + """ + valve_1.write(state) + valve_2.write(not state) + + +set_heater_chrono = None + +def monitor_heater_time(): + time.sleep(0.5) + try: + while get_heater(): + if set_heater_chrono.isTimeout(MAX_HEATER_TIME): + set_heater(False) + log("Heater timeout expired: turned off", False) + return + time.sleep(0.1) + except: + print sys.exc_info() + + +def get_heater(): + """ + """ + return gripper_dryer.read() + + +def set_heater(state): + """ + """ + global set_heater_chrono + if get_heater() != state: + gripper_dryer.write(state) + if state: + set_heater_chrono = Chrono() + fork(monitor_heater_time) + + +def get_air_stream(): + """ + """ + return valve_1.read() + + + +def set_pin_cleaner(state): + """ + """ + valve_7.write(state) + valve_8.write(not state) + + + +def get_pin_cleaner(): + """ + """ + return valve_7.read() + + +monitor_pin_cleaner_task = None + +def monitor_pin_cleaner(timeout): + global monitor_pin_cleaner_task + this = monitor_pin_cleaner_task + time.sleep(timeout) + if this == monitor_pin_cleaner_task: + set_pin_cleaner(False) + + +DEFAULT_PIN_CLEANER_TIMEOUT = 5.0 +def start_pin_cleaner(timeout=DEFAULT_PIN_CLEANER_TIMEOUT): + global monitor_pin_cleaner_task + set_pin_cleaner(True) + set_pin_cleaner_chrono = Chrono() + monitor_pin_cleaner_task = fork((monitor_pin_cleaner,(timeout,),))[0] + + diff --git a/script/hexiposi_positon.scd b/script/hexiposi_positon.scd new file mode 100644 index 0000000..471fb41 --- /dev/null +++ b/script/hexiposi_positon.scd @@ -0,0 +1,11 @@ +[ + [ [ true, "hexiposi_position", "Device", 1, 1, null ] ], + [ [ "1", null, null, null, null, null, null, null ], + [ "2", null, null, null, null, null, null, null ], + [ "3", null, null, null, null, null, null, null ], + [ "4", null, null, null, null, null, null, null ], + [ "5", null, null, null, null, null, null, null ] ], + [ [ ] ], + [ [ "", 1000, 100 ], + [ "", "" ] ] +] \ No newline at end of file diff --git a/script/imgproc/CameraCalibration.py b/script/imgproc/CameraCalibration.py new file mode 100644 index 0000000..a89510d --- /dev/null +++ b/script/imgproc/CameraCalibration.py @@ -0,0 +1,156 @@ +import ch.psi.pshell.device.Camera as Camera +import ch.psi.pshell.imaging.RendererMode as RendererMode +import ch.psi.pshell.imaging.Calibration as Calibration +from ch.psi.pshell.imaging.Overlays import * +import ch.psi.pshell.imaging.Pen as Pen +import ch.psi.utils.swing.SwingUtils as SwingUtils +import javax.swing.SwingUtilities as SwingUtilities +#from swingutils.threads.swing import callSwing +#SIMULATION = ch.psi.pshell.imaging.FileSource +""" +img.camera.setColorMode(Camera.ColorMode.Mono) +img.camera.setDataType(Camera.DataType.UInt8) +img.camera.setGrabMode(Camera.GrabMode.Continuous) +img.camera.setTriggerMode(Camera.TriggerMode.Fixed_Rate) +img.camera.setExposure(50.00) +img.camera.setAcquirePeriod(200.00) +img.camera.setGain(0.0) +img.config.rotationCrop=True +""" + +MOVE_HEXIPOSI = not is_manual_mode() +ROTATION_OFFSET = 180.0 + +if MOVE_HEXIPOSI: + release_safety() #enable_motion() + +sensor_width,sensor_height = img.camera.getSensorSize() +img.camera.setROI(0, 0,sensor_width, sensor_height) +img.config.rotation=0 +img.config.roiX,img.config.roiY, img.config.roiWidth,img.config.roiHeight =0,0,-1,-1 +img.config.setCalibration(None) +img.camera.stop() +img.camera.start() + + +p = show_panel(img) +dlg = SwingUtilities.getWindowAncestor(p) +dlg.setSize(800,800) +frm=SwingUtils.getFrame(p) +dlg.setLocationRelativeTo(frm) + +p.setMode(RendererMode.Fit) +ov_text = Text(Pen(java.awt.Color.GREEN.darker()), "", java.awt.Font("Verdana", java.awt.Font.PLAIN, 24), java.awt.Point(20,20)) +ov_text.setFixed(True) +p.addOverlay(ov_text) + + +try: + #Find image center and Prosilica ROI + ov_text.update("Click on the center of the Dewar...") + p.refresh() + dc = p.waitClick(60000) + print dc + width, height = min(dc.x, sensor_width-dc.x)*2, min(dc.y, sensor_height-dc.y)*2 + width, height = width - width%16, height - height%16 + width, height = min(width,1000), min(height,1000) + print width, height + roi_x = int(dc.x- width/2) + roi_y = int(dc.y- height/2) + roi_w = int(width) + roi_h = int(height) + set_setting("roi_x", roi_x) + set_setting("roi_y", roi_y) + set_setting("roi_w", roi_w) + set_setting("roi_h", roi_h) + img.camera.setROI(roi_x, roi_y, width, height) +except: + img.camera.setROI(int(get_setting("roi_x")), int(get_setting("roi_y")), int(get_setting("roi_w")), int(get_setting("roi_h"))) +finally: + img.camera.stop() + img.camera.start() + + +#Configure source +CC4 = (-129.9, -150) +CD5 = (129.9, -150) +CA5 = (-129.9, 150) +CF4 = (129.9, 150) + +DX = 259.8 +DY = 300.0 + +ROI_X = 470.0 +ROI_Y = 470.0 + +def rotate(x,y, degrees): + rotation = math.radians(degrees) + rw, rh = img.getImage().getWidth(), img.getImage().getHeight() + ox, oy = x - (rw / 2), y - (rh / 2) + x = ox * math.cos(rotation) - oy * math.sin(rotation) + rw / 2; + y = oy * math.cos(rotation) + ox * math.sin(rotation) + rh / 2; + return x,y + + +set_led_state(True) +try: + if MOVE_HEXIPOSI: set_hexiposi("C") + ov_text.update("Click on the center of C4 (19) position...") + p.refresh() + pc4 = p.waitClick(60000) + print pc4 + if MOVE_HEXIPOSI: set_hexiposi("D") + ov_text.update("Click on the center of D5 (13) position...") + p.refresh() + pd5 = p.waitClick(60000) + print pd5 + if MOVE_HEXIPOSI: set_hexiposi("F") + ov_text.update("Click on the center of F4 (04) position...") + p.refresh() + pf4 = p.waitClick(60000) + print pf4 + if MOVE_HEXIPOSI: set_hexiposi("A") + ov_text.update("Click on the center of A5 (28) position...") + p.refresh() + pa5 = p.waitClick(60000) + print pa5 + + + vc1x, vc1y, vc2x, vc2y = (pc4.x + pd5.x )/2.0, (pc4.y + pd5.y )/2.0, (pa5.x + pf4.x )/2.0, (pa5.y + pf4.y )/2.0 + hc1x, hc1y, hc2x, hc2y = (pc4.x + pa5.x )/2.0, (pc4.y + pa5.y )/2.0, (pd5.x + pf4.x )/2.0, (pd5.y + pf4.y )/2.0 + cx, cy = (vc1x + vc2x)/2, (hc1y + hc2y)/2 + + a1 = math.degrees(math.atan((cx-vc1x)/(vc1y-cy))) + a2 = math.degrees(math.atan((cx-vc2x)/(vc2y-cy))) + a = (a1+a2)/2 + + + dy = math.hypot(vc2y - vc1y, vc2x - vc1x) + dx = math.hypot(hc2x - hc1x, hc2y - hc1y) + print dy, dx, cx, cy + sx, sy = DX/dx, DY/dy + + + #Rotating center of puck + rcx, rcy = rotate(cx, cy, -a) + + + + roi_w, roi_h = int(ROI_X / sx), int(ROI_Y / sy) + roi_x, roi_y = int(rcx-roi_w/2), int(rcy-roi_h/2) + + print a, sx, sy, roi_w, roi_h + + img.config.rotation=-a + ROTATION_OFFSET + img.config.roiX,img.config.roiY, img.config.roiWidth,img.config.roiHeight = roi_x, roi_y, roi_w, roi_h + img.config.setCalibration(Calibration(sx, sy, -roi_w/2, -roi_h/2)) + img.config.save() + + set_return ("Success calibrating the camera") + +finally: + set_led_state(False) + p.removeOverlay(ov_text) + img.refresh() + + \ No newline at end of file diff --git a/script/imgproc/CoverDetection.py b/script/imgproc/CoverDetection.py new file mode 100644 index 0000000..904837a --- /dev/null +++ b/script/imgproc/CoverDetection.py @@ -0,0 +1,98 @@ +################################################################################################### +# Procedure to detect the cover orientation +################################################################################################### +assert_imaging_enabled() + + +#Parameters +FRAMES_INTEGRATION = 3 +STEP_SIZE = 2 +POSITION_NAMES = [ 'A','B','C','D', 'E', 'F'] + +#POSITION_ANGLES = [ 330, 30, 90, 150, 210, 270 ] +POSITION_ANGLES = [ 0, 60, 120, 180, 240, 300 ] +POSITION_TOLERANCE = 3 +MINIMUM_CONFIDENCE = 3 +DEBUG = cover_detection_debug +#REFERENCE_IMG = "ref2" +REFERENCE_IMG = "ref1" +BORDER = 7 + +#Load reference image +ref = load_image(str("{images}/cover/" + REFERENCE_IMG + ".png") , title="Line") + +#Pre-process camera image +#ip = load_image("{images}/cover/Cover_000" + str(index) + ".png", title="Img") +ip = integrate_frames(FRAMES_INTEGRATION) +ip = grayscale(ip, True) +smooth(ip) +#bandpass_filter(ip, 30, 1000) +edges(ip) +auto_threshold(ip, method = "MaxEntropy") +#binary_erode(ip, True) +#binary_dilate(ip, True) +ip.getProcessor().erode(1, 255) +cx,cy = int(ip.width/2), int(ip.height/2) +ip = sub_image(ip, cx-ref.width/2, cy-ref.height/2, ref.width, ref.height) + +if BORDER>0: + sip = sub_image(ip, BORDER,BORDER, ref.width-2*BORDER, ref.height-2*BORDER) + ip = pad_image(sip, BORDER, BORDER, BORDER, BORDER, fill_color=Color.WHITE) + +#Show ROI of pre-processed image +if DEBUG: + image_panel = show_panel(ip.bufferedImage) + + +#Calculate correlation between image and reference, rotating the reference from 0 to 360 +import ch.psi.pshell.imaging.Utils.integrateVertically as integrateVertically +ydata = [] +xdata = range (0,360,STEP_SIZE) +for i in xdata: + r = ref.duplicate() + r.getProcessor().setBackgroundValue(0.0) + r.getProcessor().rotate(float(i)) + op = op_fft(r, ip, "correlate") + bi = op.getBufferedImage() + p = integrateVertically(bi) + ydata.append(sum(p)) + + +#Calculate angle of the highest correlation, and confidence level +peaks = estimate_peak_indexes(ydata, xdata, (min(ydata) + max(ydata))/2, 25.0) +peaks_x = map(lambda x:xdata[x], peaks) +peaks_y = map(lambda x:ydata[x], peaks) +if len(peaks_x) > 1: + #remoce close peaks between 350 deg and 10 deg + if ((peaks_x[0]<10) and (peaks_x[1]>350)) or ((peaks_x[1]<10) and (peaks_x[0]>350)): + peaks.pop(1) + peaks_x.pop(1) + peaks_y.pop(1) + + +confidence = None if len(peaks_x)<2 else int(((float(peaks_y[0])/peaks_y[1])-1) * 1000) +angle = (None if len(peaks_x)==0 else peaks_x[0]) + +#From angle and confidence level estimate hexiposi position +position = None +if angle is not None: + for i in range(len(POSITION_NAMES)): + if abs(POSITION_ANGLES[i] - angle) <= POSITION_TOLERANCE: + position = POSITION_NAMES[i] + +#Plot the correlations values agains angle +if DEBUG: + plot(ydata, xdata=xdata) + +#Output results +if DEBUG: + print "Peaks", peaks + print "Peak indexes: " + str(peaks_x) + print "Peak values: " + str(peaks_y) + print "Angle: " , angle + print "Position: " , position + print "Confidence: " , confidence + +#Set return value +set_return ([position, angle, confidence]) + diff --git a/script/imgproc/CoverDetectionCalibration.py b/script/imgproc/CoverDetectionCalibration.py new file mode 100644 index 0000000..36f6a54 --- /dev/null +++ b/script/imgproc/CoverDetectionCalibration.py @@ -0,0 +1,40 @@ +#Parameters +FRAMES_INTEGRATION = 3 +MINIMUM_CONFIDENCE = 10 +DEBUG = cover_detection_debug +REFERENCE_IMG = "ref1" +ERODE_ITERATIONS = 2 + +#Load reference image +SIZE = [128,128] +BORDER = 7 + +hexiposi.move("A") +#Pre-process camera image +#ip = load_image("{images}/cover/Cover_000" + str(index) + ".png", title="Img") +ip = integrate_frames(FRAMES_INTEGRATION) +ip = grayscale(ip, True) +smooth(ip) +#bandpass_filter(ip, 30, 1000) +edges(ip) + +auto_threshold(ip, method = "MaxEntropy") +#binary_dilate(ip, True, 2) +for i in range(ERODE_ITERATIONS): + ip.getProcessor().erode(1, 255) + +cx,cy = int(ip.width/2), int(ip.height/2) +ip = sub_image(ip, cx-SIZE[0]/2, cy-SIZE[1]/2, SIZE[0], SIZE[1]) + +invert(ip) +ip = grayscale(ip, True) +#smooth(ip) + +if BORDER > 0: + sip = sub_image(ip, BORDER,BORDER, SIZE[0]-2*BORDER, SIZE[1]-2*BORDER) + ip = pad_image(sip, BORDER, BORDER, BORDER, BORDER) +if DEBUG: + image_panel = show_panel(ip.bufferedImage) + +save_image(ip, str("{images}/cover/" + REFERENCE_IMG + ".png") ,"png") + \ No newline at end of file diff --git a/script/imgproc/CreateMask.py b/script/imgproc/CreateMask.py new file mode 100644 index 0000000..98b3498 --- /dev/null +++ b/script/imgproc/CreateMask.py @@ -0,0 +1,29 @@ + +mask_img = new_image(img.getOutput().getWidth(), img.getOutput().getHeight(), image_type="byte", title = "mask_img", fill_color = Color.BLACK) + +mask_radius = 14 +mask_points = [] + + +def to_img_coords(absolute_coords): + return [img.getCalibration().convertToImageX(absolute_coords[0]), img.getCalibration().convertToImageY(absolute_coords[1])] + +for p in _puck_list: + mask_points.append(to_img_coords(p.led_mini)) + mask_points.append(to_img_coords(p.led_uni)) + + + +i = mask_img.getBufferedImage() +for p in mask_points: + #i.setRGB(p[0], p[1], 0xFFFFFF) + for x in range (p[0]-mask_radius, p[0]+mask_radius): + for y in range (p[1]-mask_radius, p[1]+mask_radius): + if math.hypot(x-p[0], y-p[1]) <= mask_radius: + i.setRGB(x,y, 0xFFFFFF) + + +mask_img = load_image(i) +#show_panel( mask_img.getBufferedImage()) + +set_return(mask_img) \ No newline at end of file diff --git a/script/imgproc/LedDetectionFilter.py b/script/imgproc/LedDetectionFilter.py new file mode 100644 index 0000000..a4818d8 --- /dev/null +++ b/script/imgproc/LedDetectionFilter.py @@ -0,0 +1,102 @@ +################################################################################################### +# Example of using ImageJ functionalities through ijutils. +################################################################################################### + +import datetime +from ijutils import * +import java.awt.Color as Color + +import ch.psi.pshell.imaging.Filter as Filter +from ch.psi.pshell.imaging.Overlays import * +import ch.psi.pshell.imaging.Pen as Pen + + +integration_count = 10 +integration_continuous = False +integration_partial = False +frames = [] +roi = get_roi() + + +color_roi = Color(0, 128, 0) + +renderer = show_panel(img) +renderer.clearOverlays() +ov_roi_shape = Ellipse(Pen(color_roi, 0,), java.awt.Point(roi[0], roi[1]), java.awt.Dimension(roi[2], roi[3])) +ov_roi_bound = Rect(Pen(color_roi, 0, Pen.LineStyle.dotted), java.awt.Point(roi[0], roi[1]), java.awt.Dimension(roi[2], roi[3])) +ov_roi_center = Crosshairs(Pen(color_roi, 0), java.awt.Point(roi_center[0],roi_center[1]), java.awt.Dimension(15,15)) + +renderer.addOverlays([ov_roi_shape, ov_roi_bound,ov_roi_center]) + + + + +last_ret = (None, None) +def detect_led(ip): + roi = get_roi() + global roi_center, roi_radius, integration_count, integration_continuous, integration_partial, frames + global count , last_ret + aux = sub_image(ip, roi[0], roi[1], roi[2], roi[3]) + grayscale(aux) + #gaussian_blur(aux) + if (integration_count>1): + frames.append(aux) + if len(frames) >integration_count: + del frames[0] + if not integration_continuous: + if (len(frames)< integration_count): + if last_ret[1] is not None: invert(last_ret[1]) + return last_ret + if (not integration_partial) and len(frames) <integration_count: + return last_ret + aux = integrate(frames) + + #aux = get_channel(aux, "blue") + invert(aux) + #subtract_background(aux) + #Tested ok: Huang, Mean, MaxEntropy, Percentile, Triangle, Yen + auto_threshold(aux, method = "Percentile") + #binary_open(aux) + (results,output) = analyse_particles(aux, 250,1000, + fill_holes = True, exclude_edges = False, print_table=False, + output_image = "outlines", minCirc = 0.3 + , maxCirc = 1.0) + r=results + + + points = "" + npoints = 0 + for row in range (r.counter): + if in_roi(r.getValue("XM",row), r.getValue("YM",row)): + points = points + " (" + str(int(r.getValue("XM", row))+roi[0]) + ", " + str(int(r.getValue("YM", row))+roi[1]) + ")" + npoints = npoints + 1 + print str(npoints) + " - " + points + + last_ret = (results,output) + if not integration_continuous: + frames = [] + + #if npoints!=12: + # save_image(op_image(aux, output,"xor", in_place=False), "{images}/" + str(datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))+".png", "png") + #return (results,aux) + return (results,output) + + + + +ip = None + +class MyFilter(Filter): + def process(self, image, data): + global roi_center, roi_radius, ip + ip = load_image(image) + (results,output) = detect_led(ip) + if output is not None: + invert(output) + output = pad_image(output, roi[0], 0,roi[1], 0) + op_image(ip, output, "xor") + return ip.getBufferedImage() + +#Setting the filter to a source +img.setFilter(MyFilter()) + diff --git a/script/imgproc/LedDetectionProc.py b/script/imgproc/LedDetectionProc.py new file mode 100644 index 0000000..c01673d --- /dev/null +++ b/script/imgproc/LedDetectionProc.py @@ -0,0 +1,106 @@ +################################################################################################### +# Procedure to detect the puck light spots. +################################################################################################### +assert_imaging_enabled() + + +COVER_PRESENT = True +ROOM_TEMP = is_room_temp() +USE_MASK = True + +if get_exec_pars().source == CommandSource.ui: + PLOT = None + RENDERER = None + TEXT = None + +if COVER_PRESENT: + cover_position = hexiposi.readback.take() + if (cover_position is None) or (cover_position == "Unknown"): + raise Exception("Unknown cover position") + else: + block_id = cover_position.upper()[0] +else: + block_id = None +print "Block id: ", block_id + + + +number_frames = 5 if ROOM_TEMP else 10 +number_backgrounds = 5 if ROOM_TEMP else 5 +minimum_size = 78 # r = 5 # 150 +maximum_size = 750 # r = 15 #1500 +min_circ = 0.2 + +threshold_method = "MaxEntropy" if ROOM_TEMP else "Default" #Apparently good for LN2: Default, Intermodes, IsoData, Otsu +threshold_method,threshold_range = "Manual", (0, 215) + +exclude_edges = True +led_latency = 0.5 #0.1 + + + +set_led_state(False) +time.sleep(led_latency) +img.waitNext(2000) + +background = average_frames(number_backgrounds) +#background = integrate_frames(number_backgrounds) + +set_led_state(True) +time.sleep(led_latency) +img.waitNext(2000) +image = average_frames(number_frames) +#image = integrate_frames(number_frames) + +set_led_state(False) + +op_image(image, background, "subtract", float_result=True, in_place=True) +image=grayscale(image) + +if RENDERER is not None and RENDERER.isShowing(): + RENDERER.setImage(None, image.getBufferedImage(), None) +else: + RENDERER = show_panel(image.getBufferedImage()) +RENDERER.clearOverlays() + +if USE_MASK: + mask_img = run("imgproc/CreateMask") + #mask_img=grayscale(mask_img) + #show_panel( mask_img.getBufferedImage()) + op_image(image, mask_img, "and", float_result=False, in_place=True) + RENDERER.setImage(None, image.getBufferedImage(), None) + +invert(image) +if threshold_method == "Manual": + threshold(image, threshold_range[0], threshold_range[1]) +else: + auto_threshold(image, method = threshold_method) #Tested ok: MaxEntropy, Triangle, Yen +(r,output) = analyse_particles(image, minimum_size,maximum_size, + fill_holes = True, exclude_edges = exclude_edges, print_table=False, + output_image = "outlines", minCirc = min_circ + , maxCirc = 1.0) + +points = [] +for row in range (r.counter): + if in_roi(r.getValue("XM",row), r.getValue("YM",row)): + x, y = int(r.getValue("XM", row)), int(r.getValue("YM", row)) + cx, cy = img.getCalibration().convertToAbsoluteX(x), img.getCalibration().convertToAbsoluteY(y) + points.append([cx,cy]) + if RENDERER is not None: + RENDERER.addOverlay(Crosshairs(Pen(java.awt.Color.MAGENTA), java.awt.Point(x,y), java.awt.Dimension(15,15))) + + +clear_detection(block_id) +detect_pucks(points, block_id) +if PLOT is not None: + plot_base_plate(points, p=PLOT) + +ret = get_puck_detection_dict(block_id) + + +if TEXT is not None: + TEXT.setText(str(ret)) + + +set_return(ret) + diff --git a/script/imgproc/Utils.py b/script/imgproc/Utils.py new file mode 100644 index 0000000..addc508 --- /dev/null +++ b/script/imgproc/Utils.py @@ -0,0 +1,113 @@ + +################################################################################################### +# Image processing utilities +################################################################################################### + + +from ijutils import * +from ch.psi.pshell.imaging.Overlays import * +import ch.psi.pshell.imaging.Pen as Pen +import java.awt.Rectangle as Rectangle + +def get_img_cover_pos(): + [position, angle, confidence] = run("imgproc/CoverDetection") + return position + +def assert_img_in_cover_pos(pos = None): + if pos==None: + pos = hexiposi.take() + elif type(pos) is int: + pos = chr( ord('A') + (pos-1)) + elif is_string(pos): + pos = pos.upper() + img_segment = get_img_cover_pos() + if img_segment != pos: + raise Exception ("Image detection of cover does not match position: " + str(img_segment)) + + +def in_roi(x,y): + global roi_center, roi_radius, roi_border + return math.hypot(x-roi_center[0], y-roi_center[1]) < (roi_radius-roi_border) + + +def integrate(ips): + roi = get_roi() + aux = None + for i in range(len(ips)): + if i==0: + aux = new_image(roi[2], roi[3], image_type="float", title = "sum", fill_color = None) + op_image(aux, ips[i], "add", float_result=True, in_place=True) + return aux + +def average (ips): + aux = integrate(ips) + op_const(aux, "divide", len(ips), in_place=True) + return aux + +def grab_frames(samples): + frames = [] + for i in range(samples): + aux = get_image() + frames.append(aux) + return frames + +def average_frames(samples = 1): + return average(grab_frames(samples)) + +def integrate_frames(samples = 1): + return integrate(grab_frames(samples)) + + +roi_center = (600, 600) #(800, 600) +roi_radius = 600 +roi_border = 30 + +def get_roi(): + #roi_center = (img.output.width/2, img.output.height/2) + #roi_radius = min(roi_center[0], roi_center[1]) + #return (roi_center[0] - roi_radius, roi_center[1] - roi_radius, 2* roi_radius, 2*roi_radius) + global roi_center, roi_radius + roi_center = (img.output.width/2, img.output.height/2) + roi_radius = min(roi_center[0], roi_center[1]) + return (0,0,img.output.width, img.output.height) + +def get_image(): + roi = get_roi() + #ip = load_image(img.output) + #ret = ip if (roi is None) else sub_image(ip, roi[0], roi[1], roi[2], roi[3]) + #grayscale(ret, do_scaling=True) + + ret = load_image(Utils.grayscale(img.output, Rectangle(roi[0], roi[1], roi[2], roi[3]) if (roi is not None) else None)) + return ret + +#def detect_pucks(ip): +# """ +# """ +# aux = grayscale(ip, in_place=False) +# threshold(aux,0,50) +# binary_fill_holes(aux) +# return analyse_particles(aux, 10000,50000, +# fill_holes = False, exclude_edges = True,print_table=True, +# output_image = "outlines", minCirc = 0.4, maxCirc = 1.0) +# +#def detect_samples(ip): +# """ +# """ +# aux = grayscale(ip, in_place=False) +# invert(aux) +# subtract_background(aux) +# auto_threshold(aux) +# binary_open(aux) +# return analyse_particles(aux, 250,1000, +# fill_holes = False, exclude_edges = True,print_table=True, + + +r,g,b = [0]*256,[0]*256,[0]*256 +b[0]=0xFF +b[1]=0xFF ; g[1] = 0x80; r[1] = 0x80 +outline_lut1 = (r,g,b) + +r,g,b = [0]*256,[0]*256,[0]*256 +g[0]=0x80;r[0]=0x80; +g[1]=0xFF ; r[1] = 0x80; b[1] = 0x80 +outline_lut2 = (r,g,b) diff --git a/script/local.groovy b/script/local.groovy new file mode 100644 index 0000000..6cd1527 --- /dev/null +++ b/script/local.groovy @@ -0,0 +1,3 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Deployment specific global definitions - executed after startup.groovy +/////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/script/local.js b/script/local.js new file mode 100644 index 0000000..17db863 --- /dev/null +++ b/script/local.js @@ -0,0 +1,4 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Deployment specific global definitions - executed after startup.js +/////////////////////////////////////////////////////////////////////////////////////////////////// + diff --git a/script/local.py b/script/local.py new file mode 100644 index 0000000..92e9ba8 --- /dev/null +++ b/script/local.py @@ -0,0 +1,438 @@ +################################################################################################### +# Deployment specific global definitions - executed after startup.py +################################################################################################### +import traceback +from ch.psi.pshell.serial import TcpDevice +from ch.psi.pshell.modbus import ModbusTCP +import ch.psi.mxsc.Controller as Controller +import ch.psi.pshell.core.Nameable as Nameable +import ch.psi.utils.Chrono as Chrono +import ch.psi.mxsc.Controller as Controller + + + +run("setup/Layout") +run("data/reports") + + + + +################################################################################################### +# Configuration +################################################################################################### + +IMAGING_ENABLED_PREFERENCE = "imaging_enabled" +PUCK_TYPES_PREFERENCE = "puck_types" +BARCODE_READER_SCAN_PUCKS = "barcode_reader_scan_pucks" +ROOM_TEMPERATURE_ENABLED_PREFERENCE = "room_temperature_enabled" +BEAMLINE_STATUS_ENABLED_PREFERENCE = "beamline_status_enabled" +VALVE_CONTROL_ENABLED_PREFERENCE = "valve_control" + +def is_imaging_enabled(): + setting = get_setting(IMAGING_ENABLED_PREFERENCE) + return not (str(setting).lower() == 'false') + +def set_imaging_enabled(value): + set_setting(IMAGING_ENABLED_PREFERENCE, (True if value else False) ) + +def assert_imaging_enabled(): + if is_imaging_enabled() == False: + raise Exception ("Imaging is disabled") + +#"unipuck", "minispine" or "mixed" +def set_puck_types(value): + set_setting(PUCK_TYPES_PREFERENCE, True if value else False ) + +def get_puck_types(): + setting = get_setting(PUCK_TYPES_PREFERENCE) + if setting == "unipuck" or setting == "minispine": + return setting + return "mixed" + + +def is_barcode_reader_scan_pucks(): + setting = get_setting(BARCODE_READER_SCAN_PUCKS) + return False if setting is None else setting.lower() == "true" + +def set_barcode_reader_scan_pucks(value): + set_setting(BARCODE_READER_SCAN_PUCKS, True if value else False ) + +def is_valve_controlled(): + setting = get_setting(VALVE_CONTROL_ENABLED_PREFERENCE) + return False if setting is None else setting.lower() == "true" + +def set_valve_controlled(value): + set_setting(VALVE_CONTROL_ENABLED_PREFERENCE, True if value else False ) + +def reset_mounted_sample_position(): + set_setting("mounted_sample_position", None) + + +def get_puck_barcode_reader(): + if is_barcode_reader_scan_pucks(): + return barcode_reader + else: + return barcode_reader_puck + +#In order to apply current config +set_imaging_enabled(is_imaging_enabled()) +set_puck_types(get_puck_types()) +set_barcode_reader_scan_pucks(is_barcode_reader_scan_pucks()) +set_valve_controlled(is_valve_controlled()) + + +force_dry_mount_count = get_setting("force_dry_mount_count") +if force_dry_mount_count is None: + set_setting("force_dry_mount_count", 0) + +force_dry_timeout = get_setting("force_dry_timeout") +if force_dry_timeout is None: + set_setting("force_dry_timeout", 0) + + +cold_position_timeout = get_setting("cold_position_timeout") +if cold_position_timeout is None: + set_setting("cold_position_timeout", 0) + + +def is_room_temperature_enabled(): + setting = get_setting(ROOM_TEMPERATURE_ENABLED_PREFERENCE) + return str(setting).lower() == 'true' + +set_setting(ROOM_TEMPERATURE_ENABLED_PREFERENCE, is_room_temperature_enabled()) + + +def is_beamline_status_enabled(): + setting = get_setting(BEAMLINE_STATUS_ENABLED_PREFERENCE) + return str(setting).lower() == 'true' + +set_setting(BEAMLINE_STATUS_ENABLED_PREFERENCE, is_beamline_status_enabled()) + +################################################################################################### +# Scripted devices and pseudo-devices +################################################################################################### + +for script in ["devices/RobotSC", "devices/Wago", "devices/BarcodeReader", "devices/LaserDistance", \ + "devices/LedCtrl", "devices/SmartMagnet", "devices/HexiPosi", "devices/Gonio"]: + try: + run(script) + except: + print >> sys.stderr, traceback.format_exc() + +#if is_imaging_enabled(): +if get_device("img") is not None: + add_device(img.getContrast(), force = True) + add_device(img.getCamera(), force = True) + + +################################################################################################### +# Utility modules +################################################################################################### + +run("data/samples") +run("data/pucks") +run("motion/tools") +run("motion/mount") +run("motion/unmount") +run("motion/get_dewar") +run("motion/put_dewar") +run("motion/get_gonio") +run("motion/put_gonio") +run("motion/move_dewar") +run("motion/move_gonio") +run("motion/move_heater") +run("motion/move_home") +run("motion/move_park") +run("motion/move_cold") +run("motion/move_scanner") +run("motion/move_aux") +run("motion/get_aux") +run("motion/put_aux") +run("motion/dry") +run("motion/trash") +run("motion/homing_hexiposi") +run("motion/calibrate_tool") +run("motion/scan_pin") +run("motion/robot_recover") +run("motion/recover") +run("tools/Math") +if is_imaging_enabled(): + run("imgproc/Utils") + +def system_check(robot_move=True): + if not air_pressure_ok.read(): + raise Exception("Air pressure is not ok") + if not n2_pressure_ok.read(): + raise Exception("N2 pressure is not ok") + hexiposi.assert_in_known_position() + + if robot_move: + if not feedback_local_safety.read(): + raise Exception("Local safety not released") + auto = not is_manual_mode() + if auto: + if not feedback_psys_safety.read(): + raise Exception("Psys safety not released") + if not guiding_tool_park.read(): + raise Exception("Guiding tool not parked") + +def system_check_msg(): + try: + system_check(True) + return "Ok" + except: + return sys.exc_info()[1] + +def get_puck_dev(segment, puck): + if type(segment) is int: + segment = chr( ord('A') + (segment-1)) + + return Controller.getInstance().getPuck(str(segment).upper() + str(puck)) + +def get_puck_elect_detection(segment, puck): + return str(get_puck_dev(segment, puck).detection) + +def get_puck_img_detection(segment, puck): + return str(Controller.getInstance().getPuck(str(segment).upper() + str(puck)).imageDetection) + +def assert_puck_detected(segment, puck): + if (segment == AUX_SEGMENT) and (puck == 1): + return + if get_puck_elect_detection(segment, puck) != "Present": + raise Exception ("Puck " + str(segment).upper() + str(puck) + " not present") + + +def start_puck_detection(): + run("tools/RestartPuckDetection") + +def check_puck_detection(): + return run("tools/CheckPuckDetection") + +def stop_puck_detection(): + run("tools/StopPuckDetection") + + + +def get_detected_pucks(): + ret = [] + for i in range(30): + p = BasePlate.getPucks()[i] + if (str(p.getDetection()) == "Present"): + ret.append(str(p.getName())) + return ret + +def get_pucks_info(): + ret = [] + for i in range(30): + p = BasePlate.getPucks()[i] + name = p.getName() + det = str(p.getDetection()) + barcode = "" if p.getId() is None else p.getId() + + ret.append({"puckAddress": name, "puckState": det, "puckBarcode":barcode}) + return json.dumps(ret) + + +################################################################################################### +# Device initialization +################################################################################################### + +try: + set_heater(False) + set_air_stream(False) + set_pin_cleaner(False) +except: + print >> sys.stderr, traceback.format_exc() + + +try: + release_local_safety.write(False) + release_psys_safety.write(False) +except: + print >> sys.stderr, traceback.format_exc() + +try: + hexiposi.set_offset(16.5) + hexiposi.polling=500 +except: + print >> sys.stderr, traceback.format_exc() + +try: + robot.setPolling(DEFAULT_ROBOT_POLLING) + robot.set_tool(TOOL_DEFAULT) + robot.set_frame(FRAME_DEFAULT) + robot.set_motors_enabled(True) + robot.set_joint_motors_enabled(True) +except: + print >> sys.stderr, traceback.format_exc() + +if is_imaging_enabled(): + try: + import ch.psi.pshell.device.Camera as Camera + #img.camera.setColorMode(Camera.ColorMode.Mono) + #img.camera.setDataType(Camera.DataType.UInt8) + img.camera.setGrabMode(Camera.GrabMode.Continuous) + img.camera.setTriggerMode(Camera.TriggerMode.Fixed_Rate) + img.camera.setExposure(25.00) + img.camera.setAcquirePeriod(200.00) + img.camera.setGain(0.0) + #img.camera.setROI(200, 0,1200,1200) + """ + img.camera.setROI(300, 200,1000,1000) + img.config.rotation=17 + img.config.rotationCrop=True + img.config.roiX,img.config.roiY, img.config.roiWidth,img.config.roiHeight = 50,50,900,900 + """ + img.camera.setROI(int(get_setting("roi_x")), int(get_setting("roi_y")), int(get_setting("roi_w")), int(get_setting("roi_h"))) + + img.camera.stop() + img.camera.start() + except: + print >> sys.stderr, traceback.format_exc() + +#TODO: The Smart Magnet keeps moving sample if detecting is enabled +# Detection keeps disabled unless during moount/unmount +try: + smart_magnet.set_supress(True) +except: + print >> sys.stderr, traceback.format_exc() + +#gripper_cam.paused = True +################################################################################################### +# Device monitoring +################################################################################################### + +DEWAR_LEVEL_RT = 5.0 +is_room_temperature = False + +def is_room_temp(): + return is_room_temperature + + +class DewarLevelListener (DeviceListener): + def onValueChanged(self, device, value, former): + global is_room_temperature + if value is not None: + is_room_temperature = value <= DEWAR_LEVEL_RT +dewar_level_listener = DewarLevelListener() + +for l in dewar_level.listeners: + #if isinstance(l, DewarLevelListener): #Class changes... + if Nameable.getShortClassName(l.getClass()) == "DewarLevelListener": + dewar_level.removeListener(l) + +dewar_level.addListener(dewar_level_listener) +dewar_level_listener.onValueChanged(dewar_level, dewar_level.take(), None) + + + + +class HexiposiListener (DeviceListener): + def onValueChanging(self, device, value, former): + robot.assert_cleared() + +hexiposi_listener = HexiposiListener() +hexiposi.addListener(hexiposi_listener) + +################################################################################################### +# Global variables & application state +################################################################################################### + + +context = get_context() + +cover_detection_debug = False + +in_mount_position = False + + +def assert_mount_position(): + print "Source: ", get_exec_pars().source + if not in_mount_position and get_exec_pars().source == CommandSource.server : + raise Exception("Not in mount position") + + +def is_puck_loading(): + return robot.state == State.Ready and robot.take()["pos"] == 'pPark' and \ + feedback_psys_safety.take() == False and \ + not guiding_tool_park.read() + +def set_pin_offset(val): + if abs(val) >5: + raise Exception("Invalid pin offset: " + str(val)) + try: + set_setting("pin_offset",float(val)) + except: + log("Error setting pin offset: " + str(sys.exc_info()[1]), False) + +def get_pin_offset(): + try: + ret = float(get_setting("pin_offset")) + if abs(ret) >5: + raise Exception("Invalid configured pin offset: " + str(ret)) + return ret + except: + log("Error getting pin offset: " + str(sys.exc_info()[1]), False) + return 0.0 + + +def set_pin_angle_offset(val): + if (abs(val) > 180.0) or (abs(val) < -180.0): + raise Exception("Invalid pin angle offset: " + str(val)) + try: + set_setting("pin_angle_offset",float(val)) + except: + log("Error setting pin angle offset: " + str(sys.exc_info()[1]), False) + +def get_pin_angle_offset(): + try: + ret = float(get_setting("pin_angle_offset")) + if (abs(ret) > 180.0) or (abs(ret) < -180.0): + raise Exception("Invalid configured pin angle offset: " + str(ret)) + return ret + except: + log("Error getting pin angle offset: " + str(sys.exc_info()[1]), False) + return 0.0 + +def is_force_dry(): + try: + dry_mount_counter = int(get_setting("dry_mount_counter")) + except: + dry_mount_counter = 0 + + try: + dry_timespan = time.time() - float( get_setting("dry_timestamp")) + except: + dry_timespan = 3600 + + try: + force_dry_mount_count = int(get_setting("force_dry_mount_count")) + if force_dry_mount_count>0 and dry_mount_counter>=force_dry_mount_count: + return True + except: + pass + + try: + force_dry_timeout = float(get_setting("force_dry_timeout")) + if force_dry_timeout>0 and dry_timespan>=force_dry_timeout: + return True + except: + pass + return False + +def assert_detector_safe(): + if detector_cleared.read() == False: + raise Exception ("Detector not in safe position: move it back") + + +def onPuckLoadingChange(puck_loading): + set_led_state(puck_loading) + #pass + +update() +add_device(Controller.getInstance().basePlate, True) +restore_samples_info() + + +print "Initialization complete" + \ No newline at end of file diff --git a/script/motion/calibrate_tool.py b/script/motion/calibrate_tool.py new file mode 100644 index 0000000..5357ef0 --- /dev/null +++ b/script/motion/calibrate_tool.py @@ -0,0 +1,32 @@ +def calibrate_tool(): + """ + """ + print "calibrate_tool" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + (detected, dm) = move_scanner() + + if detected: + print "Pin detected, trashing..." + trash() + (detected, dm) = move_scanner() + if detected: + raise Exception("Cannot trash pin") + + robot.open_tool() + robot.get_calibration_tool() + + run("calibration/ToolCalibration3") + + robot.put_calibration_tool() + + robot.save_program() diff --git a/script/motion/dry.py b/script/motion/dry.py new file mode 100644 index 0000000..a34a3ed --- /dev/null +++ b/script/motion/dry.py @@ -0,0 +1,58 @@ +DEFAULT_DRY_HEAT_TIME = 40.0 +DEFAULT_DRY_SPEED = 0.3 +DEFAULT_DRY_WAIT_COLD = 40.0 + +def dry(heat_time=None, speed=None, wait_cold = None): + """ + heat_time (float): in seconds + speed (float): % of nominal speed + wait_cold(float): if negative, move to dewar after drying + Else move to cold and wait (in seconds) before returning. + """ + print "dry" + + if heat_time is None: + heat_time = DEFAULT_DRY_HEAT_TIME + + if speed is None: + speed = DEFAULT_DRY_SPEED + + if wait_cold is None: + wait_cold = DEFAULT_DRY_WAIT_COLD + + if robot.simulated: + time.sleep(10.0) + return + + #Initial chec + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + set_status("Drying") + + #Enabling + enable_motion() + + try: + set_heater(True) + robot.move_heater(speed, False) + time.sleep(heat_time) + robot.move_heater(speed, True) + set_air_stream(True) + robot.move_heater(speed, False) + finally: + set_heater(False) + set_air_stream(False) + + + set_setting("dry_mount_counter", 0) + set_setting("dry_timestamp",time.time()) + + if wait_cold >=0 : + robot.move_cold() + time.sleep(wait_cold) + else: + robot.move_park() \ No newline at end of file diff --git a/script/motion/get_aux.py b/script/motion/get_aux.py new file mode 100644 index 0000000..67bfc7d --- /dev/null +++ b/script/motion/get_aux.py @@ -0,0 +1,21 @@ +def get_aux(sample): + """ + """ + print "get_aux: ",sample + + #Initial checks + assert_valid_sample(sample) + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + hexiposi.assert_homed() + + #Enabling + enable_motion() + + if not robot.is_aux(): + robot.move_aux() + + robot.get_aux(sample) diff --git a/script/motion/get_dewar.py b/script/motion/get_dewar.py new file mode 100644 index 0000000..d0559fc --- /dev/null +++ b/script/motion/get_dewar.py @@ -0,0 +1,30 @@ +def get_dewar(segment, puck, sample, force=False): + """ + """ + print "get_dewar: ", segment, puck, sample, force + + #Initial checks + assert_valid_address(segment, puck, sample) + assert_puck_detected(segment, puck) + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + hexiposi.assert_homed() + #location = robot.get_current_point() + + + #Enabling + enable_motion() + + set_hexiposi(segment) + + if not force: + visual_check_hexiposi(segment) + + if not robot.is_dewar(): + robot.move_dewar() + + robot.get_dewar(segment, puck, sample) diff --git a/script/motion/get_gonio.py b/script/motion/get_gonio.py new file mode 100644 index 0000000..2123e2a --- /dev/null +++ b/script/motion/get_gonio.py @@ -0,0 +1,20 @@ +def get_gonio(force=False): + """ + """ + print "get_gonio: ", force + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_gonio(): + robot.move_gonio() + + robot.get_gonio() diff --git a/script/motion/homing_hexiposi.py b/script/motion/homing_hexiposi.py new file mode 100644 index 0000000..5c99f9e --- /dev/null +++ b/script/motion/homing_hexiposi.py @@ -0,0 +1,18 @@ +def homing_hexiposi(): + """ + """ + print "homing_hexiposi" + + #Initial checks + robot.assert_no_task() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + #location = robot.get_current_point() + + set_status("Homing hexiposi") + hexiposi.move_home() + hexiposi.waitState(State.Ready,-1) + hexiposi.move_pos(1) + + \ No newline at end of file diff --git a/script/motion/mount.py b/script/motion/mount.py new file mode 100644 index 0000000..ab93351 --- /dev/null +++ b/script/motion/mount.py @@ -0,0 +1,153 @@ +mount_sample_id = None +mount_sample_detected = None + +def mount(segment, puck, sample, force=False, read_dm=False, auto_unmount=False): + """ + """ + global mount_sample_id, mount_sample_detected + print "mount: ", segment, puck, sample, force + + start = time.time() + #time.sleep(2) + is_aux = (segment == AUX_SEGMENT) + + #ZACH + needs_chilling = not is_aux and (not robot.is_cold()) + needs_drying = is_aux and robot.is_cold() + + puck_address = get_puck_address(puck) + if puck_address is None: + puck_obj = get_puck_obj_by_id(puck) + if puck_obj is not None: + puck_address = puck_obj.name + if puck_address is not None: + print "puck address: ", puck_address + segment = puck_address[:1] + puck = int(puck_address[1:]) + #Initial checks + assert_detector_safe() + assert_valid_address(segment, puck, sample) + assert_puck_detected(segment, puck) + + if robot.simulated: + time.sleep(3.0) + mount_sample_detected = True + mount_sample_id = "YYY0001" + update_samples_info_sample_mount(get_puck_name(segment, puck), sample, mount_sample_detected, mount_sample_id) + set_setting("mounted_sample_position", get_sample_name(segment, puck, sample)) + return [mount_sample_detected, mount_sample_id] + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + hexiposi.assert_homed() + assert_mount_position() + do_unmount = False + + try: + #ZACH + if needs_chilling: + robot.move_cold() + time.sleep(30.0) + + if smart_magnet.get_supress() == True: + smart_magnet.set_supress(False) + time.sleep(0.2) + #To better dectect sample + #smart_magnet.apply_reverse() + #smart_magnet.apply_resting() + #time.sleep(0.5) + + sample_det = smart_magnet.check_mounted(idle_time=0.25, timeout = 1.0) + Controller.getInstance().logEvent("Sample Detection", str(sample_det)) + if sample_det == True: + if auto_unmount and (get_setting("mounted_sample_position") is not None): + #auto_unmount set to true so detection remains enabled + sample_det = unmount(force = True, auto_unmount = True) + do_unmount = True + if sample_det == True: + raise Exception("Pin detected on gonio") + + set_status("Mounting: " + str(segment) + str(puck) + str(sample)) + Controller.getInstance().logEvent("Mount Sample", str(segment) + str(puck) + str(sample)) + #location = robot.get_current_point() + + #Enabling. If did unmount then it is already enabled. + if not do_unmount: + enable_motion() + + #ZACH + # a room temp pin is being mounted but the gripper is cold + if needs_drying: + dry(wait_cold=-1) # move to park after dry + + if is_aux: + if not robot.is_aux(): + robot.move_aux() + + robot.get_aux(sample) + + else: + set_hexiposi(segment) + + if not force: + visual_check_hexiposi(segment) + + if not robot.is_dewar(): + robot.move_dewar() + + robot.get_dewar(segment, puck, sample) + + + if read_dm: + barcode_reader.start_read(10.0) + robot.move_scanner() + #time.sleep(1.0) + + robot.move_gonio() + + if read_dm: + mount_sample_id = barcode_reader.get_readout() + print "Datamatrix: " , mount_sample_id + else: + mount_sample_id = None + + + robot.put_gonio() + cleaner_timer = float(get_setting("pin_cleaner_timer")) + if cleaner_timer > 0: + start_pin_cleaner(cleaner_timer) + + + try: + dry_mount_count = int(get_setting("dry_mount_counter")) + except: + dry_mount_count = 0 + set_setting("dry_mount_counter", dry_mount_count+1) + + if is_aux: + robot.move_home() + else: + robot.move_cold() + + mount_sample_detected = smart_magnet.check_mounted(idle_time=0.25, timeout = 1.0) + Controller.getInstance().logEvent("Sample Detection", str(mount_sample_detected)) + update_samples_info_sample_mount(get_puck_name(segment, puck), sample, mount_sample_detected, mount_sample_id) + if mount_sample_detected == False: + raise Exception("No pin detected on gonio") + + + if is_force_dry(): + smart_magnet.set_default_current() + print "Auto dry" + log("Starting auto dry", False) + set_exec_pars(then = "dry()") + + set_setting("mounted_sample_position", get_sample_name(segment, puck, sample)) + return [mount_sample_detected, mount_sample_id] + finally: + smart_magnet.set_default_current() + smart_magnet.set_supress(True) + diff --git a/script/motion/move_aux.py b/script/motion/move_aux.py new file mode 100644 index 0000000..94d6468 --- /dev/null +++ b/script/motion/move_aux.py @@ -0,0 +1,18 @@ +def move_aux(): + """ + """ + print "move_aux" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_aux(): + robot.move_aux() \ No newline at end of file diff --git a/script/motion/move_cold.py b/script/motion/move_cold.py new file mode 100644 index 0000000..89c8920 --- /dev/null +++ b/script/motion/move_cold.py @@ -0,0 +1,22 @@ +def move_cold(): + """ + """ + print "move_cold" + + if robot.simulated: + time.sleep(3.0) + return + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_cold(): + robot.move_cold() \ No newline at end of file diff --git a/script/motion/move_dewar.py b/script/motion/move_dewar.py new file mode 100644 index 0000000..3dfcbfe --- /dev/null +++ b/script/motion/move_dewar.py @@ -0,0 +1,18 @@ +def move_dewar(): + """ + """ + print "move_dewar" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_dewar(): + robot.move_dewar() \ No newline at end of file diff --git a/script/motion/move_gonio.py b/script/motion/move_gonio.py new file mode 100644 index 0000000..82397f9 --- /dev/null +++ b/script/motion/move_gonio.py @@ -0,0 +1,18 @@ +def move_gonio(): + """ + """ + print "move_gonio" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_gonio(): + robot.move_gonio() diff --git a/script/motion/move_heater.py b/script/motion/move_heater.py new file mode 100644 index 0000000..000e500 --- /dev/null +++ b/script/motion/move_heater.py @@ -0,0 +1,18 @@ +def move_heater(): + """ + """ + print "move_heater" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_heater(): + robot.move_heater() diff --git a/script/motion/move_home.py b/script/motion/move_home.py new file mode 100644 index 0000000..30592d6 --- /dev/null +++ b/script/motion/move_home.py @@ -0,0 +1,18 @@ +def move_home(): + """ + """ + print "move_home" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_home(): + robot.move_home() diff --git a/script/motion/move_park.py b/script/motion/move_park.py new file mode 100644 index 0000000..e1715c0 --- /dev/null +++ b/script/motion/move_park.py @@ -0,0 +1,18 @@ +def move_park(): + """ + """ + print "move_park" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + + if not robot.is_park(): + robot.move_park() \ No newline at end of file diff --git a/script/motion/move_scanner.py b/script/motion/move_scanner.py new file mode 100644 index 0000000..b8fe41f --- /dev/null +++ b/script/motion/move_scanner.py @@ -0,0 +1,30 @@ + +def move_scanner(): + """ + """ + print "move_scanner" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + barcode_reader.start_read(10.0) + if not robot.is_scanner(): + robot.move_scanner() + + time.sleep(0.25) + dm = barcode_reader.get_readout() + if dm is None: + detected = is_pin_detected_in_scanner() + else: + detected = True + + print ("Detected: " + str( detected) + " - Datamatrix: " + str(dm)) + + return (detected, dm) diff --git a/script/motion/put_aux.py b/script/motion/put_aux.py new file mode 100644 index 0000000..c41ee81 --- /dev/null +++ b/script/motion/put_aux.py @@ -0,0 +1,21 @@ +def put_aux(sample): + """ + """ + print "put_aux: ",sample + + #Initial checks + assert_valid_sample(sample) + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + hexiposi.assert_homed() + + #Enabling + enable_motion() + + if not robot.is_aux(): + robot.move_aux() + + robot.put_aux(sample) diff --git a/script/motion/put_dewar.py b/script/motion/put_dewar.py new file mode 100644 index 0000000..7d5c74d --- /dev/null +++ b/script/motion/put_dewar.py @@ -0,0 +1,30 @@ +def put_dewar(segment, puck, sample, force=False): + """ + """ + print "put_dewar: ", segment, puck, sample, force + + #Initial checks + assert_valid_address(segment, puck, sample) + assert_puck_detected(segment, puck) + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + hexiposi.assert_homed() + + #Enabling + enable_motion() + + set_hexiposi(segment) + + if not force: + visual_check_hexiposi(segment) + + #location = robot.get_current_point() + + if not robot.is_dewar(): + robot.move_dewar() + + robot.put_dewar(segment, puck, sample) diff --git a/script/motion/put_gonio.py b/script/motion/put_gonio.py new file mode 100644 index 0000000..c23463f --- /dev/null +++ b/script/motion/put_gonio.py @@ -0,0 +1,23 @@ +def put_gonio(force=False): + """ + """ + print "put_gonio: ", force + + + if robot.simulated: + time.sleep(3.0) + return + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + if not robot.is_gonio(): + robot.move_gonio() + robot.put_gonio() diff --git a/script/motion/recover.py b/script/motion/recover.py new file mode 100644 index 0000000..f4e61af --- /dev/null +++ b/script/motion/recover.py @@ -0,0 +1,179 @@ +import org.apache.commons.math3.geometry.euclidean.threed.Segment as Segment +import org.apache.commons.math3.geometry.euclidean.threed.Vector3D as Vector3D +import org.apache.commons.math3.geometry.euclidean.threed.Line as Line3D + +RECOVER_DESC = "mRecovery" +RECOVER_TOOL = TOOL_DEFAULT + +known_segments = [ ("pGonioA", "pGonioG", 10), \ + ("pPark", "pScan", 40), \ + ("pHome", "pPark", 60), \ + ("pScan", "pHeater", 50), \ + ("pHome", "pDewar", 10), \ + ("pHeater", "pHeatB", 10), \ + ("pHome", "pAux", 50), \ + ] + + +def get_robot_position(): + return robot.get_cartesian_pos() + + +def get_dist_to_line(segment): + tolerance = segment[2] + p1, p2 = robot.get_pnt(segment[0]), robot.get_pnt(segment[1]) + p = get_robot_position() + v = Vector3D(p[0], p[1], p[2]) + v1 = Vector3D(p1[0], p1[1], p1[2]) + v2 = Vector3D(p2[0], p2[1], p2[2]) + l = Line3D(v1, v2, 0.01) + return l.distance(v) + +def get_dist_to_segment(segment): + tolerance = segment[2] + p1, p2 = robot.get_pnt(segment[0]), robot.get_pnt(segment[1]) + p = get_robot_position() + v = Vector3D(p[0], p[1], p[2]) + v1 = Vector3D(p1[0], p1[1], p1[2]) + v2 = Vector3D(p2[0], p2[1], p2[2]) + l = Line3D(v1, v2, 0.01) + d = l.distance(v) + + pj = get_projection_at_line(segment) + + d1, d2 = v1.distance(v), v2.distance(v) + dp1, dp2 = v1.distance(pj), v2.distance(pj) + d12 = v1.distance(v2) + + + if (dp1 + dp2) > (d12 + tolerance): + d = max(d,min(d1,d2)) + return d + +def is_on_segment(segment): + tolerance = segment[2] + d = get_dist_to_segment(segment) + + if d > tolerance: + #print "Current robot position " + str(p) + " not on segment " + str(segment) + " - distance=" + str(d) + return False + #print "Current robot position " + str(p) + " on segment " + str(segment) + " - distance=" + str(d) + return True + +def get_projection_at_line(segment): + tolerance = segment[2] + p1, p2 = robot.get_pnt(segment[0]), robot.get_pnt(segment[1]) + p = get_robot_position() + v = Vector3D(p[0], p[1], p[2]) + v1 = Vector3D(p1[0], p1[1], p1[2]) + v2 = Vector3D(p2[0], p2[1], p2[2]) + l = Line3D(v1, v2, 0.01) + a = l.getAbscissa(v) + lv = l.pointAt(a) + return lv + + +def get_current_segment(): + for segment in known_segments: + if is_on_segment(segment): + return segment + return None + +def get_current_distance(): + for segment in known_segments: + if is_on_segment(segment): + return get_dist_to_segment(segment) + return None + + +def move_to_segment(segment): + tolerance = segment[2] + p = get_robot_position() + v = Vector3D(p[0], p[1], p[2]) + lv = get_projection_at_line(segment) + dlv = lv.distance(v) + if dlv> (tolerance + 0.1): + raise Exception( "Error moving from " + str(p) + " to segment - distance=" + str(dlv)) + d = [lv.x, lv.y, lv.z, p[3], p[4], p[5]] + print "Moving from " + str(p) + " to segment " + str(segment) + " - distance=" + str(dlv) + " - dest=" + str(d) + + try: + robot.movel(d, tool=RECOVER_TOOL, desc=RECOVER_DESC, sync=True) + print "Done" + except: + print sys.exc_info()[1] + +#Moves to first point of the segment ehich is safer, unless in the vicinity of the second +def move_to_safest_point(segment, vicinity_tolerance = 100): + d1, d2 = robot.get_distance_to_pnt(segment[0]), robot.get_distance_to_pnt(segment[1]) + #Always moving to primary point + if False : #(d2<=d1) and (d2 <= vicinity_tolerance): + print "Moving to secondary point " + str(segment[1] + " - d1=" + str(d1) + " d2=" + str(d2) ) + robot.movel(segment[1], tool=RECOVER_TOOL, desc=RECOVER_DESC, sync=True) + else: + print "Moving to primary point " + str(segment[0] + " - d1=" + str(d1) + " d2=" + str(d2) ) + robot.movel(segment[0], tool=RECOVER_TOOL, desc=RECOVER_DESC, sync=True) + print "Done" + #print "Recovered to point " + str(robot.get_curjoint_or_pointrent_point()) + +def is_in_dewar(): + z_hom = robot.get_pnt('pHome')[2] + z_cur=get_robot_position()[2] + if z_cur < (z_hom + 30): + return False + d_dwr = robot.get_distance_to_pnt('pDewar') + if d_dwr > 300: + return False + return True + + +def recover(move_park = True): + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + + if robot.get_current_point() is not None: + raise Exception("Robot is in known location") + + set_status("Recovering") + + #Enabling + enable_motion() + + + is_on_known_segment = False + for segment in known_segments: + if is_on_segment(segment): + #try: + # robot.set_monitor_speed(5) + is_on_known_segment = True + move_to_segment(segment) + move_to_safest_point(segment) + location = robot.get_current_point() + if location is None: + raise Exception("Robot didn't reach known point") + print "Success recovered to point: " + str(location) + if move_park: + robot.move_park() + return "Success recovering to park position" + else: + return "Success recovering to point: " + str(location) + #finally: + # robot.set_default_speed() + if not is_on_known_segment: + print ("Robot is not in known segment") + if is_in_dewar(): + robot.robot_recover() + if move_park: + robot.move_park() + return "Success recovering from dewar to park position" + else: + return "Success recovering from dewar" + else: + raise Exception("Robot is not in known segment nor inside the dewar") + + + + + \ No newline at end of file diff --git a/script/motion/robot_recover.py b/script/motion/robot_recover.py new file mode 100644 index 0000000..c25d712 --- /dev/null +++ b/script/motion/robot_recover.py @@ -0,0 +1,16 @@ +def robot_recover(): + """ + """ + print "robot_recover" + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + + set_status("Recovering robot") + + #Enabling + enable_motion() + + robot.robot_recover() diff --git a/script/motion/scan_pin.py b/script/motion/scan_pin.py new file mode 100644 index 0000000..77bb83c --- /dev/null +++ b/script/motion/scan_pin.py @@ -0,0 +1,99 @@ +###TODO: REMOVE ME +def system_check(robot_move=True): + pass + + +def scan_pin(segment, puck, sample, force=False): + pin_name = get_sample_name(segment, puck, sample) + + print "scan pin", pin_name + #Initial checks + assert_valid_address(segment, puck, sample) + #assert_puck_detected(segment, puck) + is_aux = (segment == AUX_SEGMENT) + + if robot.simulated: + time.sleep(0.5) + return "Present" + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + ###robot.assert_in_known_point() + + #Enabling + + set_status("Scanning pin: " + str(pin_name)) + + enable_motion() + + if is_aux: + if not robot.is_aux(): + robot.move_aux() + + robot.get_aux(sample) + else: + # set_hexiposi(segment) + # if not force: + # visual_check_hexiposi(segment) + + if not robot.is_dewar(): + robot.move_dewar() + + robot.get_dewar(segment, puck, sample) + + (detected, dm) = move_scanner() + update_samples_info_sample_scan(get_puck_name(segment, puck), sample, detected, dm) + + if is_aux: + robot.move_aux() + robot.put_aux( sample) + else: + robot.move_dewar() + robot.put_dewar(segment, puck, sample) + ret = "Empty" + if detected: + if (dm is None) or (len(dm.strip())==0): + ret = "Present" + else: + ret = str(dm) + return ret + + +def scan_puck(segment, puck, force=False): + if segment == AUX_SEGMENT: + raise Exception("Cannot scan auxiliary puck") + ret = [] + for i in range(16): + ret.append(scan_pin (segment, puck, i+1, force)) + return ret + + +def scan_gripper(): + print "scan gripper" + #Initial checks + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + + set_status("Scanning gripper") + + enable_motion() + + (detected, dm) = move_scanner() + + robot.move_home() + + ret = "Empty" + if detected: + if (dm is None) or (len(dm.strip())==0): + ret = "Present" + else: + ret = str(dm) + return ret diff --git a/script/motion/tools.py b/script/motion/tools.py new file mode 100644 index 0000000..58bb7fc --- /dev/null +++ b/script/motion/tools.py @@ -0,0 +1,180 @@ +POSITION_TOLERANCE = 50 + + +def is_manual_mode(): + """ + return if operating in local mode. TODO: should be based on IO, not on robot flag. + """ + return robot.working_mode == "manual" + +def release_safety(): + """ + Release sefety signals acording to working mode + """ + if air_pressure_ok.take() != True: + raise Exception("Cannot release safety: air preassure not ok") + if n2_pressure_ok.take() != True: + raise Exception("Cannot release safety: n2 pressure not ok") + auto = not is_manual_mode() + if auto: + if feedback_psys_safety.read() == False: + release_psys() + time.sleep(0.5) + if feedback_psys_safety.read() == False: + raise Exception("Cannot enable power: check doors") + + if feedback_local_safety.read() == False: + release_local() + time.sleep(0.5) + if feedback_local_safety.read() == False: + raise Exception("Cannot enable power: check sample changer emergency stop button") + +def enable_motion(): + """ + Check safety and enable arm power if in remote mode + """ + release_safety() + system_check(robot_move=True) + auto = not is_manual_mode() + if auto: + if not robot.state.isNormal(): + raise Exception("Cannot enable power: robot state is " + str(robot.state)) + robot.enable() + +def set_hexiposi(pos, force = False): + """ + Set the hexiposi position in remote mode, or wait for it to be set in manual mode + """ + robot.assert_cleared() + if force == False: + if hexiposi.position == pos: + return + + if is_manual_mode(): + set_status("Move Hexiposi to position " + str(pos) + " ...") + try: + hexiposi.waitInPosition(pos, -1) + finally: + set_status(None) + else: + hexiposi.move(pos) + +#Can be used if cover has following error (no checking readback) +def _set_hexiposi(pos): + hexiposi.moveAsync(pos) + time.sleep(1.0) + hexiposi.waitReady(-1) + + +def visual_check_hexiposi(segment): + if is_imaging_enabled(): + #if is_manual_mode(): ? + if hexiposi.moved: + #Clearing for image processing + if not robot.is_park(): + print "Moving robot to park to clear camera view..." + robot.move_park() + assert_img_in_cover_pos(segment) + hexiposi.moved = False + + + +def wait_end_of_move(): + robot.update() + while (not robot.settled) or (not robot.empty) or (not robot.isReady()) : + time.sleep(0.01) + + +def move_to_home(): + #robot.reset_motion("jHome") + robot.movej("pHome", robot.tool , DESC_SCAN) + wait_end_of_move() + +def move_to_laser(from_point = "pHome"): + robot.reset_motion() + tool = robot.tool + d = robot.get_distance_to_pnt("pLaser") + if d<0: + raise Exception ("Error calculating distance to laser: " + str(d)) + if d<POSITION_TOLERANCE: + print "FROM LASER" + robot.movel("pLaser", robot.tool, DESC_SCAN, sync = True) + return + d = robot.get_distance_to_pnt("pLaserHome") + if d<0: + raise Exception ("Error calculating distance to laser appro: " + str(d)) + if d<POSITION_TOLERANCE: + print "FROM APPRO" + robot.movel("pLaser", robot.tool, DESC_SCAN, sync = True) + return + + d = robot.get_distance_to_pnt(from_point) + if d<0: + raise Exception ("Error calculating distance to " + from_point + ": " + str(d)) + if d<POSITION_TOLERANCE: + print "FROM " + from_point + robot.movej("pLaserHome", robot.tool, DESC_DEFAULT) + robot.movel("pLaser", robot.tool, DESC_SCAN, sync = True) + return + raise Exception ("Must be in home position to start move to laser") + + +def update_tool(tool=None, x_offset=0.0, y_offset=0.0, z_offset=0.0): + #Updating tool: + t=robot.get_tool_trsf(tool) + t[0]=t[0] - x_offset + t[1]=t[1] - y_offset + t[2]=t[2] - z_offset + robot.set_tool_trsf(t, tool) + print "Updated " + (str(robot.tool) if (tool is None) else tool) + ": " + str(t) + robot.save_program() + + +def assert_valid_address(segment, puck, sample): + if (segment == AUX_SEGMENT) and (puck == 1): + return + if is_string(segment): + segment = ord(segment.upper()) - ord('A') +1 + if (segment<=0) or (segment >6): + raise Exception ("Invalid segment: " + str(segment)) + if (puck<=0) or (puck >5): + raise Exception ("Invalid puck: " + str(puck)) + if (sample<=0) or (sample >16): + raise Exception ("Invalid sample: " + str(sample)) + if get_puck_dev(segment, puck).isDisabled(): + raise Exception ("Puck is disabled") + +def assert_valid_sample(sample): + if (sample<=0) or (sample >16): + raise Exception ("Invalid sample: " + str(sample)) + +def get_puck_name(segment, puck): + try: + assert_valid_address(segment, puck, 1) + if type(segment) is int: + segment = chr( ord('A') + (segment-1)) + elif is_string(segment): + segment = segment.upper() + else: + return None + return segment + str(puck) + except: + return None + +def get_sample_name(segment, puck, sample): + puck_name = get_puck_name(segment, puck) + return None if (puck_name is None) else puck_name + str(sample) + + +def is_pin_detected_in_scanner(): + samples = [] + for i in range(10): + samples.append(laser_distance.read()) + time.sleep(0.05) + av = mean(samples) + for s in samples: + if s<=1: + return False + if abs(s-av) > 0.1: + return False + return True \ No newline at end of file diff --git a/script/motion/trash.py b/script/motion/trash.py new file mode 100644 index 0000000..8b37f2c --- /dev/null +++ b/script/motion/trash.py @@ -0,0 +1,36 @@ +def trash(): + """ + """ + print "trash" + + if robot.simulated: + time.sleep(3.0) + return + + #Initial checks + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + + #Enabling + enable_motion() + + robot.move_heater(to_bottom = False) + robot.move_heater(to_bottom = True) + + try: + for i in range(3): + robot.open_tool() + time.sleep(0.5) + robot.close_tool() + time.sleep(0.5) + finally: + robot.open_tool() + + + robot.move_heater(to_bottom = False) + robot.move_cold() + + \ No newline at end of file diff --git a/script/motion/unmount.py b/script/motion/unmount.py new file mode 100644 index 0000000..6f9ce22 --- /dev/null +++ b/script/motion/unmount.py @@ -0,0 +1,108 @@ +def unmount(segment = None, puck = None, sample = None, force=False, auto_unmount = False): + """ + Returns if sample is detected in the end + """ + print "unmount: ", segment, puck, sample, force + + #ZACH + is_aux = (segment == AUX_SEGMENT) + needs_chilling = not is_aux and (not robot.is_cold()) + needs_drying = is_aux and robot.is_cold() + + if (segment is None) or (puck is None) or (sample is None): + pos = get_setting("mounted_sample_position") + if pos is None: + raise Exception("Mounted sample position is not defined") + segment, puck , sample = pos[0:1], int(pos[1]), int(pos[2:]) + print "Mounted sample position: ", segment, puck , sample + + + #Initial checks + if not auto_unmount: + print "assert detector safe" + assert_detector_safe() + print "assert valid address" + assert_valid_address(segment, puck, sample) + print "asser puck detected" + assert_puck_detected(segment, puck) + + if robot.simulated: + time.sleep(3.0) + update_samples_info_sample_unmount(get_puck_name(segment, puck), sample) + set_setting("mounted_sample_position", None) + return False + + print "assert no task" + robot.assert_no_task() + print "reset motion" + robot.reset_motion() + print "wait ready" + robot.wait_ready() + print "assert cleared" + robot.assert_cleared() + #robot.assert_in_known_point() + print "assert homed" + hexiposi.assert_homed() + print "assert mount pos" + assert_mount_position() + + set_status("Unmounting: " + str(segment) + str(puck) + str(sample)) + Controller.getInstance().logEvent("Unmount Sample", str(segment) + str(puck) + str(sample)) + + try: + if smart_magnet.get_supress() == True: + smart_magnet.set_supress(False) + time.sleep(0.2) + + #smart_magnet.apply_reverse() + #smart_magnet.apply_resting() + + if not force: + sample_det = smart_magnet.check_mounted(idle_time=0.5, timeout = 3.0) + Controller.getInstance().logEvent("Sample Detection", str(sample_det)) + if sample_det == False: + raise Exception("No pin detected on gonio") + + #Enabling + enable_motion() + + if not is_aux: + set_hexiposi(segment) + + if not force: + visual_check_hexiposi(segment) + if needs_chilling: + robot.move_cold() + time.sleep(30.) + else: + if needs_drying: + dry(wait_cold=-1) + #location = robot.get_current_point() + + if not robot.is_gonio(): + robot.move_gonio() + + #smart_magnet.set_unmount_current() + + robot.get_gonio() + + smart_magnet.apply_reverse() + smart_magnet.apply_resting() + mount_sample_detected = smart_magnet.check_mounted(idle_time=0.25, timeout = 1.0) + Controller.getInstance().logEvent("Sample Detection", str(mount_sample_detected)) + + if is_aux: + robot.move_aux() + robot.put_aux( sample) + else: + #TODO: Should check if smart magnet detection is off? + update_samples_info_sample_unmount(get_puck_name(segment, puck), sample) + robot.move_dewar() + robot.put_dewar(segment, puck, sample) + set_setting("mounted_sample_position", None) + + return mount_sample_detected + finally: + if not auto_unmount: + smart_magnet.set_default_current() + smart_magnet.set_supress(True) diff --git a/script/setup/ExposureScan.py b/script/setup/ExposureScan.py new file mode 100644 index 0000000..7a1d438 --- /dev/null +++ b/script/setup/ExposureScan.py @@ -0,0 +1,33 @@ +import java.awt.Rectangle as Rectangle + +import ch.psi.pshell.imaging.Data as Data +class Exposure(Writable): + def write(self,pos): + #cam.setExposure(pos) + img.camera.setExposure(pos) +exposure=Exposure() + + +class Contrast(Readable): + def read(self): + data = img.getData() + #roi = Data(data.getRectSelection(500,300,700,600), False) + #return data.getGradientVariance(False, Rectangle(480,0,600,670)) + return data.getGradientVariance(False, None) +contrast=Contrast() + + +#a= lscan(exposure,img.getContrast(), 0.5, 1.0, 0.01, 0.5) +#a= lscan(exposure,contrast, 0.2, 0.4, 0.01, 0.7) +ret= lscan(exposure,contrast, 10.0, 150.0, 5.0, 0.5) +y, x = ret.getReadable(0), ret.getPositions(0) + +#(n, m, s) = fit(a.getReadable(0), xdata=a.getPositions(0)) +#if m is None: +# m=max(a.getReadable(0)) +m=x[y.index(max(y))] +p=get_plots()[0] +p.addMarker(m, p.AxisId.X, str(m), Color.RED) + +print "Setting exposure = ", m +exposure.write(m) diff --git a/script/setup/Layout.py b/script/setup/Layout.py new file mode 100644 index 0000000..5c584f4 --- /dev/null +++ b/script/setup/Layout.py @@ -0,0 +1,292 @@ +################################################################################################### +#DEFINITIONS +################################################################################################### + +PLATE_SIZE = 480 +BLOCK_ROI_TOLERANCE = 12 #mm +LED_TOLERANCE = 8 #mm Distance between LEDs = 18mm +PUCK_SIZE = 65 + +DET_UNIPUCK = "unipuck" +DET_MINISPINE = "minispine" +DET_ERROR = "error" +DET_EMPTY = "empty" +DET_UNKNOWN = "unknown" + +BLOCKS = ('A', 'B', 'C', 'D', 'E', 'F') + +#Layout table +puck_layout = ( + #Num Elm A0 Index A1 Uni Mini Center Angle Xuni Yuni Xmini=Xc Ymini==Yc + (1 , 'A', 0 , 1, 0.00 , 57.00 , 75.00 , 66.00 , 0.00 , 0.00 , 57.00 , 0.00 , 75.00 ), + (2 , 'A', 0 , 2, 0.00 , 132.00, 150.00, 141.00, 0.00 , 0.00 , 132.00 , 0.00 , 150.00 ), + (3 , 'F', 0 , 5, 19.11, 180.40, 198.40, 189.40, 19.11 , 59.06 , 170.46 , 64.95 , 187.47 ), + (4 , 'F', 0 , 4, 40.89, 180.40, 198.40, 189.40, 40.89 , 118.09 , 136.38 , 129.87 , 149.98 ), + (5 , 'F', 0 , 3, 30.00, 111.90, 129.90, 120.90, 30.00 , 55.95 , 96.91 , 64.95 , 112.50 ), + (6 , 'F', 60 , 1, 0.00 , 57.00 , 75.00 , 66.00 , 60.00 , 49.36 , 28.50 , 64.95 , 37.50 ), + (7 , 'F', 60 , 2, 0.00 , 132.00, 150.00, 141.00, 60.00 , 114.32 , 66.00 , 129.90 , 75.00 ), + (8 , 'E', 60 , 5, 19.11, 180.40, 198.40, 189.40, 79.11 , 177.15 , 34.08 , 194.83 , 37.48 ), + (9 , 'E', 60 , 4, 40.89, 180.40, 198.40, 189.40, 100.89, 177.15 , -34.08 , 194.83 , -37.48 ), + (10, 'E', 60 , 3, 30.00, 111.90, 129.90, 120.90, 90.00 , 111.90 , 0.00 , 129.90 , 0.00 ), + (11, 'E', 120, 1, 0.00 , 57.00 , 75.00 , 66.00 , 120.00, 49.36 , -28.50 , 64.95 , -37.50 ), + (12, 'E', 120, 2, 0.00 , 132.00, 150.00, 141.00, 120.00, 114.32 , -66.00 , 129.90 , -75.00 ), + (13, 'D', 120, 5, 19.11, 180.40, 198.40, 189.40, 139.11, 118.09 , -136.38, 129.87 , -149.98), + (14, 'D', 120, 4, 40.89, 180.40, 198.40, 189.40, 160.89, 59.06 , -170.46, 64.95 , -187.47), + (15, 'D', 120, 3, 30.00, 111.90, 129.90, 120.90, 150.00, 55.95 , -96.91 , 64.95 , -112.50), + (16, 'D', 180, 1, 0.00 , 57.00 , 75.00 , 66.00 , 180.00, 0.00 , -57.00 , 0.00 , -75.00 ), + (17, 'D', 180, 2, 0.00 , 132.00, 150.00, 141.00, 180.00, 0.00 , -132.00, 0.00 , -150.00), + (18, 'C', 180, 5, 19.11, 180.40, 198.40, 189.40, 199.11, -59.06 , -170.46, -64.95 , -187.47), + (19, 'C', 180, 4, 40.89, 180.40, 198.40, 189.40, 220.89, -118.09, -136.38, -129.87, -149.98), + (20, 'C', 180, 3, 30.00, 111.90, 129.90, 120.90, 210.00, -55.95 , -96.91 , -64.95 , -112.50), + (21, 'C', 240, 1, 0.00 , 57.00 , 75.00 , 66.00 , 240.00, -49.36 , -28.50 , -64.95 , -37.50 ), + (22, 'C', 240, 2, 0.00 , 132.00, 150.00, 141.00, 240.00, -114.32, -66.00 , -129.90, -75.00 ), + (23, 'B', 240, 5, 19.11, 180.40, 198.40, 189.40, 259.11, -177.15, -34.08 , -194.83, -37.48 ), + (24, 'B', 240, 4, 40.89, 180.40, 198.40, 189.40, 280.89, -177.15, 34.08 , -194.83, 37.48 ), + (25, 'B', 240, 3, 30.00, 111.90, 129.90, 120.90, 270.00, -111.90, 0.00 , -129.90, 0.00 ), + (26, 'B', 300, 1, 0.00 , 57.00 , 75.00 , 66.00 , 300.00, -49.36 , 28.50 , -64.95 , 37.50 ), + (27, 'B', 300, 2, 0.00 , 132.00, 150.00, 141.00, 300.00, -114.32, 66.00 , -129.90, 75.00 ), + (28, 'A', 300, 5, 19.11, 180.40, 198.40, 189.40, 319.11, -118.09, 136.38 , -129.87, 149.98 ), + (29, 'A', 300, 4, 40.89, 180.40, 198.40, 189.40, 340.89, -59.06 , 170.46 , -64.95 , 187.47 ), + (30, 'A', 300, 3, 30.00, 111.90, 129.90, 120.90, 330.00, -55.95 , 96.91 , -64.95 , 112.5 ), + ) + +################################################################################################### +#Puck class +################################################################################################### +class Puck: + def __init__(self, id, block, index, angle, center, led_uni, led_mini): + self.id = id + self.block = block + self.index = index + self.angle = angle + self.center = center + self.led_uni = led_uni + self.led_mini = led_mini + self.detect = DET_UNKNOWN + + def __str__(self): + return "Number: " + str(self.id) + "\nBlock: " + str(self.block) + "\nIndex: " + str(self.index) + "\nAngle: " + str(self.angle) + \ + "\nCenter: " + str(self.center) + "\nLed Unipuck: " + str(self.led_uni) + "\nLed Minispine: " + str(self.led_mini) + + def get_name(self): + return str(self.block) + str(self.index) + + def match(self, x, y): + if math.hypot(x-self.led_uni[0], y-self.led_uni[1]) <= LED_TOLERANCE: + return DET_UNIPUCK + if math.hypot(x-self.led_mini[0], y-self.led_mini[1]) <= LED_TOLERANCE: + return DET_MINISPINE + return None + + + +_block_ids = [] +_puck_list = [] +_block_list = [] + +for p in(puck_layout): + puck = Puck(p[0], p[1], p[3], p[8], (p[11],p[12]), (p[9],p[10]), (p[11],p[12])) + _puck_list.append(puck) + if puck.block not in (_block_ids): + _block_ids.append(puck.block) + +def get_puck(id): + for p in _puck_list: + if id==p.id: + return p + return None + +def get_pucks(block = None): + ret = [] + for p in _puck_list: + if (block is None) or (block==p.block): + ret.append(p) + return ret + + +################################################################################################### +#Block class +################################################################################################### + +class Block: + def __init__(self, id, angle_range, x_range, y_range): + self.id = id + self.angle_range = angle_range + self.x_range = x_range + self.y_range = y_range + self.roi = (self.x_range[0] - BLOCK_ROI_TOLERANCE, self.y_range[0] - BLOCK_ROI_TOLERANCE, + self.x_range[1] + BLOCK_ROI_TOLERANCE, self.y_range[1] + BLOCK_ROI_TOLERANCE) + + def __str__(self): + return "Id: " + str(self.id) + "\nAngle: " + str(self.angle_range) + "\nX: " + str(self.x_range) + "\nY: " + str(self.y_range) + + +for id in _block_ids: + pucks = get_pucks(id) + angles, x, y = [], [], [] + for p in pucks: + angles.append(p.angle) + x.append(p.led_uni[0]) + x.append(p.led_mini[0]) + y.append(p.led_uni[1]) + y.append(p.led_mini[1]) + el = Block(id,(min(angles), max(angles)), (min(x), max(x)), (min(y), max(y))) + _block_list.append(el) + +def get_block(id): + for e in _block_list: + if id==e.id: + return e + return None + +def get_blocks(): + return _block_list + + + + +################################################################################################### +#Detection utilities +################################################################################################### + + +def _detect_puck(point_list, puck): + puck.detect = DET_ERROR + for point in point_list: + match = puck.match(point[0], point[1]) + if match is not None: + if match==DET_UNIPUCK: + puck.detect = DET_EMPTY if (puck.detect==DET_MINISPINE) else DET_UNIPUCK + elif match==DET_MINISPINE: + puck.detect = DET_EMPTY if (puck.detect==DET_UNIPUCK) else DET_MINISPINE + +def detect_pucks(point_list, id=None): + if (id is None) or (id in BLOCKS): + for puck in get_pucks(id): + _detect_puck(point_list, puck) + else: + puck = get_puck(int(id)) + print puck + _detect_puck(point_list, puck) + + +def clear_detection(block_id=None): + for puck in get_pucks(block_id): + puck.detect = DET_UNKNOWN + return get_puck_detection_dict(block_id) + + +def get_puck_detection(det_type, block_id=None): + ret = [] + for puck in get_pucks(block_id): + if puck.detect == det_type: + ret.append(puck) + return ret + +def get_unipucks(block_id=None): + return get_puck_detection(DET_UNIPUCK, block_id) + +def get_minispines(block_id=None): + return get_puck_detection(DET_MINISPINE, block_id) + +def get_empties(block_id=None): + return get_puck_detection(DET_EMPTY, block_id) + +def get_unknowns(block_id=None): + return get_puck_detection(DET_UNKNOWN, block_id) + +def get_det_errors(block_id=None): + return get_puck_detection(DET_ERROR, block_id) + +def get_puck_detection_dict(block_id): + ret = {} + pucks = [] + for puck in get_unipucks(block_id): + pucks.append(puck.get_name()) + pucks.sort() + ret["Unipuck"] = pucks + pucks = [] + for puck in get_minispines(block_id): + pucks.append(puck.get_name()) + pucks.sort() + ret["Minispine"] = pucks + pucks = [] + for puck in get_det_errors(block_id): + pucks.append(puck.get_name()) + pucks.sort() + ret["Error"] = pucks + pucks = [] + for puck in get_empties(block_id): + pucks.append(puck.get_name()) + pucks.sort() + ret["Empty"] = pucks + pucks = [] + for puck in get_unknowns(block_id): + pucks.append(puck.get_name()) + pucks.sort() + ret["Unknown"] = pucks + return ret + +################################################################################################### +#Plotting +################################################################################################### + +from plotutils import * + +def plot_base_plate(points = None, show_detect = True, title = None, p = None): + colors = (Color.RED, Color.BLUE, Color.MAGENTA, Color(128,0,128), Color(0,128,0), Color(255,128,0)) + if p is None: p = plot(None, title=title)[0] + p.getAxis(p.AxisId.Y).setInverted(True) + plot_circle(p, 0, 0, PLATE_SIZE/2, width = 0, color = Color.GRAY, name = "Plate") + plot_point(p, 0, 0, size = 10, color = Color.GRAY, name = "Center") + #p.setLegendVisible(True) + for block in get_blocks(): + (xmin, xmax) = block.x_range + (ymin, ymax) = block.y_range + (xmin, ymin, xmax, ymax ) = block.roi + index = get_blocks().index(block) + r = plot_rectangle(p, xmin, ymin, xmax, ymax, width =0, color=colors[index], name = block.id) + #In the first time the plot shows, it takes some time for the color to be assigned + #while r.color is None: + # time.sleep(0.001) + + if block.id in ('A', 'F'): + x, y = (xmin + xmax)/2, ymax + 5 + elif block.id in ('C', 'D'): + x, y = (xmin + xmax)/2, ymin - 5 + elif block.id == 'B': + x, y = xmax + 5, (ymin + ymax)/2 + elif block.id == 'E': + x, y = xmin - 5, (ymin + ymax)/2 + + p.addText(x,y, str(block.id), r.color) + + for puck in get_pucks(block.id): + (xu, yu) = puck.led_uni + (xm, ym) = puck.led_mini + plot_point(p, xu, yu, size = 3, color = r.color, name = str(puck.id)+"u") + plot_point(p, xm, ym, size = 7, color = r.color, name = str(puck.id)+"m") + plot_circle(p, xu, yu, LED_TOLERANCE, width = 0, color = r.color, name = str(puck.id)+"uc") + plot_circle(p, xm, ym, LED_TOLERANCE, width = 0, color = r.color, name = str(puck.id)+"mc") + p.addText((xu+xm)/2, (yu+ym)/2, str(puck.id), r.color) + c,w = Color.GRAY,0 + if show_detect: + if puck.detect == DET_UNIPUCK: + c,w = Color.BLACK,1 + elif puck.detect == DET_MINISPINE: + c,w = Color(150, 100, 50),1 + elif puck.detect == DET_ERROR: + c = Color(128,0,0) + plot_circle(p, xm, ym, PUCK_SIZE/2, width = w, color = c , name = str(puck.id)) + if points is not None: + for point in points: + c, w = Color.GRAY, 1 + for puck in get_pucks(): + match = puck.match(point[0], point[1]) + if match is not None: + w=2 + c = Color.DARK_GRAY if match == "minispine" else Color.BLACK + + + plot_cross(p,point[0], point[1], size = 12, width = w, color = c, name = "P"+ str(points.index(point))) + #plot_point(p,point[0], point[1], size = 5, color = Color.BLACK, name = "Pc"+ str(points.index(point))) diff --git a/script/tasks/ColdPositionTimeout.py b/script/tasks/ColdPositionTimeout.py new file mode 100644 index 0000000..197e829 --- /dev/null +++ b/script/tasks/ColdPositionTimeout.py @@ -0,0 +1,23 @@ +cold_position_timeout = int(get_setting("cold_position_timeout")) +if cold_position_timeout > 0: + if robot.last_command_position == "cold": + if (time.time() - robot.last_command_timestamp) > cold_position_timeout: + if robot.is_cold(): + log("Detected cold position timeout", False) + if get_context().state == State.Ready: + if robot.state == State.Ready: + if feedback_psys_safety.take() == True: + #TODO: Chan + get_context().evalLine("dry(wait_cold = -1)") #Dry and park : use get_context().evalLine to change application state + else: + raise Exception("Cannot clear cold position: feedback_psys_safety = False ") + else: + raise Exception("Cannot clear cold position: robot state: " + str(robot.state)) + else: + raise Exception("Cannot clear cold position: system state: " + str(get_context().state)) + + + + + + \ No newline at end of file diff --git a/script/tasks/LedMonitoring.py b/script/tasks/LedMonitoring.py new file mode 100644 index 0000000..5182a77 --- /dev/null +++ b/script/tasks/LedMonitoring.py @@ -0,0 +1,26 @@ +DEWAR_LEVEL_LED = 25.0 + +try: + _level = dewar_level.read() +except: + log("Cannot read Dewar level", False) + _level = 0.0 + +try: + _led_room_temp = is_led_room_temp() +except: + log("Cannot get LED configuration", False) + _led_room_temp = False + +if _level <= DEWAR_LEVEL_LED: + if not _led_room_temp: + log_str="LED Monitoring: Setting LEDs to room temperature range" + log(log_str, False) + print (log_str) + set_led_range(room_temp = True) +else: + if _led_room_temp: + log_str="LED Monitoring: Setting LEDs to LN2 range" + log(log_str, False) + print (log_str) + set_led_range(room_temp = False) diff --git a/script/tasks/MangnetMonitoring.py b/script/tasks/MangnetMonitoring.py new file mode 100644 index 0000000..8450895 --- /dev/null +++ b/script/tasks/MangnetMonitoring.py @@ -0,0 +1,24 @@ +try: + if feedback_local_safety.take() ==False: + magnet_release_state = magnet_release.read() + if former_magnet_release_state != magnet_release_state: + if magnet_release_state: + print "Pressed release button" + smart_magnet.set_resting_current() + smart_magnet_changed = True + else: + print "Released release button" + smart_magnet.set_default_current() + smart_magnet_changed = False + former_magnet_release_state = magnet_release_state + + else: + if smart_magnet_changed: + smart_magnet.set_default_current() + smart_magnet_changed = False + former_magnet_release_state = False + +except: + former_magnet_release_state = False + smart_magnet_changed = False + \ No newline at end of file diff --git a/script/test/CameraCalibration.py b/script/test/CameraCalibration.py new file mode 100644 index 0000000..02e7d9d --- /dev/null +++ b/script/test/CameraCalibration.py @@ -0,0 +1,71 @@ +import ch.psi.pshell.device.Camera as Camera +import ch.psi.pshell.imaging.RendererMode as RendererMode +from ch.psi.pshell.imaging.Overlays import * + +""" +img.camera.setColorMode(Camera.ColorMode.Mono) +img.camera.setDataType(Camera.DataType.UInt8) +img.camera.setGrabMode(Camera.GrabMode.Continuous) +img.camera.setTriggerMode(Camera.TriggerMode.Fixed_Rate) +img.camera.setExposure(50.00) +img.camera.setAcquirePeriod(200.00) +img.camera.setGain(0.0) +img.config.rotationCrop=True + + +""" +#img.camera.setROI(200, 0,1200,1200) + +img.camera.setROI(0, 0,1600,1200) +img.config.rotation=0 +img.config.roiX,img.config.roiY, img.config.roiWidth,img.config.roiHeight =0,0,-1,-1 +img.config.setCalibration(None) +img.camera.stop() +img.camera.start() + +#img.camera.setROI(300, 200,1000,1000) +#img.config.rotation=17 +#img.config.roiX,img.config.roiY, img.config.roiWidth,img.config.roiHeight = 50,50,900,900 + +p = show_panel(img) +p.setMode(RendererMode.Fit) +ov_text = Text(Pen(java.awt.Color.GREEN.darker()), "", java.awt.Font("Verdana", java.awt.Font.PLAIN, 24), java.awt.Point(20,20)) +ov_text.setFixed(True) + + + +p.addOverlay(ov_text) +try: + ov_text.update("Click on upper reference...") + p1 = p.waitClick(60000) + print p1 + ov_text.update("Click on left reference...") + p2 = p.waitClick(60000) + print p2 + ov_text.update("Click on right reference...") + p3 = p.waitClick(60000) + print p3 + + x, y, z = p1.x+p1.y*1j, p2.x+p2.y*1j, p3.x+p3.y*1j + w = z-x + w /= y-x + c = (x-y)*(w-abs(w)**2)/2j/w.imag-x + cx, cy, r = -c.real, -c.imag, abs(c+x) + a = math.degrees(math.atan((cx-p1.x)/(p1.y-cy))) + + print cx, cy, r, a + + #img.camera.setROI(int((1600-cx)/2),int((1200-cy)/2),1000,1000) + img.camera.setROI(int(cx-r),int(cy-r),int(2*r),int(2*r)) + img.config.rotation=-a + #remove rotation border + d=int(r/11) + img.config.roiX,img.config.roiY, img.config.roiWidth,img.config.roiHeight =d,d, int(2*r-2*d), int(2*r-2*d) + #img.config.setCalibration(None) + img.camera.stop() + img.camera.start() + +finally: + p.removeOverlay(ov_text) + + \ No newline at end of file diff --git a/script/test/CoverDetection.py b/script/test/CoverDetection.py new file mode 100644 index 0000000..24a228b --- /dev/null +++ b/script/test/CoverDetection.py @@ -0,0 +1,116 @@ +################################################################################################### +# Procedure to detect the cover orientation +################################################################################################### +import ch.psi.pshell.imaging.Utils.integrateVertically as integrateVertically +img.backgroundEnabled=False + +REF = (0,96,125) + +line = load_image("{images}/line.png", title="Line") +#line = load_image("{images}/line360.png", title="Line") + +line.getProcessor().setBackgroundValue(0.0) + + +#ip = get_image() +ip = integrate_frames(10) +ip = grayscale(ip, True) + + +smooth(ip) +#bandpass_filter(ip, 30, 1000) +edges(ip) + +#invert(ip) +#auto_threshold(ip, method = "Default") + + +#auto_threshold(ip, method = "Li") +auto_threshold(ip, method = "MaxEntropy") + +""" +for m in AutoThresholder.getMethods(): + print m + aux = ip.duplicate() + auto_threshold(aux, method = m) + binary_fill_holes(aux, dark_background=False) + renderer = show_panel(aux.bufferedImage) + time.sleep(1.0) +""" +#binary_dilate(ip, dark_background=False) +#binary_fill_holes(ip, dark_background=False) +#binary_open(ip, dark_background=Tr) + +renderer = show_panel(ip.bufferedImage) + + + + + + + + +#line = sub_image(line, 325, 325, 512, 512) +#ip = sub_image(ip, 325, 325, 512, 512) +line = sub_image(line, 453, 453, 256, 256) +ip = sub_image(ip, 453, 453, 256, 256) +#op = op_fft(ip, line, "correlate") + + +renderer = show_panel(ip.bufferedImage) + +#renderer = show_panel(op.bufferedImage) +#line.show() +ydata = [] +xdata = range (0,180,1) +for i in xdata: + l = line.duplicate() + l.getProcessor().setBackgroundValue(0.0) + l.getProcessor().rotate(float(i)) + op = op_fft(ip, l, "correlate") + bi = op.getBufferedImage() + p = integrateVertically(bi) + ydata.append(sum(p)) + + #renderer = show_panel(op.bufferedImage) + #time.sleep(0.001) + +def moving_average(arr, n) : + ret = [] + for i in range(len(arr)): + ret.append(mean(arr[max(i-n,0):min(i+n,len(arr)-1)])) + return ret +av = moving_average(ydata, 1) + +p = plot(ydata, xdata=xdata)[0] + +p.addSeries(LinePlotSeries("Moving Average")) +p.getSeries(1).setData(xdata, av) + +peaks = estimate_peak_indexes(ydata, xdata, (min(ydata) + max(ydata))/2, 25.0) +left, right = min(peaks), max(peaks) +if xdata[left]<5 and xdata[right]>(xdata[-1]-5): + #del peaks[0 if ydata[right] > ydata[left] else -1] + peaks.remove(right if ydata[right] > ydata[left] else left) + +peaks = sorted(peaks[:3]) +peaks_x = map(lambda x:xdata[x], peaks) +peaks_y = map(lambda x:ydata[x], peaks) + +print "Peaks", peaks +print "Peak indexes: " + str(peaks_x) +print "Peak values: " + str(peaks_y) + + +for i in range(len(peaks)): + peak = xdata[peaks[i]] + p.addMarker(peak, None, "N="+str(round(peak,2)), Color(80,0,80)) + if ((peaks[i]>160) and (REF[i]<20)): + peaks[i] = peaks[i] - 180.0 + +print "Peaks x: " + str(peaks_x) + + +d = mean(arrabs(arrsub(REF, peaks_x))) + +print "Angle = ", d \ No newline at end of file diff --git a/script/test/PuckDetection.py b/script/test/PuckDetection.py new file mode 100644 index 0000000..003ef68 --- /dev/null +++ b/script/test/PuckDetection.py @@ -0,0 +1,24 @@ +################################################################################################### +# Example of using ImageJ functionalities through ijutils. +################################################################################################### + +from ijutils import * +import java.awt.Color as Color + + +#Image Loading +ip = load_image("{images}/test2.png", title="Image") + + +#Puck Detection +aux = grayscale(ip, in_place=False) +aux.show() +plot(get_histogram(aux)) + +subtract_background(aux) +threshold(aux,0,50); aux.repaintWindow() +binary_fill_holes(aux); aux.repaintWindow() +(results,output_img)=analyse_particles(aux, 10000,50000, + fill_holes = False, exclude_edges = True,print_table=True, + output_image = "outlines", minCirc = 0.0, maxCirc = 1.0) +output_img.show() \ No newline at end of file diff --git a/script/test/PuckDetectionTest.py b/script/test/PuckDetectionTest.py new file mode 100644 index 0000000..2995f9a --- /dev/null +++ b/script/test/PuckDetectionTest.py @@ -0,0 +1,11 @@ +import datetime +index = 0 +prefix = str(datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) + +while True: + suffix = "%04d - " % index + print (suffix), + run("imgproc/LedDetectionProc.py") + index=index+1 + save_image(image, "{images}/" +prefix + "_" + suffix +".png", "png") + \ No newline at end of file diff --git a/script/test/RobotCartesianScan.py b/script/test/RobotCartesianScan.py new file mode 100644 index 0000000..4a5225e --- /dev/null +++ b/script/test/RobotCartesianScan.py @@ -0,0 +1,28 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +robot.enable() +move_to_laser() + +#robot.reset_motion("jLaser") + +#import ch.psi.pshell.device.Timestamp as Timestamp +#sensor = Timestamp() + +#ret = lscan(robot_z, sensor, -50.0, 0.0, 0.5, latency = 0, relative = True) +#ret = lscan(robot_x, sensor, -50.0, 0.0, 0.5, latency = 0, relative = True) + +robot.set_motors_enabled(True) +ret = lscan(robot_x, ue.readable, -1.5, 1.5, 0.1, latency = 0.01, relative = True) + +x,y = ret.getPositions(0), ret.getReadable(0) +y = [100.0-v for v in y] +(norm, mea, sigma) = fit(y, xdata=x) +print "Maximum at " + str(mea) + + + +robot.set_motors_enabled(True) +ret = lscan(robot_rz, ue.readable, 0.0, 40.0, 1.0, latency = 0.01, relative = True, range = "auto") + + diff --git a/script/test/SampleDataInput.py b/script/test/SampleDataInput.py new file mode 100644 index 0000000..1f00cd6 --- /dev/null +++ b/script/test/SampleDataInput.py @@ -0,0 +1,1861 @@ +USER_NAME = "My User" +DEWAR_NAME = "My Dewar" + +test_sample_data = [ \ + +#Puck A1 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "flat_Base_Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "flat_Base_Pin_2", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "regular_Base_Pin_1", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "A1",\ + "sampleName": "regular_Base_Pin_2", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck B1 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + \"sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck B2 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck B3 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck B4 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "u"dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck C1 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck C2 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck D1 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + +# Puck D2 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus""sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "u"dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ +# Puck D3 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ +# Puck D4 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "XXX", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + ] + + +set_samples_info(test_sample_data) \ No newline at end of file diff --git a/script/test/SampleDataInput_Dominik.py b/script/test/SampleDataInput_Dominik.py new file mode 100644 index 0000000..8ff4ae7 --- /dev/null +++ b/script/test/SampleDataInput_Dominik.py @@ -0,0 +1,1862 @@ +USER_NAME = "My User" +DEWAR_NAME = "My Dewar" + +test_sample_data = [ \ + #Puck A1 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "CA00CF1471", \ + "puckType": "unipuck", \ + "puckAddress": "",\ + "sampleName": "flat_Base_Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "CA00CF1471", \ + "puckType": "unipuck", \ + "puckAddress": "",\ + "sampleName": "flat_Base_Pin_2", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "CA00CF1471", \ + "puckType": "unipuck", \ + "puckAddress": "",\ + "sampleName": "regular_Base_Pin_1", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Isabelle Chip", \ + "puckBarcode": "CA00CF1471", \ + "puckType": "unipuck", \ + "puckAddress": "",\ + "sampleName": "regular_Base_Pin_2", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + # Puck B1 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "IDatamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_1", \ + "puckBarcode": "DMBW1", \ + "puckType": "unipuck", \ + "puckAddress": "B1",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck B2 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW2", \ + "puckType": "unipuck", \ + "puckAddress": "B2",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck B3 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_2", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_3", \ + "puckBarcode": "DMBW3", \ + "puckType": "unipuck", \ + "puckAddress": "B3",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck B4 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Black_White_4", \ + "puckBarcode": "DMBW4", \ + "puckType": "unipuck", \ + "puckAddress": "B4",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck C1 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_1", \ + "puckBarcode": "DMGA1", \ + "puckType": "unipuck", \ + "puckAddress": "C1",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck C2 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "Datamatrix_Grey_Alu_2", \ + "puckBarcode": "DMGA2", \ + "puckType": "unipuck", \ + "puckAddress": "C2",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck D1 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_1", \ + "puckBarcode": "NODM1", \ + "puckType": "unipuck", \ + "puckAddress": "D1",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck D2 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_2", \ + "puckBarcode": "NODM2", \ + "puckType": "unipuck", \ + "puckAddress": "D2",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck D3 \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_3", \ + "puckBarcode": "NODM3", \ + "puckType": "unipuck", \ + "puckAddress": "D3",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + \ + # Puck D4 + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_1", \ + "samplePosition": 1,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_2", \ + "samplePosition": 2,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_3", \ + "samplePosition": 3,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_4", \ + "samplePosition": 4,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_5", \ + "samplePosition": 5,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_6", \ + "samplePosition": 6,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_7", \ + "samplePosition": 7,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_8", \ + "samplePosition": 8,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_9", \ + "samplePosition": 9,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_10", \ + "samplePosition": 10,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_11", \ + "samplePosition": 11,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_12", \ + "samplePosition": 12,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_13", \ + "samplePosition": 13,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_14", \ + "samplePosition": 14,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_15", \ + "samplePosition": 15,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + { "userName": USER_NAME, \ + "dewarName": DEWAR_NAME, \ + "puckName": "No_Datamatrix_4", \ + "puckBarcode": "NODM4", \ + "puckType": "unipuck", \ + "puckAddress": "D4",\ + "sampleName": "Pin_16", \ + "samplePosition": 16,\ + "sampleBarcode": "", \ + "sampleStatus": "Present", \ + "sampleMountCount": 0, + } , \ + ] +set_samples_info(test_sample_data) \ No newline at end of file diff --git a/script/test/SampleDetection.py b/script/test/SampleDetection.py new file mode 100644 index 0000000..4638b7a --- /dev/null +++ b/script/test/SampleDetection.py @@ -0,0 +1,23 @@ +################################################################################################### +# Example of using ImageJ functionalities through ijutils. +################################################################################################### + +from ijutils import * +import java.awt.Color as Color + +#Image Loading +ip = load_image("{images}/test2.png", title="Image") + +aux = grayscale(ip, in_place=False) +aux.show() + +invert(aux); aux.repaintWindow() +#gaussian_blur(aux); aux.repaintWindow() +subtract_background(aux); aux.repaintWindow() +auto_threshold(aux); aux.repaintWindow() +binary_open(aux); aux.repaintWindow() +#binary_fill_holes(aux); aux.repaintWindow() +(results,output_img)=analyse_particles(aux, 250,1000, + fill_holes = False, exclude_edges = True,print_table=True, + output_image = "outlines", minCirc = 0.7, maxCirc = 1.0) +output_img.show() diff --git a/script/test/TestAlign.py b/script/test/TestAlign.py new file mode 100644 index 0000000..0cbe1a7 --- /dev/null +++ b/script/test/TestAlign.py @@ -0,0 +1,9 @@ + +v = 2.0 +#while(true): +for i in range(100): + v = v * (-1.0) + robot_j4.initialize() + robot_j4.moveRel(v) + robot.align() + \ No newline at end of file diff --git a/script/test/TestBugPcAPI b/script/test/TestBugPcAPI new file mode 100644 index 0000000..5be0f3e --- /dev/null +++ b/script/test/TestBugPcAPI @@ -0,0 +1,4 @@ +while True: + img.initialize() + img.waitNext(10000) + time.sleep(2.0) \ No newline at end of file diff --git a/script/test/TestBugPcAPI.py b/script/test/TestBugPcAPI.py new file mode 100644 index 0000000..7a3d186 --- /dev/null +++ b/script/test/TestBugPcAPI.py @@ -0,0 +1,30 @@ +import ch.psi.pshell.prosilica.Prosilica as Prosilica +import ch.psi.pshell.device.Camera as Camera + + +add_device(Prosilica("img", 25001, "PacketSize=1522;PixelFormat=Mono8;BinningX=1;BinningY=1;RegionX=300;RegionY=200;Width=1000;Height=1000;MulticastEnable=Off"), True) + +img.camera.setGrabMode(Camera.GrabMode.Continuous) +img.camera.setTriggerMode(Camera.TriggerMode.Fixed_Rate) +img.camera.setExposure(50.00) +img.camera.setAcquirePeriod(200.00) +img.camera.setGain(0.0) +#img.camera.setROI(200, 0,1200,1200) +""" +img.camera.setROI(300, 200,1000,1000) +img.config.rotation=17 +img.config.rotationCrop=True +img.config.roiX,img.config.roiY, img.config.roiWidth,img.config.roiHeight = 50,50,900,900 +""" +img.camera.setROI(int(get_setting("roi_x")), int(get_setting("roi_y")), int(get_setting("roi_w")), int(get_setting("roi_h"))) + +img.camera.stop() +img.camera.start() + + +show_panel(img) +#while True: +# img.initialize() +# img.waitNext(1000) +# time.sleep(0.1) + diff --git a/script/test/TestBugPcAPI2.py b/script/test/TestBugPcAPI2.py new file mode 100644 index 0000000..5e92b37 --- /dev/null +++ b/script/test/TestBugPcAPI2.py @@ -0,0 +1,6 @@ +import ch.psi.pshell.imaging.MjpegSource as MjpegSource +MjpegSource2 = get_context().pluginManager.getDynamicClass("MjpegSource2") +add_device(MjpegSource("gripper_cam", "http://axis-accc8e9cc87b.psi.ch/axis-cgi/mjpg/video.cgi"), True) +#gripper_cam.polling=1000 +gripper_cam.monitored = True +show_panel(gripper_cam) \ No newline at end of file diff --git a/script/test/TestBugPcAPI3.py b/script/test/TestBugPcAPI3.py new file mode 100644 index 0000000..6acb775 --- /dev/null +++ b/script/test/TestBugPcAPI3.py @@ -0,0 +1,4 @@ +while True: + img.initialize() + img.waitNext(1000) + time.sleep(0.1) \ No newline at end of file diff --git a/script/test/TestCalib.py b/script/test/TestCalib.py new file mode 100644 index 0000000..2cc311b --- /dev/null +++ b/script/test/TestCalib.py @@ -0,0 +1,30 @@ + +print "calibrate_tool" + +#Initial checks +robot.assert_no_task() +robot.reset_motion() +robot.wait_ready() +robot.assert_cleared() +#robot.assert_in_known_point() + +#Enabling +enable_motion() + +(detected, dm) = move_scanner() + +if detected: + print "Pin detected, trashing..." + trash() + (detected, dm) = move_scanner() + if detected: + raise Exception("Cannot trash pin") + +robot.open_tool() +robot.get_calibration_tool() + +run("calibration/ToolCalibration3") + +robot.put_calibration_tool() + +robot.save_program() diff --git a/script/test/TestCameraStability1.py b/script/test/TestCameraStability1.py new file mode 100644 index 0000000..e4660e9 --- /dev/null +++ b/script/test/TestCameraStability1.py @@ -0,0 +1,11 @@ +for i in range(10): + time.sleep(2) + move_home() + time.sleep(1) + move_park() + start = time.time() + while get_img_cover_pos() != 'A': + print ".", + time.sleep(0.001) + print "Time = " , ( time.time() - start) + \ No newline at end of file diff --git a/script/test/TestCameraStability2.py b/script/test/TestCameraStability2.py new file mode 100644 index 0000000..c6ef878 --- /dev/null +++ b/script/test/TestCameraStability2.py @@ -0,0 +1,14 @@ +cover_detection_debug=True +while True: + for pos in ['A', 'B', 'C', 'D', 'E', 'F']: + print "Moving to ", pos + hexiposi.move(pos) + move_home() + move_park() + time.sleep(2.0) + ret = run("imgproc/CoverDetection") + det = ret[0] + print "Detected: ", det + if det != pos: + raise Exception("Position error") + diff --git a/script/test/TestCameraStability3.py b/script/test/TestCameraStability3.py new file mode 100644 index 0000000..bcf1327 --- /dev/null +++ b/script/test/TestCameraStability3.py @@ -0,0 +1,9 @@ +for i in range(10): + time.sleep(1) + move_home() + time.sleep(1) + move_park() + start = time.time() + time.sleep(1.0) + print run("imgproc/LedDetectionProc.py") + \ No newline at end of file diff --git a/script/test/TestCmdSynchronization.py b/script/test/TestCmdSynchronization.py new file mode 100644 index 0000000..098998d --- /dev/null +++ b/script/test/TestCmdSynchronization.py @@ -0,0 +1,32 @@ +import java.util.logging.Level + +def get_pos_str(): + return "point: " + str(robot.get_current_point()) + " - here: " + str(robot.here()) + " - herej: " + str(robot.herej()) + +enable_motion() + +get_context().setLogLevel(java.util.logging.Level.FINER) +try: + while True: + #robot.move_dewar() + #robot.move_park() + + log("Moving dewar", False) + flag = robot.start_task('moveDewar') + print "moveDewar: ", flag + log("Waiting", False) + ret = robot.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + log("Moving dewar finished (" + str(ret) + ") - pos: " + get_pos_str(), False) + robot.assert_dewar() + + + log("Moving park", False) + flag = robot.start_task('movePark') + print "movePark: ", flag + log("Waiting", False) + ret = robot.wait_task_finished(TASK_WAIT_ROBOT_POLLING) + log("Moving park finished (" + str(ret) + ") - pos: " + get_pos_str(), False) + robot.assert_park() + +finally: + get_context().setLogLevel(java.util.logging.Level.INFO) \ No newline at end of file diff --git a/script/test/TestCoverDetection.py b/script/test/TestCoverDetection.py new file mode 100644 index 0000000..e21e2c0 --- /dev/null +++ b/script/test/TestCoverDetection.py @@ -0,0 +1,19 @@ + +ca = [] +aa = [] +pa = [] + +#for i in range(6): + #index = i+1 +for i in ['A', 'B', 'C', 'D', 'E', 'F']: + hexiposi.move(i) + [position, angle, confidence] = run("imgproc/CoverDetection") + print [position, angle, confidence] + pa.append(position) + aa.append(angle) + ca.append(confidence) + +print "---" +print "Position: " ,pa +print "Angle: " ,aa +print "Confidence: " ,ca, " mean: ", mean(ca) \ No newline at end of file diff --git a/script/test/TestEuclidean.py b/script/test/TestEuclidean.py new file mode 100644 index 0000000..4376dc7 --- /dev/null +++ b/script/test/TestEuclidean.py @@ -0,0 +1,23 @@ +import org.apache.commons.math3.geometry.euclidean.threed.Segment as Segment +import org.apache.commons.math3.geometry.euclidean.threed.Vector3D as Vector3D +import org.apache.commons.math3.geometry.euclidean.threed.Line as Line + + +p = Vector3D(0, 0, 3) + +p1 = Vector3D(0,0,0) +p2 = Vector3D(0,1,1 ) +tolerance = 0.001 + +l = Line(p1, p2, tolerance) + + +print l.distance(p) +print p1.distance(p) +print p2.distance(p) +print "---" + +print l.getAbscissa(p) +print l.pointAt(l.getAbscissa(p)) + +#print l.closestPoint(Line(p, p, 0.01)) \ No newline at end of file diff --git a/script/test/TestLaserScan.py b/script/test/TestLaserScan.py new file mode 100644 index 0000000..a977ba0 --- /dev/null +++ b/script/test/TestLaserScan.py @@ -0,0 +1,12 @@ +robot.set_motors_enabled(True) +steps_x = 10 +steps_y = 8 +#ret = ascan([robot_x, robot_z], ue.readable, [-10.0, -10.0], [10.0, 10.0], [10,10], latency = 0.01, relative = True , zigzag=True) +ret = ascan([robot_x, robot_z], ue.readable, [-10.0, -10.0], [10.0, 10.0], [steps_y,steps_x], latency = 0.01, relative = True , zigzag=False) +data = Convert.transpose(Convert.toBidimensional(to_array(ret.getReadable(0),'d'),steps_x+1,steps_y+1)) +plot(data, title="data") + +data = ret.getData(0) +plot(data, title="data2") + + diff --git a/script/test/TestLayout.py b/script/test/TestLayout.py new file mode 100644 index 0000000..7404a8e --- /dev/null +++ b/script/test/TestLayout.py @@ -0,0 +1,21 @@ + + + +points = [(100, 140), (130, 77), (120, 130), (110, 100)] +clear_detection() +detect_pucks(points) +#detect_pucks(points, 'A') +#detect_pucks(points, '4') +plot_base_plate(points) + + + +#plot_detected_leds(points) + + + +block_id = None +print get_unipucks(block_id) +print get_minispines(block_id) +print get_empties(block_id) +print get_det_errors(block_id) \ No newline at end of file diff --git a/script/test/TestMicrohawk.py b/script/test/TestMicrohawk.py new file mode 100644 index 0000000..0ea517e --- /dev/null +++ b/script/test/TestMicrohawk.py @@ -0,0 +1,10 @@ +import ch.psi.pshell.serial.TcpDevice as TcpDevice +import ch.psi.pshell.serial.UdpDevice as UdpDevice + + +microscan = TcpDevice("microscan", "MicroHAWK38C528:2001") +#microscan = UdpDevice("microscan", "MicroHAWK38C528:2001") +microscan.initialize() + + + diff --git a/script/test/TestRecover.py b/script/test/TestRecover.py new file mode 100644 index 0000000..340853f --- /dev/null +++ b/script/test/TestRecover.py @@ -0,0 +1,15 @@ +print "Pos=" + str(robot.get_cartesian_pos()) +for p in robot.get_known_points(): + print p + " = " + str(get_pnt(p)) + +print "-------------" + +for segment in known_segments: + is_on_segment(segment) + +print "-------------" +for segment in known_segments: + try: + move_to_segment(segment) + except: + print sys.exc_info()[1] \ No newline at end of file diff --git a/script/test/TestRelays.py b/script/test/TestRelays.py new file mode 100644 index 0000000..adcd828 --- /dev/null +++ b/script/test/TestRelays.py @@ -0,0 +1,12 @@ +for i in range(500): + relay1.write(True) + time.sleep(1.2) + relay1.write(False) + time.sleep(1.2) +""" +for i in range(5): + relays.write(to_array([True,]*16, 'z')) + time.sleep(0.2) + relays.write(to_array([False,]*16, 'z')) + time.sleep(0.2) +""" \ No newline at end of file diff --git a/script/test/TestRemoveBackground.py b/script/test/TestRemoveBackground.py new file mode 100644 index 0000000..7376f07 --- /dev/null +++ b/script/test/TestRemoveBackground.py @@ -0,0 +1,31 @@ +number_frames = 5 +number_backgrounds = 5 +minimum_size = 78 # r = 5 # 150 +maximum_size = 750 # r = 15 #1500 +min_circ = 0.2 + +threshold_method = "MaxEntropy" +threshold_method,threshold_range = "Manual", (0, 215) + +exclude_edges = True +led_latency = 0.5 #0.1 + +set_led_state(False) +time.sleep(led_latency) +img.waitNext(2000) + +background = average_frames(number_backgrounds) +#background = integrate_frames(number_backgrounds) + +set_led_state(True) +time.sleep(led_latency) +img.waitNext(2000) +image = average_frames(number_frames) +#image = integrate_frames(number_frames) + +set_led_state(False) + +op_image(image, background, "subtract", float_result=True, in_place=True) +image=grayscale(image) + +show_panel(image.getBufferedImage()) \ No newline at end of file diff --git a/script/test/TestRobot.py b/script/test/TestRobot.py new file mode 100644 index 0000000..d64599f --- /dev/null +++ b/script/test/TestRobot.py @@ -0,0 +1,41 @@ +robot.task_create("simulateEvents",3, name = "test", priority = 20) +print robot.get_task_status("test") +print robot.get_task_status("tests") + +robot.task_suspend("test") +print robot.get_task_status("test") +robot.task_resume("test") +print robot.get_task_status("test") + +robot.task_kill("test") +print robot.get_task_status("test") + + +print robot.is_powered() +print robot.get_monitor_speed() +#print robot.set_monitor_speed(20) +#print robot.enable() +print robot.disable() +print robot.is_calibrated() +print robot.read_working_mode() +print robot.get_emergency_stop_sts() +print robot.get_safety_fault_signal() +print robot.stop() +print robot.resume() +print robot.reset_motion() +print robot.is_empty() +print robot.is_settled() +print robot.get_move_id() +print robot.set_move_id(10) +print robot.get_joint_forces() +#print robot.open("gripper") +#print robot.close("gripper") +print robot.herej() +print robot.distance_t("t", "t") +print robot.distance_p("p", "p") +print robot.compose( "p", "world", "t") +print robot.here("gripper", "world") +print robot.joint_to_point("gripper", "world", "j") +print robot.point_to_joint("gripper", "j", "p") +print robot.position("p", "world") +print robot.mount(1, 2) diff --git a/script/test/TestRobot2.py b/script/test/TestRobot2.py new file mode 100644 index 0000000..68398b8 --- /dev/null +++ b/script/test/TestRobot2.py @@ -0,0 +1,72 @@ +import traceback + +robot.task_create("simulateEvents",3, name = "test", priority = 20) + +step = 0 +try: + while(True): + start = time.time() + step = 1 + robot.is_powered() + step = 2 + robot.get_task_status("test") + step = 3 + robot.get_monitor_speed() + step = 4 + #robot.set_monitor_speed(20) + #robot.enable() + step = 5 + #robot.disable() + step = 6 + robot.is_calibrated() + step = 7 + robot.read_working_mode() + step = 8 + robot.get_emergency_stop_sts() + step = 9 + robot.get_safety_fault_signal() + step = 10 + robot.stop() + step = 11 + robot.resume() + step = 12 + robot.reset_motion() + step = 13 + robot.is_empty() + step = 14 + robot.is_settled() + step = 15 + robot.get_move_id() + step = 16 + robot.set_move_id(10) + step = 17 + robot.get_joint_forces() + step = 18 + #robot.open_tool("gripper") + #robot.close_tool("gripper") + robot.herej() + step = 19 + robot.distance_t("t", "t") + step = 20 + robot.distance_p("p", "p") + step = 21 + robot.compose( "p", "world", "t") + step = 22 + robot.here("gripper", "world") + step = 23 + robot.joint_to_point("gripper", "world", "j") + step = 24 + robot.point_to_joint("gripper", "j", "p") + step = 25 + robot.position("p", "world") + step = 26 + robot.mount(1, 2) + #print time.time()-start +except: + print >> sys.stderr, traceback.format_exc() +finally: + print "Step: " + str(step) + try: + robot.task_kill("test") + except: + pass \ No newline at end of file diff --git a/script/test/TestRobotCmds.py b/script/test/TestRobotCmds.py new file mode 100644 index 0000000..4225281 --- /dev/null +++ b/script/test/TestRobotCmds.py @@ -0,0 +1,32 @@ + +import java.lang.System as System + +index = 0 +max_time = 0 +while True: + start = System.currentTimeMillis() + robot.execute(1,1,1) + time.sleep(0.01) + robot.execute(2,1,1) + time.sleep(0.01) + robot.execute(1,1,1) + time.sleep(0.01) + robot.execute(2,1,1) + time.sleep(0.01) + robot.execute(1,1,1) + time.sleep(0.01) + robot.execute(2,1,1) + time.sleep(0.01) + robot.execute(1,1,1) + time.sleep(0.01) + robot.execute(2,1,1) + time.sleep(0.01) + robot.execute(1,1,1) + time.sleep(0.01) + robot.execute(2,1,1) + time.sleep(0.01) + cur_time = System.currentTimeMillis() - start + max_time = max(cur_time, max_time) + print index, cur_time, max_time + index = index + 1 + \ No newline at end of file diff --git a/script/test/TestRobotCmds2.py b/script/test/TestRobotCmds2.py new file mode 100644 index 0000000..048e20e --- /dev/null +++ b/script/test/TestRobotCmds2.py @@ -0,0 +1,25 @@ +if robot_req.read() != 0: + raise Exception("Ongoing command") +if robot_ack.read() != 0: + raise Exception("Robot is not ready") +robot_cmd.write(1) +args = [0,0,0,0,0,0] + +robot_args.write(to_array(args, 'i')) + + +robot_req.write(1) +while robot_ack.read() == 0: + time.sleep(0.001) + +err = robot_ack.take() +if err == 1: + ret = robot_ret.read() + print ret +if err == 2: + print ("Invalid command: " + str(command)) +print ("Unknown error: " + str(err)) +robot_req.write(0) +while robot_ack.read() != 0: + time.sleep(0.001) + diff --git a/script/test/cycle_time b/script/test/cycle_time new file mode 100644 index 0000000..79c790e --- /dev/null +++ b/script/test/cycle_time @@ -0,0 +1,4 @@ +start =time.time() +unmount('A',2,5, force=True) +mount('A',2,5, force=True, read_dm=False) +print time.time()-start \ No newline at end of file diff --git a/script/test/imgtest.py b/script/test/imgtest.py new file mode 100644 index 0000000..de442c2 --- /dev/null +++ b/script/test/imgtest.py @@ -0,0 +1,47 @@ +################################################################################################### +# Example of using ImageJ functionalities through ijutils. +################################################################################################### + +from ijutils import * +import java.awt.Color as Color + +import ch.psi.pshell.imaging.Filter as Filter +from ch.psi.pshell.imaging.Overlays import * +import ch.psi.pshell.imaging.Pen as Pen + + + +def detect_pucks(ip): + aux = grayscale(ip, in_place=False) + threshold(aux,0,50) + binary_fill_holes(aux) + return analyse_particles(aux, 10000,50000, + fill_holes = False, exclude_edges = True,print_table=True, + output_image = "outlines", minCirc = 0.4, maxCirc = 1.0) + +def detect_samples(ip): + aux = grayscale(ip, in_place=False) + invert(aux) + subtract_background(aux) + auto_threshold(aux) + binary_open(aux) + return analyse_particles(aux, 300,800, + fill_holes = False, exclude_edges = True,print_table=True, + output_image = "outlines", minCirc = 0.4 + , maxCirc = 1.0) + + +class MyFilter(Filter): + def process(self, image, data): + ip = load_image(image) + (results_puck,output_puck) = detect_pucks(ip) + (results_samples,output_samples) = detect_samples(ip) + set_lut(output_puck, outline_lut1[0], outline_lut1[1], outline_lut1[2]) + set_lut(output_samples, outline_lut2[0], outline_lut2[1], outline_lut2[2]) + op_image(ip, output_samples, "xor") + op_image(ip, output_puck, "xor") + return ip.getBufferedImage() + +#Setting the filter to a source +img.setFilter(MyFilter()) + diff --git a/script/test/ip b/script/test/ip new file mode 100644 index 0000000..e79bb62 --- /dev/null +++ b/script/test/ip @@ -0,0 +1,15 @@ +from ijutils import * +import java.awt.Color as Color + +ip = load_image(img.getImage()) +grayscale(ip) +#ip=binning(ip,2) +gaussian_blur(ip) +#bandpass_filter(ip,20, 100) +auto_threshold(ip) + +#Particle Analysis +(results,output_img)=analyse_particles(ip, 500,2000, print_table=True) +output_img.show() + +ip.show() \ No newline at end of file diff --git a/script/test/mount_profile.py b/script/test/mount_profile.py new file mode 100644 index 0000000..89ebd80 --- /dev/null +++ b/script/test/mount_profile.py @@ -0,0 +1,144 @@ +mount_sample_id = None +mount_sample_detected = None + +def mount(segment, puck, sample, force=False, read_dm=False, auto_unmount=False): + """ + """ + global mount_sample_id, mount_sample_detected + print "mount: ", segment, puck, sample, force + + start = time.time() + + #time.sleep(2) + is_aux = (segment == AUX_SEGMENT) + + #ZACH + needs_chilling = not is_aux and (not robot.is_cold()) + needs_drying = is_aux and robot.is_cold() + + puck_address = get_puck_address(puck) + if puck_address is None: + puck_obj = get_puck_obj_by_id(puck) + if puck_obj is not None: + puck_address = puck_obj.name + if puck_address is not None: + print "puck address: ", puck_address + segment = puck_address[:1] + puck = int(puck_address[1:]) + #Initial checks + assert_valid_address(segment, puck, sample) + assert_puck_detected(segment, puck) + + if robot.simulated: + time.sleep(3.0) + mount_sample_detected = True + mount_sample_id = "YYY0001" + update_samples_info_sample_mount(get_puck_name(segment, puck), sample, mount_sample_detected, mount_sample_id) + set_setting("mounted_sample_position", get_sample_name(segment, puck, sample)) + return [mount_sample_detected, mount_sample_id] + + robot.assert_no_task() + robot.reset_motion() + robot.wait_ready() + robot.assert_cleared() + #robot.assert_in_known_point() + hexiposi.assert_homed() + assert_mount_position() + + print "Pass 1: ", time.time() - start; start = time.time() + try: + #ZACH + if needs_chilling: + robot.move_cold() + time.sleep(30.0) + + if smart_magnet.get_supress() == True: + smart_magnet.set_supress(False) + time.sleep(0.2) + #To better dectect sample + smart_magnet.apply_resting() + time.sleep(0.5) + if smart_magnet.check_mounted(idle_time=0.25, timeout = 1.0) == True: + print "Pass 1b: ", time.time() - start; start = time.time() + if auto_unmount and (get_setting("mounted_sample_position") is not None): + #auto_unmount set to true so detection remains enabled + unmount(force = True, auto_unmount = True) + else: + raise Exception("Pin detected on gonio") + print "Pass 2: ", time.time() - start; start = time.time() + set_status("Mounting: " + str(segment) + str(puck) + str(sample)) + #location = robot.get_current_point() + + #Enabling + enable_motion() + + print "Pass 3: ", time.time() - start; start = time.time() + #ZACH + # a room temp pin is being mounted but the gripper is cold + if needs_drying: + dry(wait_cold=-1) # move to park after dry + + if is_aux: + if not robot.is_aux(): + robot.move_aux() + print "Pass 3b: ", time.time() - start; start = time.time() + robot.get_aux(sample) + + else: + set_hexiposi(segment) + print "Pass 4: ", time.time() - start; start = time.time() + if not force: + visual_check_hexiposi(segment) + + if not robot.is_dewar(): + robot.move_dewar() + print "Pass 4b: ", time.time() - start; start = time.time() + robot.get_dewar(segment, puck, sample) + print "Pass 5: ", time.time() - start; start = time.time() + + if read_dm: + barcode_reader.start_read(10.0) + robot.move_scanner() + #time.sleep(1.0) + + robot.move_gonio() + + if read_dm: + mount_sample_id = barcode_reader.get_readout() + print "Datamatrix: " , mount_sample_id + else: + mount_sample_id = None + print "Pass 6: ", time.time() - start; start = time.time() + + robot.put_gonio() + print "Pass 7: ", time.time() - start; start = time.time() + + try: + dry_mount_count = int(get_setting("dry_mount_counter")) + except: + dry_mount_count = 0 + set_setting("dry_mount_counter", dry_mount_count+1) + + if is_aux: + robot.move_home() + else: + robot.move_cold() + print "Pass 8: ", time.time() - start; start = time.time() + mount_sample_detected = smart_magnet.check_mounted(idle_time=0.25, timeout = 1.0) + update_samples_info_sample_mount(get_puck_name(segment, puck), sample, mount_sample_detected, mount_sample_id) + if mount_sample_detected == False: + raise Exception("No pin detected on gonio") + + + if is_force_dry(): + smart_magnet.set_default_current() + print "Auto dry" + log("Starting auto dry", False) + set_exec_pars(then = "dry()") + print "Pass 9: " , time.time() - start; start = time.time() + set_setting("mounted_sample_position", get_sample_name(segment, puck, sample)) + return [mount_sample_detected, mount_sample_id] + finally: + smart_magnet.set_default_current() + smart_magnet.set_supress(True) + diff --git a/script/test/onewire.py b/script/test/onewire.py new file mode 100644 index 0000000..5141067 --- /dev/null +++ b/script/test/onewire.py @@ -0,0 +1,122 @@ +import traceback + +class Detector(ReadonlyRegisterBase): + def __init__(self, name): + ReadonlyRegisterBase.__init__(self, name) + self.id = None + self.sn = None + self.status = None + self.type = None + self.set_inputs({}) + + def set_inputs(self, inputs): + self.inputs = inputs + self.setCache(inputs.values(), None) + if (len(self.take()) == 0): + self.setState(State.Offline) + else: + self.setState(State.Ready) + + def set_input(self, index, val): + self.inputs[index] = val + self.set_inputs(self.inputs) + +class Esera(TcpDevice): + def __init__(self, name, server, timeout = 1000, retries = 1): + TcpDevice.__init__(self, name, server) + self.setMode(self.Mode.FullDuplex) + self.detectors = [] + self.complete = False + + def start(self): + self.write("set,sys,run,1\n") + + def stop(self): + self.write("set,sys,run,0\n") + + def list(self): + self.write("get,owb,listall1\n") + + def req_data(self): + self.write("get,sys,data\n") + + def doInitialize(self): + super(Esera, self).doInitialize() + try: + self.setState(State.Ready) #So can communicate + #self.stop() + #time.sleep(0.1) + #self.flush() + self.detectors = [] + for i in range(30): + self.detectors.append(Detector("Detector " + str(i+1))) + self.list() + time.sleep(0.5) + self.start() + self.req_data() + except: + print >> sys.stderr, traceback.format_exc() + getLogger().log(traceback.format_exc()) + raise + + def doUpdate(self): + if not self.complete: + init = True + for det in self.detectors: + if det.id == None: + init = False + break + if init: + self.complete = True + self.start() + + self.req_data() + #def onByte(self, rx): + # print rx + + def onString(self, msg): + print msg + tokens = msg.split("|") + if len(tokens)>1: + try: + if msg[:3] == "LST": + #LST|1_OWD1|3AF361270000009E|S_0|DS2413| + if tokens[1] > 1: + index = int(tokens[1].split("_")[1][3:]) - 1 + if index < len(self.detectors): + det = self.detectors[index] + det.id = tokens[1] + det.sn= tokens[2] if len(tokens)>2 else None + det.status = int(tokens[3][2:]) if len(tokens)>3 else None + det.type = tokens[4] if len(tokens)>4 else None + if det.status!= 0: + det.set_inputs({}) + else: + for det in self.detectors: + if det.id is not None and msg.startswith(det.id): + det_id = int(tokens[0][len(det.id)+1:]) + det.set_input(det_id, int(tokens[1])) + except: + print >> sys.stderr, traceback.format_exc() + getLogger().log(traceback.format_exc()) + + + + +#count = 1 +#while (True): +# print onewire.waitString("\n", 1000)# +# +# print count +# count = count + 1 + + + + + + +add_device(Esera("onewire", "129.129.126.83:5000"), force = True) +onewire.setPolling(1000) +add_device(onewire.detectors[0], force = True) +add_device(onewire.detectors[1], force = True) +add_device(onewire.detectors[2], force = True) diff --git a/script/test/test.py b/script/test/test.py new file mode 100644 index 0000000..92fcaca --- /dev/null +++ b/script/test/test.py @@ -0,0 +1,24 @@ +from ijutils import * +import java.awt.Color as Color + + +#ip = load_array(img.getData().getMatrix()) +ip = load_image(img.getOutput()) +#ip = load_image("{images}/test3.png", title="Image") + + +aux = grayscale(ip, in_place=False) +aux.show() + +#convolve(aux, KERNEL_SOBEL); aux.repaintWindow() + +invert(aux); aux.repaintWindow() +subtract_background(aux); aux.repaintWindow() +auto_threshold(aux); aux.repaintWindow() +binary_open(aux); aux.repaintWindow() + +(table, image) = analyse_particles(aux, 250,1000, + fill_holes = False, exclude_edges = True,print_table=True, + output_image = "outlines", minCirc = 0.82, maxCirc = 1.0) + +image.show() \ No newline at end of file diff --git a/script/test/test_hexiposi.py b/script/test/test_hexiposi.py new file mode 100644 index 0000000..324a480 --- /dev/null +++ b/script/test/test_hexiposi.py @@ -0,0 +1,8 @@ +index = 1 +while(True): + for pos in ['A', 'B', 'C', 'D', 'E', 'F']: + hexiposi.move(pos) + visual_check_hexiposi(pos) + print "Ok: " + pos + print index + index = index + 1 \ No newline at end of file diff --git a/script/test/test_segments.py b/script/test/test_segments.py new file mode 100644 index 0000000..54a8898 --- /dev/null +++ b/script/test/test_segments.py @@ -0,0 +1,16 @@ +p=robot.get_cartesian_pos() +print "Pos: ", p +print "Cache pos:", robot.cartesian_pos +print "Cache dest: ", robot.cartesian_destination + + +print "Points: ", robot.get_current_points() +for segment in known_segments: + if is_on_segment(segment): + print " On : " + str(segment) + " - Dist:" + str(get_dist_to_segment(segment)) + + + +p2 = robot.get_cartesian_pos() +if arrsub( p2, p) != [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]: + print "Pos: ", p2 \ No newline at end of file diff --git a/script/test/test_swingutils.py b/script/test/test_swingutils.py new file mode 100644 index 0000000..65b354b --- /dev/null +++ b/script/test/test_swingutils.py @@ -0,0 +1,24 @@ +import ch.psi.pshell.imaging.RendererMode as RendererMode +import ch.psi.pshell.imaging.Calibration as Calibration +from ch.psi.pshell.imaging.Overlays import * +import ch.psi.utils.swing.SwingUtils as SwingUtils +import javax.swing.SwingUtilities as SwingUtilities +from swingutils.threads.swing import callSwing + + +p = show_panel(img) +dlg = SwingUtilities.getWindowAncestor(p) +dlg.setSize(800,800) +frm=SwingUtils.getFrame(p) +dlg.setLocationRelativeTo(frm) + +def update_frame(frm): + SwingUtilities.updateComponentTreeUI(frm) + frm.validate() + frm.repaint() +#$callSwing(update_frame, frm) + + +x=0 +for i in range(0,10000000): + x=x+1 \ No newline at end of file diff --git a/script/test/then.py b/script/test/then.py new file mode 100644 index 0000000..fcbe1bb --- /dev/null +++ b/script/test/then.py @@ -0,0 +1,3 @@ +time.sleep(3.0) + +set_exec_pars(then = "time.sleep(5.0)") \ No newline at end of file diff --git a/script/test/transfer_profile.py b/script/test/transfer_profile.py new file mode 100644 index 0000000..00be4cb --- /dev/null +++ b/script/test/transfer_profile.py @@ -0,0 +1,23 @@ + + +measures = [] +#smart_magnet.measures = [] + + +for m in range(1): + print "Step ", m + start = time.time() + #mount('A',3,m+1, force=True, read_dm=False, auto_unmount=True) + #mount('A',5,m+1, force=True, read_dm=False, auto_unmount=True) + + #mount('A',3,1, force=True, read_dm=False, auto_unmount=True) + #unmount('A',3,1, force=True) + #mount('A',3,1, force=True, read_dm=False, auto_unmount=True) + measures.append( time.time()-start) + +print "Total transfer: ", measures +print "Time: ", mean(measures), "+-", stdev(measures) + + +#print "SM cehck: ", smart_magnet.measures +#print "Time: ", mean(smart_magnet.measures), "+-", stdev(smart_magnet.measures) \ No newline at end of file diff --git a/script/test/unmount_profile.py b/script/test/unmount_profile.py new file mode 100644 index 0000000..5adcdb3 --- /dev/null +++ b/script/test/unmount_profile.py @@ -0,0 +1,98 @@ +def unmount(segment = None, puck = None, sample = None, force=False, auto_unmount = False): + """ + """ + print "unmount: ", segment, puck, sample, force + start = time.time() + #ZACH + is_aux = (segment == AUX_SEGMENT) + needs_chilling = not is_aux and (not robot.is_cold()) + needs_drying = is_aux and robot.is_cold() + + if (segment is None) or (puck is None) or (sample is None): + pos = get_setting("mounted_sample_position") + if pos is None: + raise Exception("Mounted sample position is not defined") + segment, puck , sample = pos[0:1], int(pos[1]), int(pos[2:]) + print "Mounted sample position: ", segment, puck , sample + + #Initial checks + print "assert valid address" + assert_valid_address(segment, puck, sample) + print "asser puck detected" + assert_puck_detected(segment, puck) + + if robot.simulated: + time.sleep(3.0) + update_samples_info_sample_unmount(get_puck_name(segment, puck), sample) + set_setting("mounted_sample_position", None) + return + + print "assert no task" + robot.assert_no_task() + print "reset motion" + robot.reset_motion() + print "wait ready" + robot.wait_ready() + print "assert cleared" + robot.assert_cleared() + #robot.assert_in_known_point() + print "assert homed" + hexiposi.assert_homed() + print "assert mount pos" + assert_mount_position() + print "Pass A: " , time.time() - start; start = time.time() + set_status("Umounting: " + str(segment) + str(puck) + str(sample)) + + try: + if smart_magnet.get_supress() == True: + smart_magnet.set_supress(False) + time.sleep(0.2) + + smart_magnet.apply_resting() + + if not force: + if smart_magnet.check_mounted(idle_time=0.5, timeout = 3.0) == False: + raise Exception("No pin detected on gonio") + print "Pass B: " , time.time() - start; start = time.time() + #Enabling + enable_motion() + print "Pass C: " , time.time() - start; start = time.time() + + if not is_aux: + set_hexiposi(segment) + print "Pass D: " , time.time() - start; start = time.time() + if not force: + visual_check_hexiposi(segment) + if needs_chilling: + robot.move_cold() + time.sleep(30.) + else: + if needs_drying: + dry(wait_cold=-1) + #location = robot.get_current_point() + print "Pass E: " , time.time() - start; start = time.time() + if not robot.is_gonio(): + robot.move_gonio() + print "Pass F: " , time.time() - start; start = time.time() + #smart_magnet.set_unmount_current() + + robot.get_gonio() + print "Pass G: " , time.time() - start; start = time.time() + #smart_magnet.apply_reverse() + #smart_magnet.apply_resting() + + if is_aux: + robot.move_aux() + robot.put_aux( sample) + else: + #TODO: Shuld check if smart magnet detection is off? + update_samples_info_sample_unmount(get_puck_name(segment, puck), sample) + robot.move_dewar() + print "Pass H: " , time.time() - start; start = time.time() + robot.put_dewar(segment, puck, sample) + print "Pass I: " , time.time() - start; start = time.time() + set_setting("mounted_sample_position", None) + finally: + if not auto_unmount: + smart_magnet.set_default_current() + smart_magnet.set_supress(True) diff --git a/script/tools/CheckGripper.py b/script/tools/CheckGripper.py new file mode 100644 index 0000000..603c756 --- /dev/null +++ b/script/tools/CheckGripper.py @@ -0,0 +1,23 @@ + + + +vector = [1.0,0.0] + + + +class GripperTool(RegisterBase): + def doRead(self): + return 1.0 if robot.is_tool_open() else 0.0 + + def doWrite(self, val): + if val: + robot.open_tool() + else: + robot.close_tool() + + +add_device(GripperTool("gripper_tool"), True) + + +vscan(gripper_tool, [laser_distance,], vector, latency = 3, passes=50) + diff --git a/script/tools/CheckPuckDetection.py b/script/tools/CheckPuckDetection.py new file mode 100644 index 0000000..d9bf425 --- /dev/null +++ b/script/tools/CheckPuckDetection.py @@ -0,0 +1,6 @@ +USR,PWD = "pi", "raspberry" +HOST,PORT = "tell6d-raspberrypi", 22 +CMD= "sudo systemctl status puck_detection.service" + +ret = run("tools/SshExec") +set_return(ret) \ No newline at end of file diff --git a/script/tools/Math.py b/script/tools/Math.py new file mode 100644 index 0000000..c76ce33 --- /dev/null +++ b/script/tools/Math.py @@ -0,0 +1,68 @@ +################################################################################################### +# Math utilities +################################################################################################### + + +from mathutils import estimate_peak_indexes, fit_gaussians, create_fit_point_list, Gaussian +import java.awt.Color as Color + +import mathutils +mathutils.MAX_ITERATIONS = 100000 + +def fit(ydata, xdata = None, draw_plot = True): + if xdata is None: + xdata = frange(0, len(ydata), 1) + max_y= max(ydata) + index_max = ydata.index(max_y) + max_x= xdata[index_max] + #print "Max index:" + str(index_max), + #print " x:" + str(max_x), + #print " y:" + str(max_y) + + if draw_plot: + plots = plot([ydata],["data"],[xdata], title="Fit" ) + p = None if plots is None else plots[0] + + gaussians = fit_gaussians(ydata, xdata, [index_max,]) + if gaussians[0] is None: + if draw_plot and (p is not None): + p.addMarker(max_x, None, "Max="+str(round(max_x,4)), Color.GRAY) + print "Fitting error" + return (None, None, None) + + (norm, mean, sigma) = gaussians[0] + if draw_plot: + fitted_gaussian_function = Gaussian(norm, mean, sigma) + scale_x = [float(min(xdata)), float(max(xdata)) ] + points = max((len(xdata)+1), 100) + resolution = (scale_x[1]-scale_x[0]) / points + fit_y = [] + fit_x = frange(scale_x[0],scale_x[1],resolution, True) + for x in fit_x: + fit_y.append(fitted_gaussian_function.value(x)) + #Server + if p is None: + plot([ydata,fit_y],["data","fit"],[xdata,fit_x], title="Fit") + draw_plot = False + else: + p.addSeries(LinePlotSeries("fit")) + p.getSeries(1).setData(fit_x, fit_y) + + if abs(mean - xdata[index_max]) < abs((scale_x[0] + scale_x[1])/2): + if draw_plot: + p.addMarker(mean, None, "Mean="+str(round(mean,4)), Color.MAGENTA.darker()) + #print "Mean -> " + str(mean) + return (norm, mean, sigma) + else: + if draw_plot: + p.addMarker(max_x, None, "Max="+str(round(max_x,4)), Color.GRAY) + #print "Invalid gaussian fit: " + str(mean) + return (None, None, None) + + +def enforce_monotonic(x): + epsilon = 1e-8 + for i in range(len(x)-1): + if x[i+1]<=x[i]: + x[i+1] = x[i]+ epsilon + return x \ No newline at end of file diff --git a/script/tools/RestartPuckDetection.py b/script/tools/RestartPuckDetection.py new file mode 100644 index 0000000..8f14c00 --- /dev/null +++ b/script/tools/RestartPuckDetection.py @@ -0,0 +1,8 @@ +USR,PWD = "pi", "raspberry" +HOST,PORT = "tell6d-raspberrypi", 22 +CMD= "sudo systemctl stop puck_detection.service;sudo systemctl start puck_detection.service" + +ret = run("tools/SshExec") +set_return(ret) + +puck_detection.initialize() \ No newline at end of file diff --git a/script/tools/SshExec.py b/script/tools/SshExec.py new file mode 100644 index 0000000..c9e4f50 --- /dev/null +++ b/script/tools/SshExec.py @@ -0,0 +1,61 @@ +import com.jcraft.jsch.Channel as Channel +import com.jcraft.jsch.ChannelShell as ChannelShell +import com.jcraft.jsch.JSch as JSch +import com.jcraft.jsch.JSchException as JSchException +import com.jcraft.jsch.Session as Session +import java.lang.System as System +import java.io.PrintStream as PrintStream + + +#Parameters: +#CMD +#USR +#PWD +#HOST +#PORT + +jsch= JSch() +session=jsch.getSession(USR, HOST, PORT) +session.setPassword(PWD) +session.setConfig("StrictHostKeyChecking", "no") +session.connect() + +#channel=session.openChannel("shell") +#input_stream=channel.getInputStream() +#output_stream = channel.getOutputStream() +#print_stream = PrintStream(output_stream, True) +#print_stream.println("ls") + +channel=session.openChannel("exec") +channel.setCommand(CMD) +channel.setInputStream(None) +channel.setOutputStream(System.out) +channel.setErrStream(System.err) +input_stream=channel.getInputStream() + + +def wait_ret(): + global input_stream + rx = "" + while True: + while (input_stream.available() > 0): + i = input_stream.read() + if i < 0: + break + rx=rx+chr(i) + if channel.closed: + break + time.sleep(0.1) + return rx + + +channel.connect() + +try: + ret = wait_ret() +except: + channel.disconnect() + session.disconnect() + raise + +set_return(ret) \ No newline at end of file diff --git a/script/tools/StopPuckDetection.py b/script/tools/StopPuckDetection.py new file mode 100644 index 0000000..9c5b27b --- /dev/null +++ b/script/tools/StopPuckDetection.py @@ -0,0 +1,8 @@ +USR,PWD = "pi", "raspberry" +HOST,PORT = "tell6d-raspberrypi", 22 +CMD= "sudo systemctl stop puck_detection.service" + +ret = run("tools/SshExec") +set_return(ret) + +