package org.reprap.devices; import java.io.IOException; import org.reprap.utilities.Debug; import org.reprap.Device; import org.reprap.Preferences; import org.reprap.AxisMotor; import org.reprap.ReprapException; import org.reprap.comms.Address; import org.reprap.comms.Communicator; import org.reprap.comms.IncomingContext; import org.reprap.comms.OutgoingMessage; import org.reprap.comms.IncomingMessage; import org.reprap.comms.IncomingMessage.InvalidPayloadException; import org.reprap.comms.messages.IncomingIntMessage; import org.reprap.comms.messages.OutgoingAddressMessage; import org.reprap.comms.messages.OutgoingBlankMessage; import org.reprap.comms.messages.OutgoingByteMessage; import org.reprap.comms.messages.OutgoingIntMessage; import org.reprap.comms.messages.VersionRequestMessage; import org.reprap.comms.messages.VersionResponseMessage; /** * */ public class SNAPStepperMotor extends GenericStepperMotor { /** * Communicator * */ private Communicator communicator = null; /** * API for firmware * Activate the stepper motor in forward direction */ public static final byte MSG_SetForward = 1; /** * Activate the stepper motor in reverse direction */ public static final byte MSG_SetReverse = 2; /** * Set the stepper motor position (how?) */ public static final byte MSG_SetPosition = 3; /** * Get the current stepper motor position */ public static final byte MSG_GetPosition = 4; /** * */ public static final byte MSG_Seek = 5; /** * Set the motor to idle; this turns the torque off whereas speed = 0 keeps torque on */ public static final byte MSG_SetIdle = 6; /** * Set notification (?) */ public static final byte MSG_SetNotification = 7; /** * Set the sync mode (?) */ public static final byte MSG_SetSyncMode = 8; /** * Calibrate (?) */ public static final byte MSG_Calibrate = 9; /** * Get the range (?) */ public static final byte MSG_GetRange = 10; /** * DDAMaster ? */ public static final byte MSG_DDAMaster = 11; /** * Move on step in forward direction */ public static final byte MSG_StepForward = 12; /** * Move on step in backward direction */ public static final byte MSG_StepBackward = 13; /** * Set the power to the stepper motor */ public static final byte MSG_SetPower = 14; /** * Homereset(?? */ public static final byte MSG_HomeReset = 16; /** * Stick a point in the input buffer (firmware v1.4 and greater) */ public static final byte MSG_QueuePoint = 17; /** * What's the thing up to? */ public static final byte MSG_GetStatus = 18; /** * What the firmware sends when the queue is active */ public static final byte modeQueue = 7; /** * */ public static final byte SYNC_NONE = 0; public static final byte SYNC_SEEK = 1; public static final byte SYNC_INC = 2; public static final byte SYNC_DEC = 3; /** * */ private boolean haveInitialised = false; private boolean haveSetNotification = false; private boolean haveCalibrated = false; /** * Power output limiting (0-100 percent) */ private int maxTorque; /** * */ private int firmwareVersion = 0; private static final int firmwareVersionForBuffering = (1 << 8) + 4; // Version 1.4 /** * our snap comms device. */ private Device snap; private boolean wasAlive = false; /** * @param communicator * @param address * @param prefs * @param motorId */ public SNAPStepperMotor(Communicator com, Address address, int motorId) { super(address, motorId); communicator = com; snap = new Device(communicator, address); isAvailable(); refreshPreferences(); if(!isAvailable()) return; try { firmwareVersion = getVersion(); } catch (Exception ex) {} Debug.d("Stepper " + axis + " firmware is version: " + ((firmwareVersion >> 8) & 0xff) + "." + (firmwareVersion & 0xff)); } public boolean isAvailable() { try { getVersion(); } catch (Exception ex) { wasAlive = false; return false; } wasAlive = true; return true; } /** * Result of last call to isAvailable(), which we don't want to * call repeatedly as each call polls the device. * @return */ public boolean wasAvailable() { return wasAlive; } /** * @return the communicator */ public Communicator getCommunicator() { return communicator; } /** * @return Version ID of the firmware the device is running * @throws IOException * @throws InvalidPayloadException */ public int getVersion() throws IOException, InvalidPayloadException { VersionRequestMessage request = new VersionRequestMessage(); IncomingContext replyContext = sendMessage(request); VersionResponseMessage reply = new VersionResponseMessage(replyContext); return reply.getVersion(); } /** * @param message * @return incoming context * @throws IOException */ public IncomingContext sendMessage(OutgoingMessage message) throws IOException { return communicator.sendMessage(this, message); } /** * Method to lock communication to this device. *

TODO: when called?

*/ public void lock() { communicator.lock(); } /** * Method to unlock communication to this device *

TODO: when called?

* */ public void unlock() { communicator.unlock(); } //****************************************************************** public void refreshPreferences() { try { this.maxTorque = Preferences.loadGlobalInt(axis + "AxisTorque(%)"); } catch (Exception ex) { System.err.println("Refresh motor preferences: " + ex.toString()); } } /** * @throws IOException */ private void initialiseIfNeeded() throws IOException { if (!haveInitialised) { haveInitialised = true; setMaxTorque(maxTorque); } } /** * Dispose of this object */ public void dispose() { } private int getStatus() throws IOException { int value; communicator.lock(); try { IncomingContext replyContext = communicator.sendMessage(snap, new OutgoingBlankMessage(MSG_GetStatus)); IncomingIntMessage reply = new RequestStatusResponse(replyContext); try { value = reply.getValue(); } catch (IncomingMessage.InvalidPayloadException ex) { throw new IOException(ex.getMessage()); } } finally { communicator.unlock(); } //System.out.println("status: " + (byte)(value >> 8) + ", " + (byte)(value & 0xff)); return value; } /** * * */ private void waitTillQueueNotFull() throws IOException { if(firmwareVersion < firmwareVersionForBuffering) return; int value = getStatus(); while((value >> 8) != 0) { printer.machineWait(500); value = getStatus(); } } /** * * */ public void waitTillNotBusy() throws IOException { if(firmwareVersion < firmwareVersionForBuffering) return; int value = getStatus(); while((value & 0xff) == modeQueue) { //System.out.println("busy: " + value); printer.machineWait(500); value = getStatus(); } } /** * Add an XY point to the firmware buffer for plotting * Only works for recent firmware. * * @param endX * @param endY * @param movementSpeed * @return */ public boolean queuePoint(int endX, int endY, int movementSpeed, int control) throws IOException { // Firmware clever enough? if(firmwareVersion < firmwareVersionForBuffering) return false; if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return false; } initialiseIfNeeded(); waitTillQueueNotFull(); //System.out.println("control: " + control); communicator.lock(); Debug.d(axis + " axis - queuing point at speed " + movementSpeed + ", control = " + control + ", endX = " + endX + ", endY = " + endY); try { OutgoingMessage request = new RequestQueue(endX, endY, movementSpeed, control); communicator.sendMessage(snap, request); } finally { communicator.unlock(); } return true; } /** * Set the motor speed (or turn it off) * @param speed A value between -255 and 255. * @throws ReprapException * @throws IOException */ public void setSpeed(int speed) throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - setting speed: " + speed); try { OutgoingMessage request = new RequestSetSpeed(speed); communicator.sendMessage(snap, request); } finally { communicator.unlock(); } } /** * @throws IOException */ public void setIdle() throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - going idle."); try { OutgoingMessage request = new RequestSetSpeed(); communicator.sendMessage(snap, request); } finally { communicator.unlock(); } } /** * @throws IOException */ public void stepForward() throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - stepping forward."); try { OutgoingMessage request = new RequestOneStep(true); communicator.sendMessage(snap, request); } finally { communicator.unlock(); } } /** * @throws IOException */ public void stepBackward() throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - stepping backward."); try { OutgoingMessage request = new RequestOneStep(false); communicator.sendMessage(snap, request); } finally { communicator.unlock(); } } /** * @throws IOException */ public void resetPosition() throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } setPosition(0); } /** * @param position * @throws IOException */ public void setPosition(int position) throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - setting position to: " + position); try { communicator.sendMessage(snap, new RequestSetPosition(position)); } finally { communicator.unlock(); } } /** * @return current position of the motor * @throws IOException */ public int getPosition() throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return 0; } int value; initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - getting position. It is... "); try { IncomingContext replyContext = communicator.sendMessage(snap, new OutgoingBlankMessage(MSG_GetPosition)); IncomingIntMessage reply = new RequestPositionResponse(replyContext); try { value = reply.getValue(); } catch (IncomingMessage.InvalidPayloadException ex) { throw new IOException(ex.getMessage()); } } finally { communicator.unlock(); } Debug.d("..." + value); return value; } /** * @param speed * @param position * @throws IOException */ public void seek(int speed, int position) throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - seeking position " + position + " at speed " + speed); try { communicator.sendMessage(snap, new RequestSeekPosition(speed, position)); } finally { communicator.unlock(); } } /** * @param speed * @param position * @throws IOException */ public void seekBlocking(int speed, int position) throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - seeking-blocking position " + position + " at speed " + speed); try { setNotification(); new RequestSeekResponse(snap, new RequestSeekPosition(speed, position), 50000); setNotificationOff(); } catch (Exception e) { // TODO: Nasty error. But WTF do we do about it? e.printStackTrace(); } finally { communicator.unlock(); } } /** * @param speed * @return range of the motor * @throws IOException * @throws InvalidPayloadException */ public AxisMotor.Range getRange(int speed) throws IOException, InvalidPayloadException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return new AxisMotor.Range(); } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - getting range."); try { if (haveCalibrated) { IncomingContext replyContext = communicator.sendMessage(snap, new OutgoingBlankMessage(MSG_GetRange)); RequestRangeResponse response = new RequestRangeResponse(replyContext); return response.getRange(); } else { setNotification(); IncomingContext replyContext = communicator.sendMessage(snap, new OutgoingByteMessage(MSG_Calibrate, (byte)speed)); RequestRangeResponse response = new RequestRangeResponse(replyContext); setNotificationOff(); return response.getRange(); } } finally { communicator.unlock(); } } /** * @param speed * @throws IOException * @throws InvalidPayloadException */ public void homeReset(int speed) throws IOException, InvalidPayloadException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - home reset at speed " + speed); try { setNotification(); new RequestHomeResetResponse(snap, new OutgoingByteMessage(MSG_HomeReset, (byte)speed), 60000); setNotificationOff(); } finally { communicator.unlock(); } } /** * @param syncType * @throws IOException */ public void setSync(byte syncType) throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - setting sync to " + syncType); try { communicator.sendMessage(snap, new OutgoingByteMessage(MSG_SetSyncMode, syncType)); } finally { communicator.unlock(); } } /** * @param speed * @param x1 * @param deltaY * @throws IOException */ public void dda(int speed, int x1, int deltaY) throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); communicator.lock(); Debug.d(axis + " axis - dda at speed " + speed + ". x1 = " + x1 + ", deltaY = " + deltaY); try { setNotification(); new RequestDDAMasterResponse(snap, new RequestDDAMaster(speed, x1, deltaY), 60000); setNotificationOff(); } finally { communicator.unlock(); } } /** * @throws IOException */ private void setNotification() throws IOException { initialiseIfNeeded(); Debug.d(axis + " axis - setting notification on."); if (!haveSetNotification) { communicator.sendMessage(snap, new OutgoingAddressMessage(MSG_SetNotification, communicator.getAddress())); haveSetNotification = true; } } /** * @throws IOException */ private void setNotificationOff() throws IOException { initialiseIfNeeded() ; Debug.d(axis + " axis - setting notification off."); if (haveSetNotification) { communicator.sendMessage(snap, new OutgoingAddressMessage(MSG_SetNotification, snap.getAddress().getNullAddress())); haveSetNotification = false; } } /** * * @param maxTorque An integer value 0 to 100 representing the maximum torque percentage * @throws IOException */ public void setMaxTorque(int maxTorque) throws IOException { if(!wasAvailable()) { Debug.d("Attempting to control or interrogate non-existent axis drive for " + axis); return; } initialiseIfNeeded(); waitTillNotBusy(); if (maxTorque > 100) maxTorque = 100; double power = maxTorque * 68.0 / 100.0; byte scaledPower = (byte)power; communicator.lock(); Debug.d(axis + " axis - setting maximum torque to: " + maxTorque); try { communicator.sendMessage(snap, new OutgoingByteMessage(MSG_SetPower, scaledPower)); } finally { communicator.unlock(); } } /** * */ protected class RequestPositionResponse extends IncomingIntMessage { /** * @param incomingContext * @throws IOException */ public RequestPositionResponse(IncomingContext incomingContext) throws IOException { super(incomingContext); } /* (non-Javadoc) * @see org.reprap.comms.messages.IncomingIntMessage#isExpectedPacketType(byte) */ protected boolean isExpectedPacketType(byte packetType) { return packetType == MSG_GetPosition; } } /** * */ protected class RequestStatusResponse extends IncomingIntMessage { /** * @param incomingContext * @throws IOException */ public RequestStatusResponse(IncomingContext incomingContext) throws IOException { super(incomingContext); } /* (non-Javadoc) * @see org.reprap.comms.messages.IncomingIntMessage#isExpectedPacketType(byte) */ protected boolean isExpectedPacketType(byte packetType) { return packetType == MSG_GetStatus; } } /** * */ protected class RequestSeekResponse extends IncomingIntMessage { /** * @param device * @param message * @param timeout * @throws IOException */ public RequestSeekResponse(Device device, OutgoingMessage message, long timeout) throws IOException { super(device, message, timeout); } /* (non-Javadoc) * @see org.reprap.comms.messages.IncomingIntMessage#isExpectedPacketType(byte) */ protected boolean isExpectedPacketType(byte packetType) { return packetType == MSG_Seek; } } /** * */ protected class RequestSetPosition extends OutgoingIntMessage { /** * @param position */ public RequestSetPosition(int position) { super(MSG_SetPosition, position); } } protected class RequestSetSpeed extends OutgoingMessage { /** * */ byte [] message; /** * The empty constructor will create a message to idle the motor */ RequestSetSpeed() { message = new byte [] { MSG_SetIdle }; } /** * Create a message for setting the motor speed. * @param speed The speed to set the motor to. Note that specifying * 0 will stop the motor and hold it at 0 and thus still draws * high current. To idle the motor, use the message created by the * empty constructor. */ RequestSetSpeed(int speed) { byte command; if (speed >= 0) { command = MSG_SetForward; } else { command = MSG_SetReverse; speed = -speed; } if (speed > 255) speed = 255; message = new byte[] { command, (byte)speed }; } /* (non-Javadoc) * @see org.reprap.comms.OutgoingMessage#getBinary() */ public byte[] getBinary() { return message; } } /** * */ protected class RequestOneStep extends OutgoingMessage { /** * */ byte [] message; /** * @param forward */ RequestOneStep(boolean forward) { byte command; if (forward) command = MSG_StepForward; else command = MSG_StepBackward; message = new byte[] { command }; } /* (non-Javadoc) * @see org.reprap.comms.OutgoingMessage#getBinary() */ public byte[] getBinary() { return message; } } /** * */ protected class RequestSeekPosition extends OutgoingMessage { /** * */ byte [] message; /** * @param speed * @param position */ RequestSeekPosition(int speed, int position) { message = new byte[] { MSG_Seek, (byte)speed, (byte)(position & 0xff), (byte)((position >> 8) & 0xff)}; } /* (non-Javadoc) * @see org.reprap.comms.OutgoingMessage#getBinary() */ public byte[] getBinary() { return message; } } /** * */ protected class RequestDDAMaster extends OutgoingMessage { byte [] message; /** * @param speed * @param x1 * @param deltaY */ RequestDDAMaster(int speed, int x1, int deltaY) { message = new byte[] { MSG_DDAMaster, (byte)speed, (byte)(x1 & 0xff), (byte)((x1 >> 8) & 0xff), (byte)(deltaY & 0xff), (byte)((deltaY >> 8) & 0xff) }; } /* (non-Javadoc) * @see org.reprap.comms.OutgoingMessage#getBinary() */ public byte[] getBinary() { return message; } } /** * */ protected class RequestDDAMasterResponse extends IncomingIntMessage { /** * @param device * @param message * @param timeout * @throws IOException */ public RequestDDAMasterResponse(Device device, OutgoingMessage message, long timeout) throws IOException { super(device, message, timeout); } /* (non-Javadoc) * @see org.reprap.comms.messages.IncomingIntMessage#isExpectedPacketType(byte) */ protected boolean isExpectedPacketType(byte packetType) { return packetType == MSG_DDAMaster; } } /** * */ protected class RequestQueue extends OutgoingMessage { byte [] message; /** * @param speed * @param x1 * @param deltaY */ RequestQueue(int endX, int endY, int movementSpeed, int control) { message = new byte[] { MSG_QueuePoint, (byte)movementSpeed, (byte)control, (byte)(endX & 0xff), (byte)((endX >> 8) & 0xff), (byte)(endY & 0xff), (byte)((endY >> 8) & 0xff) }; } /* (non-Javadoc) * @see org.reprap.comms.OutgoingMessage#getBinary() */ public byte[] getBinary() { return message; } } /** * */ protected class RequestQueueResponse extends IncomingIntMessage { /** * @param device * @param message * @param timeout * @throws IOException */ public RequestQueueResponse(Device device, OutgoingMessage message, long timeout) throws IOException { super(device, message, timeout); } /* (non-Javadoc) * @see org.reprap.comms.messages.IncomingIntMessage#isExpectedPacketType(byte) */ protected boolean isExpectedPacketType(byte packetType) { return packetType == MSG_QueuePoint; } } /** * */ protected class RequestRangeResponse extends IncomingIntMessage { /** * @param incomingContext * @throws IOException */ public RequestRangeResponse(IncomingContext incomingContext) throws IOException { super(incomingContext); } /* (non-Javadoc) * @see org.reprap.comms.messages.IncomingIntMessage#isExpectedPacketType(byte) */ protected boolean isExpectedPacketType(byte packetType) { // We could get this either as an asynchronous notification // from calibration or by explicit request return packetType == MSG_GetRange || packetType == MSG_Calibrate; } /** * @return * @throws InvalidPayloadException */ public AxisMotor.Range getRange() throws InvalidPayloadException { byte [] reply = getPayload(); if (reply == null || reply.length != 3) throw new InvalidPayloadException("Unexpected payload getting range"); Range r = new AxisMotor.Range(); r.minimum = 0; r.maximum = IncomingIntMessage.ConvertBytesToInt(reply[1], reply[2]); return r; } } /** * */ protected class RequestHomeResetResponse extends IncomingMessage { /** * @param device * @param message * @param timeout * @throws IOException */ public RequestHomeResetResponse(Device device, OutgoingMessage message, long timeout) throws IOException { super(device, message, timeout); } /* (non-Javadoc) * @see org.reprap.comms.IncomingMessage#isExpectedPacketType(byte) */ protected boolean isExpectedPacketType(byte packetType) { return packetType == MSG_HomeReset; } } }