From d7570bcaf3270b21bb482be1149d10371bb04c03 Mon Sep 17 00:00:00 2001 From: gac-S_Changer Date: Wed, 29 Nov 2017 09:37:22 +0100 Subject: [PATCH] --- config/config.properties | 4 +- config/devices.properties | 2 +- config/mail.properties | 9 + config/plugins.properties | 3 + devices/20161117_163816.png.properties | 20 + devices/img.properties | 4 +- devices/robot x.properties | 9 + devices/robot_j1.properties | 9 + devices/robot_j2.properties | 9 + devices/robot_j3.properties | 9 + devices/robot_j4.properties | 9 + devices/robot_j5.properties | 9 + devices/robot_j6.properties | 9 + devices/robot_rx.properties | 9 + devices/robot_ry.properties | 9 + devices/robot_rz.properties | 9 + devices/robot_x.properties | 9 + devices/robot_y.properties | 9 + devices/robot_z.properties | 9 + plugins/LaserUE.java | 45 +- plugins/MXSC-1.7.0.jar | Bin 34708 -> 34708 bytes plugins/MXSC-1.8.0.jar | Bin 0 -> 34708 bytes plugins/RobotPanel.form | 450 ++++++++++++++++++++ plugins/RobotPanel.java | 543 +++++++++++++++++++++++++ plugins/Wayne.form | 46 +++ plugins/Wayne.java | 82 ++++ script/calibration/ScanRZ.py | 21 + script/calibration/ScanX.py | 37 ++ script/calibration/ScanXZ.py | 79 ++++ script/calibration/ToolCalibration.py | 42 ++ script/devices/RobotMotors.py | 73 ++++ script/devices/RobotSC.py | 68 +++- script/devices/RobotTCP.py | 180 ++++++-- script/local.py | 17 +- script/motion/tools.py | 60 +++ script/test/RobotCartesianScan.py | 28 ++ script/test/TestLaserScan.py | 12 + script/test/TestRobot.py | 2 +- script/test/TestRobot2.py | 2 +- 39 files changed, 1881 insertions(+), 65 deletions(-) create mode 100644 config/mail.properties create mode 100644 devices/20161117_163816.png.properties create mode 100644 devices/robot x.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_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 plugins/MXSC-1.8.0.jar create mode 100644 plugins/RobotPanel.form create mode 100644 plugins/RobotPanel.java create mode 100644 plugins/Wayne.form create mode 100644 plugins/Wayne.java create mode 100644 script/calibration/ScanRZ.py create mode 100644 script/calibration/ScanX.py create mode 100644 script/calibration/ScanXZ.py create mode 100644 script/calibration/ToolCalibration.py create mode 100644 script/devices/RobotMotors.py create mode 100644 script/motion/tools.py create mode 100644 script/test/RobotCartesianScan.py create mode 100644 script/test/TestLaserScan.py diff --git a/config/config.properties b/config/config.properties index a975681..116cb38 100644 --- a/config/config.properties +++ b/config/config.properties @@ -1,4 +1,4 @@ -#Thu Aug 31 17:39:38 CEST 2017 +#Fri Nov 17 14:23:32 CET 2017 autoSaveScanData=true createSessionFiles=false dataLayout=default @@ -8,12 +8,14 @@ dataScanFlushRecords=true dataScanPreserveTypes=false dataScanReleaseRecords=false dataServerPort=-1 +depthDimension=0 hostName=null instanceName=MXSC logDaysToLive=-1 logLevel=Fine logLevelConsole=Off logPath={logs}/{date}_{time} +notificationLevel=null scanStreamerPort=-1 serverEnabled=true serverPort=8080 diff --git a/config/devices.properties b/config/devices.properties index 46828c6..6a06931 100644 --- a/config/devices.properties +++ b/config/devices.properties @@ -1,4 +1,4 @@ -img=ch.psi.pshell.prosilica.Prosilica|25001 ''|||true +img=ch.psi.pshell.prosilica.Prosilica|25001 ''||-100|false microscan=ch.psi.pshell.serial.TcpDevice|129.129.126.200:2001||| microscan_cmd=ch.psi.pshell.serial.TcpDevice|129.129.126.200:2003||| ue=LaserUE|COM4||| 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 index 59ad112..3f3132e 100644 --- a/config/plugins.properties +++ b/config/plugins.properties @@ -1,3 +1,6 @@ +MXSC-1.8.0.jar=disabled +RobotPanel.java=enabled +Wayne.java=disabled LaserUE.java=enabled MXSC-1.7.0.jar=disabled MXSC-1.6.0.jar=disabled 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/img.properties b/devices/img.properties index 303e7da..79e039f 100644 --- a/devices/img.properties +++ b/devices/img.properties @@ -1,4 +1,4 @@ -#Wed Jul 12 15:55:35 CEST 2017 +#Tue Oct 24 14:01:22 CEST 2017 colormap=Grayscale colormapAutomatic=false colormapMax=18.133 @@ -13,7 +13,7 @@ roiHeight=1200 roiWidth=1200 roiX=200 roiY=0 -rotation=140.0 +rotation=0.0 rotationCrop=true scale=1.0 spatialCalOffsetX=-600.0 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_j1.properties b/devices/robot_j1.properties new file mode 100644 index 0000000..0977d42 --- /dev/null +++ b/devices/robot_j1.properties @@ -0,0 +1,9 @@ +#Tue Nov 28 17:23:41 CET 2017 +maxValue=180.0 +minValue=-180.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +unit=deg diff --git a/devices/robot_j2.properties b/devices/robot_j2.properties new file mode 100644 index 0000000..00e1ce2 --- /dev/null +++ b/devices/robot_j2.properties @@ -0,0 +1,9 @@ +#Tue Nov 28 17:23:45 CET 2017 +maxValue=127.5 +minValue=-127.5 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +unit=deg diff --git a/devices/robot_j3.properties b/devices/robot_j3.properties new file mode 100644 index 0000000..9ea7576 --- /dev/null +++ b/devices/robot_j3.properties @@ -0,0 +1,9 @@ +#Tue Nov 28 17:23:40 CET 2017 +maxValue=152.5 +minValue=-152.5 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +unit=deg diff --git a/devices/robot_j4.properties b/devices/robot_j4.properties new file mode 100644 index 0000000..0354887 --- /dev/null +++ b/devices/robot_j4.properties @@ -0,0 +1,9 @@ +#Tue Nov 28 17:23:44 CET 2017 +maxValue=270.0 +minValue=-270.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +unit=deg diff --git a/devices/robot_j5.properties b/devices/robot_j5.properties new file mode 100644 index 0000000..4bb81fd --- /dev/null +++ b/devices/robot_j5.properties @@ -0,0 +1,9 @@ +#Tue Nov 28 17:23:42 CET 2017 +maxValue=132.5 +minValue=-122.5 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +unit=deg diff --git a/devices/robot_j6.properties b/devices/robot_j6.properties new file mode 100644 index 0000000..54507e0 --- /dev/null +++ b/devices/robot_j6.properties @@ -0,0 +1,9 @@ +#Tue Nov 28 17:23:43 CET 2017 +maxValue=270.0 +minValue=-270.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=false +scale=1.0 +unit=deg diff --git a/devices/robot_rx.properties b/devices/robot_rx.properties new file mode 100644 index 0000000..69e8d2c --- /dev/null +++ b/devices/robot_rx.properties @@ -0,0 +1,9 @@ +#Thu Nov 23 17:23:52 CET 2017 +maxValue=180.0 +minValue=-180.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=true +scale=1.0 +unit=deg diff --git a/devices/robot_ry.properties b/devices/robot_ry.properties new file mode 100644 index 0000000..51daa4a --- /dev/null +++ b/devices/robot_ry.properties @@ -0,0 +1,9 @@ +#Thu Nov 23 17:23:32 CET 2017 +maxValue=180.0 +minValue=-180.0 +offset=0.0 +precision=-2 +resolution=0.05 +rotation=true +scale=1.0 +unit=deg diff --git a/devices/robot_rz.properties b/devices/robot_rz.properties new file mode 100644 index 0000000..11604f5 --- /dev/null +++ b/devices/robot_rz.properties @@ -0,0 +1,9 @@ +#Tue Nov 28 16:56:22 CET 2017 +maxValue=360.0 +minValue=-360.0 +offset=0.0 +precision=2 +resolution=0.1 +rotation=true +scale=1.0 +unit=deg diff --git a/devices/robot_x.properties b/devices/robot_x.properties new file mode 100644 index 0000000..4dbffac --- /dev/null +++ b/devices/robot_x.properties @@ -0,0 +1,9 @@ +#Tue Sep 12 15:07:33 CEST 2017 +maxValue=1000.0 +minValue=-1000.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=false +scale=1.0 +unit=mm diff --git a/devices/robot_y.properties b/devices/robot_y.properties new file mode 100644 index 0000000..4bc4e79 --- /dev/null +++ b/devices/robot_y.properties @@ -0,0 +1,9 @@ +#Tue Sep 12 15:07:33 CEST 2017 +maxValue=1000.0 +minValue=-1000.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=false +scale=1.0 +unit=null diff --git a/devices/robot_z.properties b/devices/robot_z.properties new file mode 100644 index 0000000..a3126c5 --- /dev/null +++ b/devices/robot_z.properties @@ -0,0 +1,9 @@ +#Tue Sep 12 15:07:32 CEST 2017 +maxValue=1000.0 +minValue=-1000.0 +offset=0.0 +precision=2 +resolution=0.05 +rotation=false +scale=1.0 +unit=null diff --git a/plugins/LaserUE.java b/plugins/LaserUE.java index e733781..ad0c03e 100644 --- a/plugins/LaserUE.java +++ b/plugins/LaserUE.java @@ -1,10 +1,9 @@ +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 java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; /* * @@ -14,40 +13,64 @@ import java.util.logging.Logger; * */ public class LaserUE extends SerialPortDevice{ - + final Readable readable; + static final double RANGE_MIN = 1.0; + static final double RANGE_MAX = 30.0; public LaserUE(String name, String port) { super(name, port, 921600, SerialPortDeviceConfig.DataBits.DB_8, SerialPortDeviceConfig.StopBits.SB_1, SerialPortDeviceConfig.Parity.None); 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 + } + + public Readable getReadable(){ + return readable; } int value = 0; int count = 0; - @Override protected void onByte(int rx) { - if (rx<0){ - rx+=256; - rx|=BIT7; - } - System.out.println(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; - this.setCache(value); + double val = ((double)value)/1000; + if ((valRANGE_MAX)){ + val = Double.NaN; + } + setCache(val); count = 0; } else { count = 0; } } else{ - count = 0; + count = 0; } } + + } diff --git a/plugins/MXSC-1.7.0.jar b/plugins/MXSC-1.7.0.jar index 2325f3be6a500f359642501c7e013858125bb070..a4c4d75b40fb452e2eb73fcd2a2f1dc4d10e5102 100644 GIT binary patch delta 642 zcmbQz&orfK7HU=jwe6y#3RLW;^I|A#ft$A=#Ju<6+o5__MRqeo#4KZ7x%fbi zw>)vEb`A>zgF5Hr#2m5Bi_$kjT@jx15vtawpn?mmYx0RIO$g&_l>x*Y&FZ^M93XRC z*;^7O^VRtaaQNv47?^|_m~1yP*=A%AHaWM>1EOhXoiR9oCx583gNPW_>p~dG^@b3} zjCwN&<5ImVgdyGF4Pj(8_&^xDU<|HCe~3tIqZfp+x)G}8H%!E{$sHm!1;)7F6b2Eo zY=&CV)a(QiInfL?N36vGA`;aCwS6Uw@uS5PBIVi&)!qkVTyKS%Bim*P)!qg*XI|S> KL@-&mM*;v}pybT} delta 642 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 literal 0 HcmV?d00001 diff --git a/plugins/RobotPanel.form b/plugins/RobotPanel.form new file mode 100644 index 0000000..db2bf43 --- /dev/null +++ b/plugins/RobotPanel.form @@ -0,0 +1,450 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/RobotPanel.java b/plugins/RobotPanel.java new file mode 100644 index 0000000..edbd65a --- /dev/null +++ b/plugins/RobotPanel.java @@ -0,0 +1,543 @@ + +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.lang.reflect.Method; +import java.util.Map; + +/** + * + */ +public class RobotPanel extends DevicePanel { + + /** + * Creates new form RobotPanel + */ + public RobotPanel() { + initComponents(); + } + + @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; + Integer speed = null; + String task = null; + String mode = 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 { + speed = (Integer) status.get("speed"); + } 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)); + 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)); + //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") + // //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 javax.swing.JSpinner(); + 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(); + panelState = new ch.psi.pshell.swing.DeviceStatePanel(); + + 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) + .addComponent(spinnerSpeed, 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)) + .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 + + 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)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(textTask)) + .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}); + + 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)) + ); + + 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) + ); + 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(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)) + ); + }// //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 + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton butonStop; + private javax.swing.JButton buttonAbort; + private javax.swing.JButton buttonDisable; + private javax.swing.JButton buttonEnable; + 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 jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + 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 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 ch.psi.pshell.swing.DeviceStatePanel panelState; + private javax.swing.JSpinner spinnerSpeed; + private javax.swing.JTextField textMode; + private javax.swing.JTextField textTask; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/Wayne.form b/plugins/Wayne.form new file mode 100644 index 0000000..f6456c5 --- /dev/null +++ b/plugins/Wayne.form @@ -0,0 +1,46 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/Wayne.java b/plugins/Wayne.java new file mode 100644 index 0000000..955c0e5 --- /dev/null +++ b/plugins/Wayne.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014-2017 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 Wayne extends Panel { + + public Wayne() { + 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jButton1 = new javax.swing.JButton(); + + jButton1.setText("jButton1"); + 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() + .addGap(18, 18, 18) + .addComponent(jButton1) + .addContainerGap(358, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(29, 29, 29) + .addComponent(jButton1) + .addContainerGap(85, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + try { + this.runAsync("test/RobotCartesianScan"); + } catch (Exception ex) { + Logger.getLogger(Wayne.class.getName()).log(Level.SEVERE, null, ex); + } + }//GEN-LAST:event_jButton1ActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + // End of variables declaration//GEN-END:variables +} diff --git a/script/calibration/ScanRZ.py b/script/calibration/ScanRZ.py new file mode 100644 index 0000000..acb04c5 --- /dev/null +++ b/script/calibration/ScanRZ.py @@ -0,0 +1,21 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +RANGE = [-20.0,20.0] +STEP = 1.0 +LATENCY = 0.005 + +robot.enable() +move_to_laser() + + + + + + +robot.set_motors_enabled(True) +ret = lscan(robot_rz, ue.readable, RANGE[0], RANGE[1], STEP, latency = LATENCY, relative = True, range = "auto") + + + + diff --git a/script/calibration/ScanX.py b/script/calibration/ScanX.py new file mode 100644 index 0000000..71eecb6 --- /dev/null +++ b/script/calibration/ScanX.py @@ -0,0 +1,37 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +robot.enable() +move_to_laser() + + +RANGE = [-1.5, 1.5] +STEP = 0.025 +Z_OFFSET = -1.0 +LATENCY = 0.005 + +robot.set_motors_enabled(True) +current_positon = robot_x.getPosition() +robot_z.moveRel(Z_OFFSET) +ret = lscan(robot_x, ue.readable, RANGE[0], RANGE[1], STEP, latency = LATENCY, relative = True) + +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 X detection") + + +center_index = (first_index + last_index)/2 +center_positon = ret.getPositions(0)[center_index] +center_offset = center_positon-current_positon + + +print "X offset = ", center_offset \ No newline at end of file diff --git a/script/calibration/ScanXZ.py b/script/calibration/ScanXZ.py new file mode 100644 index 0000000..96cbcd6 --- /dev/null +++ b/script/calibration/ScanXZ.py @@ -0,0 +1,79 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + +SINGLE_PASS = False +if SINGLE_PASS: + STEP_SIZE = 0.5 +else: + STEP_SIZE = 1.0 +RANGE = [-5.0, 5.0] +LATENCY = 0.005 + +robot.enable() +move_to_laser() + +step_x = STEP_SIZE +step_z = STEP_SIZE +range_x = [RANGE[0], RANGE[1]] +range_z = [RANGE[0], RANGE[1]] + +robot.set_motors_enabled(True) +current_x = robot_x.getPosition() +current_z = robot_z.getPosition() + +print "Current pos x,z" , current_x, ",", current_z +ret = ascan([robot_x, robot_z], ue.readable, [range_x[0], range_z[0]], [range_x[1], range_z[1]], [step_x,step_z], latency = LATENCY, relative = True , zigzag=False) +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_x[0], range_x[1], step_x , False, True) +p = plot(integ, title = "Fit", xdata=xdata)[0] + + +max_x_index = integ.index(max(integ)) +max_x = xdata[max_x_index] +(normalization, mean_val, sigma) = fit_gaussian(integ, xdata) +gaussian = Gaussian(normalization, mean_val, sigma) +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 X detection") +x_offset = mean_val +center_x = current_x + x_offset + +print "X offset = ", x_offset + + +if SINGLE_PASS: + z_scan_data = data[max_x_index] +else: + robot_x.move(center_x) + step_z = step_z/4.0 + 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 + +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..26e2cac --- /dev/null +++ b/script/calibration/ToolCalibration.py @@ -0,0 +1,42 @@ +import plotutils +from mathutils import fit_gaussian, Gaussian + + +robot.enable() +move_to_laser() +robot.set_motors_enabled(True) + + +initial_pos = robot.get_cartesian_pos() + + + +run("calibration/ScanXZ") + +robot.set_motors_enabled(True) +robot_x.moveRel(x_offset) +robot_z.moveRel(z_offset) + + +initial_x = robot_x.take() +initial_z = robot_z.take() +initial_y = ue.take() +if initial_y is None: + raise Exception("Invalid XZ scan values") + + + + + + +#update_tool(None, x_offset=x_offset, z_offset=z_offset) + +current_x = robot_x.getPosition() +current_y = robot_x.getPosition() +current_z = robot_z.getPosition() + + + +#Updating tool: +#update_tool(None, x_offset=x_offset, z_offset=z_offset) + diff --git a/script/devices/RobotMotors.py b/script/devices/RobotMotors.py new file mode 100644 index 0000000..2b0ad33 --- /dev/null +++ b/script/devices/RobotMotors.py @@ -0,0 +1,73 @@ + +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(None, None) + self.setCache(self.doRead(), None) + + def doRead(self): + return float("nan") if self.robot.cartesian_destination is None else float(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.updating = True + try: + 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) + except: + self.robot.updating = False + + def doReadReadback(self): + #return float(self.robot.get_cartesian_pos(self._get_tool(),self.frame)[self.index]) + 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.updating = True + try: + self.robot.set_jnt(joint, name="tcp_j") + self.robot.movej("tcp_j", self.robot.tool , DESC_SCAN) + finally: + self.robot.updating = False + + + 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 index fa809ee..c713e72 100644 --- a/script/devices/RobotSC.py +++ b/script/devices/RobotSC.py @@ -1,9 +1,25 @@ +TOOL_CALIBRATION = "tCalib" +TOOL_SUNA= "tSuna" +TOOL_DEFAULT= TOOL_SUNA + +DESC_FAST = "mFast" +DESC_SLOW = "mSlow" +DESC_SCAN = "mScan" +DESC_DEFAULT = DESC_FAST + + run("devices/RobotTCP") -simulation = True + +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.tool = TOOL_CALIBRATION + def mount(self, puck, sample): return self.execute('mount',segment, puck, sample) @@ -22,10 +38,11 @@ class RobotSC(RobotTCP): RobotTCP.doUpdate(self) global simulation if not simulation: - if self.state != State.Offline: - self.get_joint_forces() - for dev in [jf1, jf2, jf3, jf4,jf5, jf6, jfc]: - dev.update() + 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): @@ -35,20 +52,30 @@ class RobotSC(RobotTCP): 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 set_tool(self,tool): + if tool != self.tool: + self.tool = tool + for dev in (robot_x, robot_y, robot_z, robot_rx, robot_ry, robot_rz): + dev.initialize() + dev.update() - - if simulation: - add_device(RobotSC("robot","129.129.126.92:1000"),force = True) -#add_device(RobotSC("robot","129.129.126.74:1000"),force = True) + #add_device(RobotSC("robot","129.129.126.92:1000"),force = True) + add_device(RobotSC("robot","129.129.110.99:1000"),force = True) else: - add_device(RobotSC("robot", "129.129.126.100:1000"), force = True) + add_device(RobotSC("robot", "129.129.110.100:1000"), force = True) robot.high_level_tasks = ["mount", "firstmount"] -robot.setPolling(20) +robot.setPolling(500) #robot.set_monitor_speed(20) @@ -79,10 +106,15 @@ class jfc(ReadonlyRegisterBase): return float('NaN') return (robot.joint_forces[1]+74)/4 + (robot.joint_forces[2]+30)/4 + (robot.joint_forces[4]-0.8)/0.2 -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) +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 index 2260cb2..4738ab8 100644 --- a/script/devices/RobotTCP.py +++ b/script/devices/RobotTCP.py @@ -1,5 +1,10 @@ import threading +FRAME_DEFAULT = "world" + + +run("devices/RobotMotors") + class RobotTCP(TcpDevice, Stoppable): def __init__(self, name, server, timeout = 1000, retries = 1): TcpDevice.__init__(self, name, server) @@ -20,11 +25,36 @@ class RobotTCP(TcpDevice, Stoppable): self.lock = threading.Lock() self.joint_forces = None self.current_task = None - self.high_level_tasks = [] + self.high_level_tasks = [] + self.cartesian_destination = None + self.cartesian_pos = None + self.joint_pos = None + self.cartesian_motors_enabled = False + self.cartesian_motors = [] + self.joint_motors_enabled = False + self.joint_motors = [] + self.tool = None + self.frame = FRAME_DEFAULT + self.updating = False + def doInitialize(self): super(RobotTCP, self).doInitialize() self.reset = True + + + def set_tool(self,tool): + self.tool = tool + if self.cartesian_motors_enabled: + self.cartesian_pos = self.get_cartesian_pos() + for m in self.cartesian_motors: + m.initialize() + m.update() + + + def get_tool(self): + return self.tool + def _sendReceive(self, msg_id, msg = "", timeout = None): tx = self.header if (self.header != None) else "" @@ -106,13 +136,13 @@ class RobotTCP(TcpDevice, Stoppable): for i in range(size): ret.append(float(a[i])); return ret - def get_trf(self, name="tcp_t"): + 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_tr(self, l, name="tcp_t"): + 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"): @@ -125,10 +155,26 @@ class RobotTCP(TcpDevice, Stoppable): 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 + #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: + name = self.tool + return self.get_trsf(name+".trsf") + + def set_tool_trsf(self, trsf, name=None): + if name is None: + name = self.tool + self.set_trsf(trsf, name+".trsf") def eval_int(self, cmd): self.evaluate("tcp_n=" + cmd) @@ -152,7 +198,7 @@ class RobotTCP(TcpDevice, Stoppable): def eval_trf(self, cmd): self.evaluate("tcp_t=" + cmd) - return self.get_trf() + return self.get_trsf() def eval_pnt(self, cmd): self.evaluate("tcp_p=" + cmd) @@ -165,16 +211,19 @@ class RobotTCP(TcpDevice, Stoppable): return self.powered def enable(self): - self.evaluate("enablePower()") if not self.is_powered(): - raise Exception("Cannot enable power") - + 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) + ")") @@ -185,6 +234,10 @@ class RobotTCP(TcpDevice, Stoppable): 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): if mode==1: self.working_mode = "manual" @@ -205,7 +258,7 @@ class RobotTCP(TcpDevice, Stoppable): def read_working_mode(self): try: mode = self.eval_int("workingMode(tcp_a)") - status = int(self.get_var("tcp_a[0]")) + status = int(self.get_var("tcp_a[0]")) self._update_working_mode(mode, status) self._update_state() except: @@ -232,8 +285,8 @@ class RobotTCP(TcpDevice, Stoppable): def resume(self): self.evaluate("restartMove()") - def resetMotion(self): - self.evaluate("resetMotion()") + def reset_motion(self, joint=None): + self.evaluate("resetMotion()" if (joint is None) else ("resetMotion(" + joint + ")")) def is_empty(self): self.empty = self.eval_bool("isEmpty()") @@ -243,7 +296,7 @@ class RobotTCP(TcpDevice, Stoppable): def is_settled(self): self.settled = self.eval_bool("isSettled()") self._update_state() - return self.powered + return self.settled def get_move_id(self): return self.eval_int("getMoveId()") @@ -289,7 +342,7 @@ class RobotTCP(TcpDevice, Stoppable): return self.eval_float("distance(" + trsf1 + ", " + trsf2 + ")") def distance_p(self, pnt1, pnt2): - return self.eval_float("distance(" + pnt1 + ", " + pnt2 + ")") + return self.eval_float("distance(" + pnt1 + ", " + pnt2 + ")") def compose(self, pnt, frame, trsf): return self.eval_pnt("compose(" + pnt + ", " + frame + ", " + trsf + ")") @@ -297,16 +350,24 @@ class RobotTCP(TcpDevice, Stoppable): def here(self, tool, frame): return self.eval_pnt("here(" + tool + ", " + frame + ")") - def joint_to_point(self, tool, frame, joint): + def joint_to_point(self, tool, frame, joint="tcp_j"): return self.eval_pnt("jointToPoint(" + tool + ", " + frame + ", " + joint +")") - def point_to_joint(self, tool, initial_joint, point): + def point_to_joint(self, tool, initial_joint="tcp_j", point="tcp_p"): if self.eval_bool("pointToJoint(" + tool + ", " + initial_joint + ", " + point +", j)"): return self.get_jnt() def position(self, point, 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) @@ -332,7 +393,8 @@ class RobotTCP(TcpDevice, Stoppable): #waits for the task to be actually killed def task_kill(self, name): - self.evaluate('taskKill("' + str(name)+ '")', timeout = 2000) + #self.evaluate('taskKill("' + str(name)+ '")', timeout = 5000) + self.execute('kill', str(name), timeout=5000) def get_task_status(self, name): code = self.eval_int('taskStatus("' + str(name)+ '")') @@ -381,14 +443,83 @@ class RobotTCP(TcpDevice, Stoppable): "speed": self.speed, "empty": self.empty, "settled": self.settled, - "task": self.current_task }, None) - - #print time.time() - start + "task": self.current_task, + "mode": self.working_mode, + "status": self.status + }, None) + if not self.updating: + if self.cartesian_motors_enabled: + self.cartesian_pos = self.get_cartesian_pos() + for m in self.cartesian_motors: + #m.update() + m.readback.update() + else: + self.cartesian_pos = None + if self.joint_motors_enabled: + self.joint_pos = self.herej() + 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): + return self.eval_pnt("jointToPoint(" + self.tool + ", " + self.frame + ", herej())") + #self.set_jnt(self.herej()) + #return self.joint_to_point(tool, frame) + + def get_cartesian_destination(self): + return self.here(self.tool, self.frame) + + def get_distance_to_pnt(self, name): + #self.here(self.tool, self.frame) #??? + self.get_cartesian_pos() + return self.distance_p("tcp_p", name) + + #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 = [] + self.joint_destination = None + else: + if value: + self.joint_destination = self.get_joint_destination() + for m in self.joint_motors: + m.initialize() + + #High-level, exclusive motion task. def start_task(self, program, *args, **kwargs): tasks = [t for t in self.high_level_tasks] @@ -410,8 +541,7 @@ class RobotTCP(TcpDevice, Stoppable): self._update_state() def stop_task(self): - #tasks = [t for t in self.high_level_tasks] - tasks = [] + 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: @@ -426,7 +556,7 @@ class RobotTCP(TcpDevice, Stoppable): self.current_task = task return task return None - + def on_event(self,ev): pass diff --git a/script/local.py b/script/local.py index 314bdbf..546724f 100644 --- a/script/local.py +++ b/script/local.py @@ -17,6 +17,7 @@ run("devices/RobotSC") #run("devices/RobotModbus") #run("devices/OneWire") + #Raspberry login: usr=pi pwd=Buntschu add_device(img.getContrast(), force = True) @@ -31,6 +32,12 @@ def set_led_range(room_temp = True): led_ctrl2.config.save() +################################################################################################### +# Motion modules +################################################################################################### + +run("motion/tools") + ################################################################################################### # Barcode reader @@ -155,9 +162,9 @@ def fit(ydata, xdata = None, draw_plot = True): 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) + #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" ) @@ -191,12 +198,12 @@ def fit(ydata, xdata = None, draw_plot = True): 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) + #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) + #print "Invalid gaussian fit: " + str(mean) return (None, None, None) context = get_context() diff --git a/script/motion/tools.py b/script/motion/tools.py new file mode 100644 index 0000000..7706876 --- /dev/null +++ b/script/motion/tools.py @@ -0,0 +1,60 @@ +POSITION_TOLERANCE = 50 + +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_home(): + #robot.reset_motion("jHome") + robot.movej("pHome", robot.tool , DESC_SCAN) + wait_end_of_move() + +def move_to_laser(): + tool = robot.tool + d = robot.get_distance_to_pnt("pLaser") + if d<0: + raise Exception ("Error calculating distance to laser: " + str(d)) + if d