/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * SRV1Console.java - base station console for SRV-1 robot * Copyright (C) 2005-2008 Surveyor Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details (www.gnu.org/licenses) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ import java.awt.*; import java.util.*; import java.io.*; import java.net.*; import java.awt.image.*; import java.awt.color.*; import java.awt.event.*; import java.applet.*; //import javax.comm.*; import gnu.io.*; import com.surveyor.wstreamd.*; public class SRV1Console extends Canvas implements ActionListener, KeyListener { private static final boolean DEBUG = true; private OutputStream debug = null; private static Map props = new HashMap(); private static boolean connectOnStartup = false; private MediaTracker tracker = null; private String comPort = null; private Socket s = null; private SerialPort serialPort = null; private OutputStream out = null; private InputStream is = null; private SRVEventLoop eventLoop = null; private FrameDecoder frameDecoder = null; private LogService logService = null; private NotifyService notifyService = null; private Map notifyTokens = new HashMap(); private WCSPush wcsPush = null; private boolean shouldRun = true; private boolean idle = true; private java.util.List commandQ = Collections.synchronizedList(new ArrayList()); static int width = 80; static int height = 192; static int size = width * height; Image image; int pixels[] = new int[size]; MemoryImageSource source; private static Frame f = new Frame ("SRV-1 Console"); private static final String CONFIG_FILE = "srv.config"; private static final String NOTIFY_CONFIG_FILE = "srv_notify.config"; private static String LINESEP = System.getProperty ("line.separator"); private static TextArea log = new TextArea ("[Surveyor SRV-1 Console]" + LINESEP + LINESEP, 10, 25, TextArea.SCROLLBARS_VERTICAL_ONLY); private static Dialog logWindow = new Dialog(f, "SRV-1 Console - Event Log");; private static SRV1Console bv = new SRV1Console(); private Map cmdButtons = new HashMap(); private Map commands = new HashMap(); private int frameX = 0; // coordinates of top left corner of frame private int frameY = 0; private Cursor target; private int[] scanHits = new int[80]; private static final Color SCAN_COLOR = new Color(255, 0, 0, 120); // R,G,B,A private static boolean scanEnabled = false; static { log.setEditable (false); logWindow.setSize(500, 300); logWindow.add(log); logWindow.hide(); logWindow.addWindowListener (new WindowAdapter () { public void windowClosing (WindowEvent evt) { logWindow.hide(); } }); } public SRV1Console() { if (DEBUG) { try { debug = new FileOutputStream("debug/debug.bin"); } catch (Exception e) { e.printStackTrace(); } } } public static SRV1Console getInstance() { return bv; } public static void main (String[]args) { bv.loadConfig(); // read command line parameters if (args.length > 0) { for (int i = 0; i < args.length ; i++) { if ("-c".equalsIgnoreCase(args[i])) { connectOnStartup = true; } else if ("-s".equalsIgnoreCase(args[i])) { scanEnabled = true; } else if ("-p".equalsIgnoreCase(args[i])) { int port = -1; try { port = Integer.parseInt(args[i + 1]); } catch (Exception e) {} if (port > 0) props.put("wcs.port", String.valueOf(port)); } } } f.setBackground(Color.WHITE); f.setLayout (new BorderLayout(3, 3)); f.add("Center", bv); bv.init(); f.pack(); f.show(); f.repaint(); } public void repaint () { paint(this.getGraphics()); } public void paint (Graphics g) { int cW = getWidth(); int cH = getHeight(); if (g != null && image != null) { int iW = image.getWidth(null); int iH = image.getHeight(null); if (iW != width || iH != height) { g.clearRect(0, 0, cW, cH); width = iW; height = iH; setSize(iW, iH); f.pack(); target.recenter(width, height); } frameX = (cW - iW) / 2; frameY = (cH - iH) / 2; g.drawImage(image, frameX, frameY, null); g.setXORMode(Color.WHITE); target.draw(frameX, frameY, g); g.setPaintMode(); paintScanOverlay(g); } else { //trace("paint() - null, g: " + g + ", image: " + image); } } private void paintScanOverlay(Graphics g) { if (!scanEnabled) return; for (int i = 0; i < scanHits.length; i++) { g.setColor(SCAN_COLOR); int x = frameX + (2 * i); int y = height - 2 - (2 * scanHits[i]); g.drawLine(x, y, x, y + 2); g.drawLine(x + 1, y, x + 1, y + 2); } } private void initRawBuffer() { source = new MemoryImageSource(width, height, pixels, 0, width); source.setAnimated(true); image = createImage(source); } public void init() { tracker = new MediaTracker(this); // The window has become active. The item // that wishes to have the initial focus must request it here. // Note, requestFocusInWindow is prefered to requestFocus in Java 1.4. f.addWindowListener(new WindowAdapter() { private void setupKeyListener() { String enableKB = (String) props.get("enable_keyboard"); if (enableKB == null) enableKB = (String) props.get("ENABLE_KEYBOARD"); if (enableKB == null || "1".equals(enableKB)) { // clear any old listeners, to prevent duplicate events KeyListener[] kl = bv.getKeyListeners(); for (int i = 0; i < kl.length; i++) { bv.removeKeyListener(kl[i]); } bv.addKeyListener(bv); } } public void windowOpened(WindowEvent e) { setupKeyListener(); bv.requestFocusInWindow(); } public void windowActivated(WindowEvent e) { bv.requestFocusInWindow(); } }) ; // handle window close f.addWindowListener (new WindowAdapter () { public void windowClosing (WindowEvent evt) { shutdown(); System.exit(0); } }); eventLoop = new SRVEventLoop(); eventLoop.start(); frameDecoder = new FrameDecoder(); frameDecoder.start(); logService = new LogService(); logService.start(); notifyService = new NotifyService(); notifyService.start(); Thread wcs = new WCSServer(); wcs.start(); wcsPush = new WCSPush ((String) props.get("wcs.server"), (String) props.get("wcs.port"), (String) props.get("wcs.camID"), (String) props.get("wcs.pass")); wcsPush.start(); while (StreamServer.getInstance() == null) try { Thread.sleep(1); } catch (InterruptedException ie) { } StreamServer.getInstance().setContextObj(bv); setSize(width, height); pixels = new int[width * height]; int index = 0; for (int y = 0; y < height; y++) { int red = (y * 255) / (height - 1); for (int x = 0; x < width; x++) { int blue = (x * 255) / (width - 1); pixels[index++] = (255 << 24) | (red << 16) | blue; } } initRawBuffer(); Panel p = new Panel(new GridLayout (0, 1)); Panel bp = getButtonPanel(); p.add(bp); f.add("South", p); Panel cp = getConfigPanel(); f.add ("North", cp); target = new Cursor(); addMouseListener(target); addMouseMotionListener(target); // Connect to SRV if requested on command line if (connectOnStartup && comPort != null) { connectButton.setLabel("Disconnect"); trace("connecting..."); openSRVConnection(); if (wcsPush != null) wcsPush.connect(); } } private Button connectButton = new Button (" Connect "); private Choice comPorts = null; private Button consoleButton = new Button("Event Log"); private Button cursorButton = new Button("+"); private Panel getConfigPanel () { Panel p = new Panel (new FlowLayout ()); comPorts = new Choice(); comPorts.add("-- Ports --"); comPorts.select(0); // no selection by default comPorts.add("Network"); if ("Network".equalsIgnoreCase(comPort)) comPorts.select(1); //if (DEBUG) comPorts.add("Test"); java.util.List l = getSerialPorts (); for (int i = 0; i < l.size (); i++) { String cp = (String) l.get (i); comPorts.add(cp); if (cp.equals(comPort)) comPorts.select(i + 2); } connectButton.addActionListener(this); p.add(comPorts); p.add(connectButton); consoleButton.addActionListener(this); p.add(consoleButton); cursorButton.addActionListener(this); p.add(cursorButton); return p; } public void sendSRVCommand(String buttonID) { //_sendSRVCommand((String) commands.get(buttonID)); String cmd = (String) commands.get(buttonID); commandQ.add(cmd); } private void _sendSRVCommand(String cmd) { if (cmd == null || "".equals(cmd)) return; trace("### Command ###"); trace("# Sending: 0x" + cmd); trace("###############"); try { int length = cmd.length() / 2; if (cmd.indexOf("$x") != -1) length += 1; // we need two bytes for coordinates if (cmd.indexOf("$y") != -1) length += 1; if (cmd.indexOf("$r") != -1) length += 1; byte[] cmdBytes = new byte[length]; int idx = 0; for (int j = 0; j < cmd.length() / 2; j++) { String cb = cmd.substring(2*j, 2*j + 2); // handle special tokens if ("$x".equals(cb)) { short x = target.getCenterX(); cmdBytes[idx++] = (byte) (x & 0xFF); // FIXME: byte order / masking cmdBytes[idx++] = (byte) ((x / 256) & 0xFF); } else if ("$y".equalsIgnoreCase(cb)) { short y = target.getCenterY(); cmdBytes[idx++] = (byte) (y & 0xFF); // FIXME: byte order / masking cmdBytes[idx++] = (byte) ((y / 256) & 0xFF); } else if ("$r".equalsIgnoreCase(cb)) { short r = target.getRadius(); cmdBytes[idx++] = (byte) (r & 0xFF); // FIXME: byte order / masking cmdBytes[idx++] = (byte) ((r / 256) & 0xFF); } else { // convert hex string to byte cmdBytes[idx++] = (byte) Integer.parseInt(cb, 16); } //System.out.println(cb + " -> " + (idx>1?cmdBytes[idx - 2]:0) + "-" + cmdBytes[idx-1]); } out.write(cmdBytes); if ("Network".equals(comPort)) out.flush(); } catch (Exception ex) { error("command: " + cmd + ", " + ex); ex.printStackTrace (); } } // Key listener methods public void keyTyped(KeyEvent e) { } public void keyReleased(KeyEvent e) { } // See http://java.sun.com/j2se/1.4.2/docs/api/constant-values.html#java.awt.event.KeyEvent.CHAR_UNDEFINED // for Java's "virtual key codes" public void keyPressed(KeyEvent e) { byte keyCode = (byte) (e.getKeyCode() & 0xff); // the interesting keys have codes < 256 if (out == null) { System.out.println("* Key (" + keyCode + ") pressed, but SRV-1 is disconnected"); error("not connected to SRV"); return; } System.out.println("* Sending key code: " + keyCode); try { out.write(keyCode); if ("Network".equals(comPort)) out.flush(); } catch (IOException ioe) { error("key event, keycode: " + keyCode + " - " + ioe); } } public synchronized void actionPerformed(ActionEvent e) { bv.requestFocusInWindow(); // immediately regain keyboard focus Object s = e.getSource(); if (s instanceof ImageButton) { if (out == null) { error("not connected to SRV"); return; } String cmd = (String) ((ImageButton) s).getSVCommand(); //_sendSRVCommand(cmd); commandQ.add(cmd); } else if (s == connectButton) { String cmd = connectButton.getLabel(); if ("Connect".equals(cmd.trim())) { if (comPorts.getSelectedIndex () != 0) { comPort = comPorts.getSelectedItem(); props.put("comport", comPort); saveConfig(); connectButton.setLabel("Disconnect"); trace("connecting..."); openSRVConnection(); if (wcsPush != null) wcsPush.connect(); } else { error("no COM port selected"); } } else { if (wcsPush != null) wcsPush.disconnect(); connectButton.setLabel(" Connect "); closeSRVConnection(); trace("disconnected"); } } else if (s == consoleButton) { if (logWindow.isShowing()) logWindow.hide(); else logWindow.show(); } else if (s == cursorButton) { target = new Cursor(); addMouseListener(target); addMouseMotionListener(target); repaint(); } } private Panel getButtonPanel() { URL base = null; try { base = new URL ("file://"); } catch (IOException e) { } catch (Exception e) { } LayoutManager lm = new FlowLayout (); int cols = 8; try { cols = Integer.parseInt((String) props.get("button.columns")); } catch (Exception e) { } lm = new GridLayout(0, cols); Panel pan = new Panel(lm); int i = 0; while (true) { i++; String gif = (String) props.get("button." + i); String cmd = (String) props.get("command." + i); if (gif == null || "".equals(gif)) break; try { ImageButton b = new ImageButton((new File (gif)).toURL (), (new File (gif)).toURL (), cmd); b.addActionListener(this); pan.add(b); trace("added: " + gif + ", cmd: " + cmd); cmdButtons.put(cmd, b); commands.put("" + i, cmd); } catch (MalformedURLException e) { trace("unable to add: " + gif + ", cmd: " + cmd); } } return pan; } /* JPEG - SVFRIMJ1xxxx xxxx = binary length, low to high IMJ1 - 80x64, IMJ3 - 160x128 IMJ5 - 320x240 IMJ7 - 640x480 RAW (Y pixels only) - SVFRIMR1xxxx xxxx = binary length, low to high IMJ1 - 80x60, IMJ3 - 160x120 IMJ5 - 320x240 IMJ7 - 640x480 */ private boolean newFrame(Image i) { image = i; repaint(); return true; } /** * Main SRV event loop: issues periodic requests for * frames and handles deferred sending of commands. * */ private class SRVEventLoop extends Thread { private int count = 0; private long lastFrameRequest = 0; public void run() { while(shouldRun) { try { Thread.sleep(10); } catch (InterruptedException ie) { } if (!idle && count++ < 50) continue; count = 0; if (out == null) continue; try { // don't send "heartbeat" request more than twice a second if (frameSize == 0 && System.currentTimeMillis() - lastFrameRequest > 500) { lastFrameRequest = System.currentTimeMillis(); requestFrame(); } //try { Thread.sleep(20); } catch (InterruptedException ie) { } if (is.available() > 0) readLoop(); } catch (Exception e) { error(e.toString()); } } } } String comLock = "LOCK"; private static final byte[] frameHeader = { '#', '#', 'I', 'M', 'J' }; int pos = 0; int frameSize = 0; byte[] frame = null; char frameDim = '5'; private void requestFrame() throws IOException { processCommandQueue(); out.write("I".getBytes()); // grab frame if (DEBUG) System.out.println("wrote 'I'"); if ("Network".equals(comPort)) out.flush(); } private void getScanHits() throws IOException { if (!scanEnabled) return; out.write("S".getBytes()); if (DEBUG) System.out.println("wrote 'S'"); if ("Network".equals(comPort)) out.flush(); try { Thread.sleep(500); } catch (InterruptedException ie) { } if (DEBUG) System.out.println("'S' ACK - " + is.available()); int r = 0; StringBuffer sb = new StringBuffer(); while (is.available() > 0 && r < 168) { sb.append((char) is.read()); r++; } String scan = sb.toString(); if (scan.startsWith("##Scan")) { if (DEBUG) System.out.println(scan); int ish = 0; for (int i = 9; i < sb.length(); i += 2) { try { scanHits[ish++] = Integer.parseInt(sb.substring(i, i + 2).trim()); } catch (Exception e) { } } // print out the resulting scan hits array if (DEBUG) { StringBuffer after = new StringBuffer(" "); for (int i = 0; i < scanHits.length; i++) after.append(scanHits[i]).append(","); System.out.println(after); } } } // send queued commands private void processCommandQueue() { int retries = 0; while (!commandQ.isEmpty()) { String cmd = (String) commandQ.remove(0); if (DEBUG) System.out.println("## sending command: " + cmd); _sendSRVCommand(cmd); // Command ACK / retry /* try { Thread.sleep(100); } catch (InterruptedException ie) { } if ((is.available() > 2 && (char) is.read() == '#' && (char) is.read() == cmd.charAt(0)) || retries++ >= 2) { System.out.println("## got ACK for " + cmd + " (" + retries + ")"); commandQ.remove(0); retries = 0; } */ } } private void readLoop() { int mark = 0; try { //if (DEBUG) System.out.println("readLoop() - available() = " + is.available()); if (frameSize == 0) { while (is.available() > 0 && mark < frameHeader.length && shouldRun) { byte b = (byte) is.read(); if (debug != null) debug.write(b); if (frameHeader[mark] == (char) b) mark++; else mark = 0; } if (mark == frameHeader.length) { idle = false; byte b = (byte) is.read(); if (debug != null) debug.write(b); frameDim = (char) b; for (int i = 0; i < 4; i++) { int in = is.read(); if (debug != null) debug.write(in); frameSize += in * Math.pow(256, i); } } } if (frameSize > 0 && frameSize < (100 * 1024)) { // limit frames to 100k if (DEBUG) System.out.println("reading frame: " + pos + " / " + frameSize); if (frame == null) frame = new byte[frameSize]; int timeout = 0; while (pos < frameSize && timeout < 600 && shouldRun) { if (is.available() <= 0) { timeout++; try { Thread.sleep(50); } catch (InterruptedException ie) {} continue; } int thisRead = is.read(frame, pos, frameSize - pos); /* if (DEBUG && debug != null) { debug.write(frame, pos, thisRead); debug.flush(); } */ pos += thisRead; //System.out.println(" current pos - " + pos); } if (pos == frameSize) { if (DEBUG) System.out.println("finished frame - " + frameSize + " - " + System.currentTimeMillis()); frameDecoder.newRawFrame("IMJ" + frameDim, frame); } else { if (DEBUG) System.out.println("**short read: " + pos + " of " + frameSize); //frameDecoder.newRawFrame("IMJ" + frameDim, frame); } getScanHits(); requestFrame(); pos = 0; frameSize = 0; frame = null; } idle = true; } catch (Exception e) { if (DEBUG) e.printStackTrace(); } } public void serialEvent(SerialPortEvent ev) { /* //synchronized (comLock) { //long start = System.currentTimeMillis(); if (ev.getEventType() != SerialPortEvent.DATA_AVAILABLE) return; try { System.out.println("serialEvent() - " +is.available() + ", " + System.currentTimeMillis()); } catch (IOException e) { } readLoop(); //} */ } /** * Shutdown the SRV console and clean up resources */ private void shutdown() { shouldRun = false; idle = true; closeSRVConnection(); try { if (debug != null) debug.close(); } catch (Exception e) { } } private void trace(String msg) { logService.log(msg + LINESEP); } private void traceChar(String msg) { logService.log(msg); } private void error(String msg) { logService.log("[ERROR] " + msg + LINESEP); } private void openSRVConnection() { if ("Network".equalsIgnoreCase(comPort)) { openNetworkSRV(); } else if ("Test".equalsIgnoreCase(comPort)) { try { is = new FileInputStream("/tmp/srv/testing.bin"); out = new FileOutputStream("/tmp/srv/testing.out"); } catch (Exception e) { } } else { openSerialPort(); } } private void closeSRVConnection() { if ("Network".equalsIgnoreCase(comPort)) { /* if (networkSRV != null) { networkSRV.disconnect(); networkSRV = null; } */ } else { closeSerialPort(); } try { out.close(); } catch(Exception e) { } out = null; try { is.close(); } catch(Exception e) { } is = null; try { s.close(); } catch(Exception e) { } s = null; //try { serialPort.close(); } catch(Exception e) { } //serialPort = null; } /** * Open connection to networked SRV */ private void openNetworkSRV() { String host = (String) props.get("network.srv.host"); String port = (String) props.get("network.srv.port"); if (host != null && port != null) { try { s = new Socket(host, Integer.parseInt(port)); is = new BufferedInputStream(s.getInputStream()); out = new BufferedOutputStream(s.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } trace (LINESEP + "[NetworkSRV] - opened connection to " + host + ":" + port); /* networkSRV = new NetworkSRV(host, port); networkSRV.start(); networkSRV.connect(); */ } else { error("network.srv.host and network.srv.port missing from config file"); } } private boolean setBps(int bps) { boolean r = false; try { serialPort.setSerialPortParams (bps, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE); r = true; } catch (UnsupportedCommOperationException e3) { error ("error configuring RS232 port"); e3.printStackTrace (); } return r; } private java.util.List getSerialPorts() { java.util.List l = new ArrayList (); Enumeration portList; CommPortIdentifier portId; portList = CommPortIdentifier.getPortIdentifiers(); while (portList.hasMoreElements()) { portId = (CommPortIdentifier) portList.nextElement (); if (portId.getPortType () == CommPortIdentifier.PORT_SERIAL) { l.add(portId.getName ()); } } return l; } private void openSerialPort() { Enumeration portList; CommPortIdentifier portId; if (comPort == null || "".equals (comPort)) { error("no COM port specified"); } portList = CommPortIdentifier.getPortIdentifiers(); while (portList.hasMoreElements()) { portId = (CommPortIdentifier) portList.nextElement (); if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { if (comPort.equals(portId.getName ())) { trace("opening " + portId.getName () + "..."); try { serialPort = (SerialPort) portId.open("SRV Console", 0); } catch (PortInUseException e) { trace("RS232 port in use"); return; } setBps (115200); try { out = serialPort.getOutputStream(); is = new BufferedInputStream(serialPort. getInputStream(), 16384); } catch (IOException e2) { error(e2.toString()); } serialPort.setDTR(false); serialPort.setRTS(false); /* try { serialPort.addEventListener(this); } catch (TooManyListenersException e) { } serialPort.notifyOnDataAvailable(true); */ trace("connected to " + portId.getName ()); return; } } } } private void closeSerialPort() { try { if (out != null) out.close (); if (is != null) is.close (); if (serialPort != null) serialPort.close(); out = null; is = null; serialPort = null; } catch (IOException ioe) { trace ("Error closing SRV connection: " + ioe); } } private class LogService extends Thread { private StringBuffer logBuf = new StringBuffer(512); public void log(String msg) { synchronized (logBuf) { logBuf.append(msg); } } public void run() { while(shouldRun) { try { Thread.sleep(250); } catch (InterruptedException ie) { } String l = null; synchronized (logBuf) { l = logBuf.toString(); logBuf.delete(0, logBuf.length()); } if (l != null && l.length() > 0) { log.append(l); try { log.setCaretPosition(Integer.MAX_VALUE); } catch (IllegalComponentStateException ise) { } } } } } /** * This thread serves as the URL notification worker. Every ASCII * character read from the SRV-1 is sent to this worker. When the internal * buffer ('collector') has reached SEARCH_INTERVAL characters, * NotifyService searches the string for tokens (defined in srv_notify.config). * When a token is encountered, the associated URL is added to the visit queue. */ private class NotifyService extends Thread { private java.util.List urls = new ArrayList(); // queue that hold URLs to be visted private StringBuffer collector = new StringBuffer(); // run a search every 64 characters by default private static final int SEARCH_INTERVAL = 64; public void newChar(char c) { collector.append(c); } private int findNotificationTokens(String str) { int lastIndex = 0; for (Iterator i = notifyTokens.keySet().iterator(); i.hasNext(); ) { String tok = (String) i.next(); int index = str.indexOf(tok); if (index != -1) { if (index > lastIndex) lastIndex = index + tok.length(); String url = (String) notifyTokens.get(tok); trace("Found: " + tok + ", visiting: " + url); visitUrl(url); } } if (lastIndex == 0) lastIndex = str.length() - (SEARCH_INTERVAL / 2); return lastIndex; } private void visitUrl(String url) { synchronized (urls) { urls.add(url); } } public void run() { while(true) { try { Thread.sleep(250); } catch (InterruptedException ie) { } if (collector.length() >= SEARCH_INTERVAL) { System.out.println("searching: " + collector.toString()); int lastIndex = findNotificationTokens(collector.toString()); collector = new StringBuffer(collector.substring(lastIndex)); } while (urls.size() > 0) { try { String u = null; synchronized (urls) { u = (String) urls.remove(0); } if (u != null) { URL url = new URL(u); InputStream urlis = url.openStream(); urlis.close(); } } catch (Exception e) { trace("NotifyService: " + e); } } } } } private class FrameDecoder extends Thread { private byte[] img = null; private String type = "IMJ3"; public void newRawFrame(String type, byte[] f) { this.img = f; this.type = type; } public void run() { while (shouldRun) { if (img == null) { try { Thread.sleep(50); } catch (InterruptedException ie) { } continue; } if (type.startsWith ("IMB")) { char res = type.charAt (3); if (res == '1') { width = 40; height = 32; } else if (res == '3') { width = 80; height = 64; } pixels = new int[img.length * 8]; initRawBuffer (); for (int i = 0; i < img.length; i++) { for (int j = 0; j < 8; j++) { if (((int)img[i] & (0x00000080 >> j)) > 0) pixels[i*8 + j] = 0xFFFFFFFF; else pixels[i*8 + j] = 0xFF000000; } } source.newPixels(0, 0, width, height); newFrame(image); } else { // JPEG ("IMJ") is default Image i = Toolkit.getDefaultToolkit().createImage(img); tracker.addImage (i, 0); try { tracker.waitForID (0); } catch (InterruptedException ie) { error ("JPEG decode " + ie); } if (!tracker.isErrorID (0)) { newFrame(i); } tracker.removeImage(i); if (wcsPush != null) wcsPush.newFrame(img); } img = null; } } } /** * Handle cursor dragging and resizing */ private class Cursor implements MouseListener, MouseMotionListener { private Image cursor = null; private int cursorW = 20; private int cursorH = 20; private int cursorWOrig; // original size of cursor bitmap private int cursorHOrig; private int cursorX = (width / 2) - 10; // coordinates of top left corner or cursor private int cursorY = (height / 2) - 10; boolean dragging = false; boolean sizing = false; private int offsetX = 0; private int offsetY = 0; private int x1 = cursorX; private int y1 = cursorY; private int clickX = 0; private int clickY = 0; private int refX; // absolute corrds of frame that contains this cursor private int refY; public Cursor() { load(); } public void draw(int refX, int refY, Graphics g) { this.refX = refX; this.refY = refY; g.drawImage(cursor, refX + cursorX, refY + cursorY, null); } public void recenter(int width, int height) { cursorX = (width / 2) - (cursorW / 2); cursorY = (height / 2) - (cursorH / 2); } // x-coordinate of cursor center (relative to refX,refY) public short getCenterX() { return (short) (cursorX + (cursorW / 2)); } // y-coordinate of cursor center (relative to refX,refY) public short getCenterY() { return (short) (cursorY + (cursorH / 2)); } // cursor 'radius' in pixels // = distance from the center point to corner public short getRadius() { int x1 = cursorX + (cursorW / 2); int y1 = cursorY + (cursorH / 2); int x2 = cursorX + cursorW; int y2 = cursorY + cursorH; return (short) Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))); } private void render() { tracker.addImage(cursor, 0); try { tracker.waitForID(0); } catch (InterruptedException iec) { error("cursor failure " + iec); } if (!tracker.isErrorID(0)) { cursorW = cursor.getWidth(null); cursorH = cursor.getHeight(null); } tracker.removeImage(cursor); } public void load() { if (cursor != null) cursor.flush(); cursor = Toolkit.getDefaultToolkit().getImage("cursor.png"); render(); cursorWOrig = cursorW; cursorHOrig = cursorH; } public void mousePressed(MouseEvent evt) { if (dragging || sizing) // Exit if a drag is already in progress. return; clickX = evt.getX(); // Location where user clicked. clickY = evt.getY(); int boundX = cursorX + refX; int boundY = cursorY + refY; // check if click was on cursor if (clickX >= boundX && clickX < boundX + cursorW && clickY >= boundY && clickY < boundY + cursorH) { // size of the resize handle (bottom quarter, minimum 5x5 pixels) int handle = Math.max(cursorW / 4, cursorH / 4); if (handle < 5) handle = 5; // resize if click was in handle zone if (clickX >= boundX + cursorW - handle && clickY >= boundY + cursorH - handle) { sizing = true; } else { dragging = true; offsetX = clickX - boundX; // Distance from corner of square to click point. offsetY = clickY - boundY; } } } // Dragging / sizing stops when user releases the mouse button. public void mouseReleased(MouseEvent evt) { dragging = false; sizing = false; } public void mouseDragged(MouseEvent evt) { int x = evt.getX(); // position of mouse int y = evt.getY(); //System.out.println("mouseDragged(): " + x + ", " + y + " : " + offsetX + ", " + offsetY); if (dragging) { // new top left coords of cursor int newX = x - offsetX; int newY = y - offsetY; if (newX < refX) newX = refX; if (newX > refX + width - cursorW) newX = refX + width - cursorW; if (newY < refY) newY = refY; if (newY > refY + height - cursorH) newY = refY + height - cursorH; cursorX = newX - refX; // move the cursor cursorY = newY - refY; repaint(); } else if (sizing) { int add = Math.max(x - clickX, y - clickY); int newW = cursorW + add; int newH = cursorH + add; if (Math.abs(newW - cursorW) < 2 || Math.abs(newH - cursorH) < 2) return; if (newW <= 10 || newH <= 10) { newW = cursorW; newH = cursorH; } //System.out.println("sizing: " + x + ", " + clickX + " : " + newW + ", " + newH); if (newW > width - cursorX) { newW = width - cursorX; newH = cursorH + (newW - cursorW); } else if (newH > height - cursorY) { newH = height - cursorY; newW = cursorW + (newH - cursorH); } cursorW = newW; cursorH = newH; // reload original bitmap if we're near original size // (repeated sizing tends to distort images) if (Math.abs(cursorW - cursorWOrig) < 2 || Math.abs(cursorH - cursorHOrig) < 2) { trace("reverting: " + cursorW + ", " + cursorH + " : " + cursorWOrig + ", " + cursorHOrig); load(); } else { Image i = cursor.getScaledInstance(cursorW, cursorH, Image.SCALE_DEFAULT); cursor.flush(); cursor = i; } render(); repaint(); } else { return; } } public void mouseMoved(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } } private class WCSServer extends Thread { public void run() { StreamServer.main(new String[] { (String) props.get("wcs.port") }); } } /** * Thread to handle Webcamsat push. */ private class WCSPush extends Thread { private boolean shouldPush = false; private byte[] wcsFrame = null; private Object frameLock = new Integer (1); private Socket s = null; private OutputStream wcs = null; private String server, port, camID, pass; public WCSPush(String server, String port, String camID, String pass) { this.server = server; this.port = port; this.camID = camID; this.pass = pass; } public void connect() { shouldPush = true; } public void disconnect() { shouldPush = false; wcsFrame = null; closeConnection (); } private void closeConnection() { try { if (wcs != null) wcs.close(); if (s != null) s.close(); wcs = null; s = null; } catch (Exception e) { } trace (LINESEP + "[wcs] - closed connection to " + server + ":" + port); } private void openConnection () { try { s = new Socket (server, Integer.parseInt (port)); wcs = new BufferedOutputStream (s.getOutputStream ()); wcs.write (("SOURCE " + camID + " " + pass + " HTTP/1.1\r\n\r\n\r\n").getBytes ()); } catch (Exception e) { e.printStackTrace (); } trace (LINESEP + "[wcs] - opened connection to " + server + ":" + port); } public void newFrame (byte[]f) { synchronized (frameLock) { wcsFrame = f; trace ("[wcs] - new frame"); } } public void run () { while (true) { if (!shouldPush || wcsFrame == null) { try { Thread.sleep (250); } catch (InterruptedException e) { } continue; } if (wcs == null) { openConnection (); } synchronized (frameLock) { String frameHead = "SVFR "; try { wcs.write (frameHead.getBytes ()); wcs.write (wcsFrame); wcs.flush (); } catch (Exception e) { trace ("[wcs] - error: " + e); closeConnection (); } wcsFrame = null; } } } } /******************************* * Config file utility methods ******************************/ public void loadConfig () { try { FileInputStream nf = new FileInputStream(NOTIFY_CONFIG_FILE); Properties notifyProps = new Properties(); notifyProps.load(nf); notifyTokens.putAll(notifyProps); nf.close(); } catch (Exception e) { //trace ("error loading notify config file: " + e); } props.clear (); try { BufferedReader br = new BufferedReader (new FileReader(CONFIG_FILE)); String line = null; while ((line = br.readLine ()) != null) { if (!line.startsWith ("#")) { String[]p = line.split ("="); if (p.length == 2 && p[0] != null && p[1] != null) { props.put (p[0].trim (), p[1].trim ()); } } } br.close (); } catch (Exception e) { trace ("error loading config file: " + e); } String s = (String) props.get ("comport"); if (s != null && !"".equals (s)) comPort = s; } public void saveConfig () { java.util.List lines = new ArrayList(); java.util.List written = new ArrayList(); try { BufferedReader br = new BufferedReader (new FileReader(CONFIG_FILE)); String line = null; while ((line = br.readLine ()) != null) { lines.add (line); } br.close (); BufferedWriter bw = new BufferedWriter (new FileWriter(CONFIG_FILE)); for (Iterator i = lines.iterator (); i.hasNext ();) { String l = (String) i.next (); String[]p = l.split("="); if (p.length == 2 && p[0] != null && p[1] != null) { String v = (String) props.get (p[0].trim ()); if (v != null) { l = p[0] + "=" + v; written.add(p[0]); } } bw.write(l); bw.write(LINESEP); } for (Iterator it = props.keySet ().iterator (); it.hasNext ();) { String k = (String) it.next (); if (!written.contains (k)) { bw.write(k + "=" + props.get (k)); bw.write(LINESEP); written.add(k); } } bw.close (); } catch (Exception e) { trace ("error saving config file: " + e); } } private final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D','E', 'F' }; /** * This converts a byte[] to a String * in hexidecimal format. * * @param input byte[] to convert * @return String - resulting Hex String */ public String byteArrayToHex(byte[]input) { StringBuffer result = new StringBuffer(); byte highNibble, lowNibble; if (input != null) { for (int i = 0; i < input.length; i++) { highNibble = (byte) ((input[i] >>> 4) & 0x0F); lowNibble = (byte) (input[i] & 0x0F); result.append(HEX_DIGITS[highNibble]); result.append(HEX_DIGITS[lowNibble]); result.append(" "); } } return result.toString(); } } /** byte[] rawImage = conx.receiveBytes( imageLength ); if ( ! isJpgValid ( rawImage ) ) { throw new IOException ( "Invalid JPG image signature: " + Integer.toHexString( rawImage[0] ) + " " + Integer.toHexString( rawImage[1] ) ); } // start process of converting image from jpg to internal format // old AWT style: // Image image = toolkit.createImage( rawImage ); // new ImageIO style, convert raw bytes to BufferedImage BufferedImage image = ImageIO.read ( new ByteArrayInputStream ( rawImage ) ); ------------------------------ public static boolean isJpgValid ( byte image[] ) { return( ( image[0] & 0xff ) == 0xff ) && ( (image[1] & 0xff ) == 0xd8 ); } -- */