I wrote an application last year called Disco Desktop which uses TinkerProxy to allow AIR to communicated with Arduino over USB serial port. My TinkerProxy
and TinkerProxyEvent
classes is posted below.
The application bundles serproxy and uses Native Process API to call it so there are 2 different installers - Mac OS X and Windows. TinkerProxy.as
extends Socket, writes the serproxy configuration file at runtime according to the users' input and launches serproxy based on this configuration as a background process. The terminal/cmd window is never visible.
Note: call open()
instead of connect()
. Included in the installers are a simple device schematic and sketch for Arduino.
TinkerProxy
:
package com.mattie.net
{
//Imports
import com.mattie.events.TinkerProxyEvent;
import flash.desktop.NativeApplication;
import flash.desktop.NativeProcessStartupInfo;
import flash.desktop.NativeProcess;
import flash.errors.IOError;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.filesystem.File;
import flash.net.Socket;
import flash.system.Capabilities;
import flash.utils.Timer;
import flash.utils.Endian;
//Class
public class TinkerProxy extends Socket
{
//Properties
private var systemIsWindowsProperty:Boolean;
private var openingProperty:Boolean;
private var connectedProperty:Boolean;
//Variables
private var windowsProxyFile:String;
private var macProxyFile:String;
private var tinkerProxyApplication:File;
private var tinkerProxyConfigurationFile:File;
private var serialPort:String;
private var baudRate:uint;
private var networkAddress:String;
private var networkPort:uint;
private var loadDelay:uint;
private var loadDelayTimer:Timer;
private var initializeDelay:uint;
private var initializeDelayTimer:Timer;
private var comDatabits:uint;
private var comStopbits:uint;
private var proxyTimeout:uint;
private var writeConfigStream:FileStream;
private var tinkerProxyProcess:NativeProcess;
//Constructor
public function TinkerProxy(windowsProxyFile:String = "serproxy.exe", macProxyFile:String = "serproxy.osx", endian:String = Endian.LITTLE_ENDIAN)
{
//Set Included File Proxy Names
this.windowsProxyFile = windowsProxyFile;
this.macProxyFile = macProxyFile;
super();
super.endian = endian;
init();
}
//Resolve The Operating System
private function init():void
{
//Check If Source Tinker Proxy Files Are Included In Application Directory
if (!File.applicationDirectory.resolvePath(windowsProxyFile).exists && !File.applicationDirectory.resolvePath(macProxyFile).exists)
throw new Error("Tinker Proxy source files \"" + windowsProxyFile + "\" (Windows) and/or \"" + macProxyFile + "\" (Mac) cannot be found in application directory (Included Files)");
//Resoslve Operating System
if (Capabilities.os.toLowerCase().indexOf("windows") > -1)
{
systemIsWindowsProperty = true;
tinkerProxyApplication = File.applicationDirectory.resolvePath(windowsProxyFile);
tinkerProxyConfigurationFile = File.applicationStorageDirectory.resolvePath(windowsProxyFile.substring(0, windowsProxyFile.lastIndexOf(".exe")) + ".cfg");
}
else if (Capabilities.os.toLowerCase().indexOf("mac") > -1)
{
systemIsWindowsProperty = false;
tinkerProxyApplication = File.applicationDirectory.resolvePath(macProxyFile);
tinkerProxyConfigurationFile = File.applicationStorageDirectory.resolvePath(macProxyFile + ".cfg");
}
else
{
throw new Error("TinkerProxy Error: Operating System Is Not Supported");
}
}
//Open Tinker Proxy Socket Connection
public function open(
serialPort:String,
baudRate:uint,
networkAddress:String = "127.0.0.1",
networkPort:uint = 5331,
loadDelay:uint = 1000,
initializeDelay:uint = 2000,
comDatabits:uint = 8,
comStopbits:uint = 1,
proxyTimeout:uint = 63115200
)
{
//Disable Opening Socket If Currently Opening
if (!openingProperty)
{
//Set Accessor
openingProperty = true;
//Dispatch Event
dispatchEvent(new TinkerProxyEvent(TinkerProxyEvent.LOADING));
//Check If Connection Parameters For Configuration File Have Changed
if (
this.serialPort == serialPort &&
this.baudRate == baudRate &&
this.networkAddress == networkAddress &&
this.networkPort == networkPort &&
this.comDatabits == comDatabits &&
this.comStopbits == comStopbits &&
this.proxyTimeout == proxyTimeout
)
{
//Assign Timer Variables
this.loadDelay = loadDelay;
this.initializeDelay = initializeDelay;
//Launch Tinker Proxy Application If Connection Parameters Have Not Changed
launchTinkerProxyApplication(null);
return;
}
//Assign Variables
this.serialPort = serialPort;
this.baudRate = baudRate;
this.networkAddress = networkAddress;
this.networkPort = networkPort;
this.loadDelay = loadDelay;
this.initializeDelay = initializeDelay;
this.comDatabits = comDatabits;
this.comStopbits = comStopbits;
this.proxyTimeout = proxyTimeout;
//Add Event Listeners To New File Stream
writeConfigStream = new FileStream();
writeConfigStream.addEventListener(Event.CLOSE, launchTinkerProxyApplication);
writeConfigStream.addEventListener(IOErrorEvent.IO_ERROR, IOErrorEventHandler);
//Write Tinker Proxy Configuration File
writeConfigStream.openAsync(tinkerProxyConfigurationFile, FileMode.WRITE);
writeConfigStream.writeUTFBytes("serial_device1=" + serialPort + File.lineEnding);
writeConfigStream.writeUTFBytes("comm_ports=1" + File.lineEnding);
writeConfigStream.writeUTFBytes("net_port1=" + networkPort + File.lineEnding);
writeConfigStream.writeUTFBytes("newlines_to_nils=false" + File.lineEnding);
writeConfigStream.writeUTFBytes("comm_baud=" + baudRate + File.lineEnding);
writeConfigStream.writeUTFBytes("comm_databits=" + comDatabits + File.lineEnding);
writeConfigStream.writeUTFBytes("comm_stopbits=" + comStopbits+ File.lineEnding);
writeConfigStream.writeUTFBytes("comm_parity=none" + File.lineEnding);
writeConfigStream.writeUTFBytes("timeout=" + proxyTimeout + File.lineEnding);
writeConfigStream.close();
}
}
//Launch Tinker Proxy Application
private function launchTinkerProxyApplication(evt:Event):void
{
if (evt)
{
//Remove File Stream Event Listeners
writeConfigStream.removeEventListener(Event.CLOSE, launchTinkerProxyApplication);
writeConfigStream.removeEventListener(IOErrorEvent.IO_ERROR, IOErrorEventHandler);
}
//Start Tinker Proxy Application As Native Process
var tinkerProxyProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
tinkerProxyProcessStartupInfo.executable = tinkerProxyApplication;
var processArguments:Vector.<String> = new Vector.<String>();
processArguments[0] = tinkerProxyConfigurationFile.nativePath;
tinkerProxyProcessStartupInfo.arguments = processArguments;
tinkerProxyProcess = new NativeProcess();
tinkerProxyProcess.start(tinkerProxyProcessStartupInfo);
//Delay Process To Allow Tinker Proxy Application To Initialize
loadDelayTimer = new Timer(loadDelay, 1);
loadDelayTimer.addEventListener(TimerEvent.TIMER_COMPLETE, connectTinkerProxy);
loadDelayTimer.start();
}
//Initialize Tinker Proxy Socket Connection
private function connectTinkerProxy(evt:TimerEvent):void
{
//Dispatch Event
dispatchEvent(new TinkerProxyEvent(TinkerProxyEvent.INITIALIZING));
//Remove Tinker Proxy Application Initilization Timer
loadDelayTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, connectTinkerProxy);
loadDelayTimer = null;
//Add Connection Error Event Listeners
addEventListener(Event.CONNECT, initializeDelayTimerHandler);
addEventListener(Event.CLOSE, connectionErrorEventHandler);
addEventListener(IOErrorEvent.IO_ERROR, connectionErrorEventHandler);
addEventListener(SecurityErrorEvent.SECURITY_ERROR, connectionErrorEventHandler);
//Connect Socket (Super)
try {
super.connect(networkAddress, networkPort);
}
catch(error:IOError) {connectionErrorEventHandler(null);}
catch(error:SecurityError) {connectionErrorEventHandler(null);}
}
//Delay Process To Allow Device To Initialize
private function initializeDelayTimerHandler(evt:Event):void
{
removeEventListener(Event.CONNECT, initializeDelayTimerHandler);
initializeDelayTimer = new Timer(initializeDelay, 1);
initializeDelayTimer.addEventListener(TimerEvent.TIMER_COMPLETE, tinkerProxyConnectionComplete);
initializeDelayTimer.start();
}
//Tinker Proxy Socket Has Been Successfully Connected
private function tinkerProxyConnectionComplete(evt:TimerEvent):void
{
//Set Accessors
openingProperty = false;
connectedProperty = true;
//Dispatch Event
dispatchEvent(new TinkerProxyEvent(TinkerProxyEvent.CONNECT));
//Remove Device Initilization Timer
initializeDelayTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, tinkerProxyConnectionComplete);
initializeDelayTimer = null;
}
//Throw Error If Stock Connect Method Is Explicitly Called
override public function connect(host:String, port:int):void
{
throw new Error("Cannot call connect() method on TinkerProxy instance. Call open() method instead.");
}
//Close Tinker Proxy Application
override public function close():void
{
//Stop Configuration File And Timers If Socket Is Currently Opening
if (openingProperty)
{
//Set Accessor
openingProperty = false;
//Stop File Stream
if (writeConfigStream.hasEventListener(Event.CLOSE))
{
writeConfigStream.close();
writeConfigStream.removeEventListener(Event.CLOSE, launchTinkerProxyApplication);
writeConfigStream.removeEventListener(IOErrorEvent.IO_ERROR, IOErrorEventHandler);
}
//Stop Process Initialization Timer
if (loadDelayTimer.running)
{
loadDelayTimer.stop();
loadDelayTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, connectTinkerProxy);
loadDelayTimer = null;
}
//Stop Device Initialization Timer
if (initializeDelayTimer.running)
{
initializeDelayTimer.stop();
initializeDelayTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, connectTinkerProxy);
initializeDelayTimer = null;
}
}
//Close Socket (Super)
super.close();
//Close Tinker Proxy Application
tinkerProxyProcess.exit(true);
tinkerProxyProcess = null;
//Dispatch Event
dispatchEvent(new TinkerProxyEvent(TinkerProxyEvent.DISCONNECT));
//Set Accessor
connectedProperty = false;
//Remove Connection Error Event Listeners
removeEventListener(Event.CLOSE, connectionErrorEventHandler);
removeEventListener(IOErrorEvent.IO_ERROR, connectionErrorEventHandler);
removeEventListener(SecurityErrorEvent.SECURITY_ERROR, connectionErrorEventHandler);
}
//Server Automatically Closed The Socket Due To A Connection Error
private function connectionErrorEventHandler(evt:*):void
{
//Set Accessors
openingProperty = false;
connectedProperty = false;
//Dispatch Event
dispatchEvent(new TinkerProxyEvent(TinkerProxyEvent.ERROR));
//Remove Device Initilization Timer
if (initializeDelayTimer != null)
{
if (initializeDelayTimer.running)
initializeDelayTimer.stop();
initializeDelayTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, tinkerProxyConnectionComplete);
initializeDelayTimer = null;
}
//Remove Connection Error Event Listeners
removeEventListener(Event.CLOSE, connectionErrorEventHandler);
removeEventListener(IOErrorEvent.IO_ERROR, connectionErrorEventHandler);
removeEventListener(SecurityErrorEvent.SECURITY_ERROR, connectionErrorEventHandler);
//Close Tinker Proxy Application
tinkerProxyProcess.exit(true);
tinkerProxyProcess = null;
}
//IO Error Event Handler
private function IOErrorEventHandler(evt:IOErrorEvent):void
{
throw new Error("TinkerProxy IOError: " + evt);
}
//Accessors
public function get systemIsWindows():Boolean
{
return systemIsWindowsProperty;
}
public function get opening():Boolean
{
return openingProperty;
}
override public function get connected():Boolean
{
return connectedProperty;
}
}
}
TinkerProxyEvent
:
package com.mattie.events
{
//Imports
import flash.events.Event;
//Class
public class TinkerProxyEvent extends Event
{
//Constants
public static const LOADING:String = "Loading";
public static const INITIALIZING:String = "Initializing";
public static const CONNECT:String = "Connect";
public static const DISCONNECT:String = "Disconnect";
public static const ERROR:String = "Error";
//Constructor
public function TinkerProxyEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false)
{
super(type, bubbles, cancelable);
}
}
}