001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.hardware.turntable;
031
032import java.io.BufferedReader;
033import java.io.IOException;
034import java.io.InputStreamReader;
035import java.io.UnsupportedEncodingException;
036
037import gnu.io.SerialPort;
038
039import org.openimaj.hardware.serial.SerialDevice;
040
041
042/**
043 * A simple controller for our serially connected electronic turntable.
044 * 
045 * Send NNNNNA0 to rotate anticlockwise by NNNNN increments (360/24000th of a degree)
046 * Send NNNNNC0 to rotate clockwise by NNNNN increments (360/24000th of a degree)
047 * 
048 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
049 */
050public class Turntable {
051        protected final static int TICKS_PER_REVOLUTION = 24000;
052        protected final static double TICKS_PER_DEGREE = TICKS_PER_REVOLUTION / 360.0;
053        protected final static double TICKS_PER_RADIAN = TICKS_PER_REVOLUTION / (2.0 * Math.PI);
054
055        protected int currentAngleTicks = 0;
056        protected SerialDevice turntableDevice;
057
058        /**
059         * Default constructor. Opens a connection to the turntable on
060         * the given port.
061         * 
062         * @param port The port
063         * @throws Exception
064         */
065        public Turntable(String port) throws Exception {
066                turntableDevice = new SerialDevice( port, 9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE );
067        }
068
069        /**
070         * Get the current absolute angle in degrees (relative to the
071         * position at initialisation)
072         * @return the absolute angle in degrees
073         */
074        public double getCurrentAngleDegrees() {
075                return currentAngleTicks / TICKS_PER_DEGREE;
076        }
077
078        /**
079         * Get the current absolute angle in radians (relative to the
080         * position at initialisation)
081         * @return the absolute angle in radians 
082         */
083        public double getCurrentAngleRadians() {
084                return currentAngleTicks / TICKS_PER_RADIAN;
085        }
086
087        /**
088         * Rotate the turntable to the given absolute angle in radians 
089         * (relative to the position at initialisation). The turntable 
090         * will take the shortest path to the requested position.
091         * 
092         * @param rads the angle in radians
093         * @throws IOException
094         */
095        public void rotateToRadians(double rads) throws IOException {
096                rotateToDegrees(rads * 180 / Math.PI);
097        }
098
099        /**
100         * Rotate the turntable to the given absolute angle in degrees 
101         * (relative to the position at initialisation). The turntable 
102         * will take the shortest path to the requested position.
103         * 
104         * @param degrees the angle in degrees
105         * @throws IOException
106         */
107        public void rotateToDegrees(double degrees) throws IOException {
108                final double current = getCurrentAngleDegrees();
109                double delta = degrees - current;
110                
111                if (delta > 180)
112                        delta = 360-delta;
113                if (delta < -180)
114                        delta = 360+delta;
115                
116                sendCommand((int)Math.rint(delta * TICKS_PER_DEGREE));
117        }
118
119        /**
120         * Rotate the turntable by the given angle in radians.
121         * Positive angles are clockwise, negative anticlockwise.
122         * 
123         * @param rads the angle in radians
124         * @throws IOException
125         */
126        public void rotateByRadians(double rads) throws IOException {
127                sendCommand((int)Math.rint(rads * TICKS_PER_RADIAN));
128        }
129
130        /**
131         * Rotate the turntable by the given angle in degrees.
132         * Positive angles are clockwise, negative anticlockwise.
133         * 
134         * @param degrees the angle in degrees
135         * @throws IOException
136         */
137        public void rotateByDegrees(double degrees) throws IOException {
138                sendCommand((int)Math.rint(degrees * TICKS_PER_DEGREE));
139        }
140
141        protected void sendCommand(int ticks) throws IOException {
142                if (ticks < 0) {
143                        sendCommand(Math.abs(ticks), false);
144                } else {
145                        sendCommand(ticks, true);
146                }
147        }
148        
149        protected void sendCommand(int ticks, boolean cw) throws IOException {
150                final String dir = cw ? "C" : "A";
151                
152                if (cw) 
153                        currentAngleTicks += ticks;
154                else 
155                        currentAngleTicks -= ticks;
156                
157                if (currentAngleTicks > TICKS_PER_REVOLUTION/2)
158                        currentAngleTicks = TICKS_PER_REVOLUTION - currentAngleTicks;
159                if (currentAngleTicks < -TICKS_PER_REVOLUTION/2)
160                        currentAngleTicks = TICKS_PER_REVOLUTION + currentAngleTicks;
161                
162                try {   
163                        final String cmd = ticks + dir + "0\n";
164                        turntableDevice.getOutputStream().write(cmd.getBytes("US-ASCII"));
165                } catch (final UnsupportedEncodingException e) {
166                        throw new RuntimeException(e);
167                }
168        }
169
170        /**
171         * Close the connection to the turntable. 
172         */
173        public void close() {
174                turntableDevice.close();
175        }
176        
177        /**
178         * Test the turntable
179         * 
180         * @param args
181         * @throws Exception
182         */
183        public static void main( String[] args ) throws Exception {
184                System.out.println("Initializing Turntable");
185                System.out.println("the command \"r 10\" will rotate the turntable to 10 degrees CW relative to the starting point");
186                System.out.println("the command \"i -10\" will rotate the turntable to 10 degrees AW relative to the current point");
187                
188                //Turntable t = new Turntable("/dev/tty.usbserial-FTCXE2RA");
189                final Turntable t = new Turntable("/dev/tty.usbserial");
190
191                System.out.println("Turntable is ready");
192                System.out.println("Current absolute angle is " + t.getCurrentAngleDegrees() + " degrees");
193                
194                final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
195                
196                String s;
197                while ((s = br.readLine()) != null) {
198                        try {
199                                final String[] parts = s.split("\\s");
200                                
201                                if (parts[0].equals("q"))
202                                        break;
203                                
204                                final double ang = Double.parseDouble(parts[1]);
205                                if (parts[0].equals("i"))
206                                        t.rotateByDegrees(ang);
207                                else if (parts[0].equals("r"))
208                                        t.rotateToDegrees(ang);
209                                else
210                                        throw new Exception();
211                                
212                                System.out.println("Rotating to absolute angle of " + t.getCurrentAngleDegrees() + " degrees");
213                        } catch (final Throwable throwable) {
214                                System.out.println("invalid command");
215                        }
216                }
217                
218                System.out.println("Done");
219                System.exit(0);
220        }
221}