--- /dev/null
+package lejos.devices;\r
+\r
+import java.io.*;\r
+\r
+/*\r
+ * Developer Notes:\r
+ * The following document explains keyboard control:\r
+ * http://www.computer-engineering.org/ps2keyboard/\r
+ */\r
+\r
+/**\r
+ * This class will only work with SPP keyboards, not standard HID\r
+ * keyboards. If it doesn't say it supports Bluetooth SPP then it\r
+ * will not work. There are only two known SPP models (also available\r
+ * on eBay or Amazon):\r
+ * Freedom Universal Bluetooth keyboard\r
+ * http://www.freedominput.com\r
+ * iTech Virtual Keyboard (SPP only)\r
+ * http://www.virtual-laser-keyboard.com/\r
+ * \r
+ * Note: This class is currently only tested with Freedom Universal\r
+ * \r
+ */\r
+// TODO: Create a method that returns a stream of ASCII from keyboard. Thread handles keep alive, auto connect\r
+// and reconnecting if disconnected.\r
+// TODO: Currently only handles make code. Need break code (key release).\r
+// TODO: Repeat when key held down. (See above)\r
+\r
+\r
+// Typematic Key Repeat:\r
+// Typematic delay - 0.25 seconds to 1.00 second (500ms default)\r
+// Typematic rate - 2.0 cps (characters per second) to 30.0 cps (10.9 default)\r
+// "Set Typematic Rate/Delay" (0xF3) command\r
+// Typematic data is not buffered within the keyboard. In the case \r
+// where more than one key is held down, only the last key pressed \r
+// becomes typematic. Typematic repeat then stops when that key is \r
+// released, even though other keys may be held down.\r
+\r
+public class Keyboard extends Thread {\r
+ \r
+ /**\r
+ * Command used by keyboards. Full list of commands can be found here:\r
+ * http://www.computer-engineering.org/ps2keyboard/\r
+ */\r
+ private static int ECHO = 0xEE; \r
+ \r
+ /**\r
+ * Time between echo command sent to keyboard to prevent it\r
+ * from going to sleep. Universal Keyboard sleeps after 5000 ms\r
+ */\r
+ private static int KEEP_ALIVE_DELAY = 4000; // milliseconds\r
+ \r
+ private static int MAX_LISTENERS = 3;\r
+ \r
+ private InputStream in = null;\r
+ private OutputStream out = null;\r
+ \r
+ // Use Vector? Resizable array?\r
+ private KeyListener [] listeners = new KeyListener[MAX_LISTENERS];\r
+ \r
+ /**\r
+ * The index of this array is the Scan Code (e.g. 0x1C) and the\r
+ * value at that index is the appropriate ASCII key (without shift key).\r
+ * NOTE: There is no ASCII Caps Lock for 0x58. There is shift-in and shift-out, but that\r
+ * seems to have a different function than Caps (i.e. Caps only applies to A-Z, but shift \r
+ * applies to number keys displaying symbols).\r
+ * Note: Scan code 0x12 is left-shift, however ASCII has shift-in (0x0F) and shift-out (0x0E) so\r
+ * I'm uncertain how to handle this as a stream. Will have to do some special case statements. This\r
+ * might not really be a factor anyway since shifts probably irrelevant to ASCII stream.\r
+ * Note: ASCII has "Device Control" 1-4 I'm assuming ALT and CTRL fit in here. Maybe Fn. Maybe Windows key. \r
+ * Again, these might be irrelevant to ASCII stream.\r
+ * NOTE: Enter - equivalent to line feed? (ASCII 0x0A) Should stream\r
+ * send two characters: line feed and carriage return?\r
+ * NOTE: There are no ASCII characters for arrow keys.\r
+ */\r
+ // 64 unique keys on Freedom Universal, array is 113, many blanks \r
+ private static byte [] scanCodes = {\r
+ 0,0,0,0,0,0,0,0x14,0,0,0,0,0,0x09,0x60,0, // 0x00\r
+ 0,0x12,0x0F,0x12,0x11,0x71,0x31,0,0,0,0x7A,0x73,0x61,0x77,0x32,0, // 0x10\r
+ 0,0x63,0x78,0x64,0x65,0x34,0x33,0,0,0x20,0x76,0x66,0x74,0x72,0x35,0, // 0x20\r
+ 0,0x6E,0x62,0x68,0x67,0x79,0x36,0,0,0,0x6D,0x6A,0x75,0x37,0x38,0, // 0x30\r
+ 0,0,0x6B,0x69,0x6F,0x30,0x39,0,0,0x2E,0x2F,0x6C,0x3B,0x70,0x2D,0, // 0x40\r
+ 0,0,0x27,0,0x5B,0x3D,0,0,0x00,0x0F,0x0A,0x5D,0x20,0x5C,0,0, // 0x50\r
+ 0,0,0,0,0,0,0x7F,0,0,0,0,0,0,0,0,0, // 0x60\r
+ 0,0x08 // 0x70\r
+ }; \r
+ \r
+ public Keyboard(InputStream in, OutputStream out) {\r
+ this.in = in;\r
+ this.out = out;\r
+ this.setDaemon(true);\r
+ this.start();\r
+ }\r
+ \r
+ // TODO: Make InputStream that implements KeyListener, outputs ASCII\r
+ public void addKeyListener(KeyListener l) {\r
+ // !! Test code until I figure out storage object\r
+ listeners[0] = l;\r
+ }\r
+ \r
+ public void removeKeylistener(KeyListener l) {\r
+ // !! Test code until I figure out storage object\r
+ listeners[0] = null;\r
+ }\r
+ \r
+ private void notifyListeners(KeyEvent e) {\r
+ // !! Test code until I figure out storage object\r
+ if(e.getID() == KeyEvent.KEY_PRESSED)\r
+ listeners[0].keyPressed(e);\r
+ else if(e.getID() == KeyEvent.KEY_RELEASED)\r
+ listeners[0].keyReleased(e);\r
+ else if(e.getID() == KeyEvent.KEY_TYPED)\r
+ listeners[0].keyTyped(e);\r
+ }\r
+ \r
+ /**\r
+ * Converts raw keyboard scan code into ASCII.\r
+ * Works with lower case only.\r
+ * @param scanCode\r
+ * @return the ascii character\r
+ */\r
+ // TODO: Make private\r
+ public static byte getASCII(byte scanCode) {\r
+ if(scanCode < 0) // bit 8 makes value 0\r
+ return 0; // !! Need to handle break code here\r
+ return scanCodes[scanCode]; // !! handles make codes only\r
+ }\r
+ \r
+ public void run() {\r
+ //Debug.out("Keyboard thread started.\n");\r
+ int previousEcho = (int)System.currentTimeMillis();\r
+ while(true) {\r
+ \r
+ // Keep-alive code:\r
+ int now = (int)System.currentTimeMillis();\r
+ if(now - previousEcho >= KEEP_ALIVE_DELAY) {\r
+ try {\r
+ //Debug.out("Echo Sent\n");\r
+ out.write(ECHO);\r
+ out.flush();\r
+ } catch(IOException e) {/*Debug.out("COMMAND EXCEPTION");*/}\r
+ previousEcho = now;\r
+ }\r
+\r
+ // Notifier code:\r
+ try {\r
+ if(in.available() > 0) { // Check if byte available.\r
+ int bval = in.read();\r
+ KeyEvent e = getKeyEvent(bval, now);\r
+ if(e != null) notifyListeners(e); // ignore non-chars\r
+ }\r
+ } catch(IOException e) {/*Debug.out("EXCEPTION");*/}\r
+ Thread.yield();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Helper method to generate keyEvent from scan code from keyboard \r
+ * @return\r
+ */\r
+ private KeyEvent getKeyEvent(int scanCode, int timeStamp) {\r
+ // TEST CODE:\r
+ char curChar = (char)Keyboard.getASCII((byte)scanCode);\r
+ if((byte)scanCode < 0) return null; // if 8th bit on (i.e. key release)\r
+ KeyEvent e = new KeyEvent(this, KeyEvent.KEY_PRESSED, timeStamp, 0, 0, curChar);\r
+ //Debug.out(++sentenceCount + ": " + bval + " = " + (char)Keyboard.getASCII((byte)bval) + "\n");\r
+ //System.out.println("" + (char)Keyboard.getASCII((byte)scanCode));\r
+ return e;\r
+ }\r
+}\r