1 if (Garmin == undefined) var Garmin = {};
  2 /** Copyright � 2007 Garmin Ltd. or its subsidiaries.
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the 'License')
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *    http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an 'AS IS' BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  * 
 16  * @fileoverview Garmin.DeviceControl A high-level JavaScript API which supports listener and callback functionality.
 17  * 
 18  * @author Diana Chow diana.chow[at]garmin.com, Carlo Latasa carlo.latasa@garmin.com
 19  * @version 1.0
 20  */
 21 /** A controller object that can retrieve and send data to a Garmin 
 22  * device.<br><br>
 23  * @class Garmin.DeviceControl
 24  * 
 25  * The controller must be unlocked before anything can be done with it.  
 26  * Then you'll have to find a device before you can start to read data from
 27  * and write data to the device.<br><br>
 28  * 
 29  * We use the <a href="http://en.wikipedia.org/wiki/Observer_pattern">observer pattern</a> 
 30  * to handle the asynchronous nature of device communication.  You must register
 31  * your class as a listener to this Object and then implement methods that will 
 32  * get called on certain events.<br><br>
 33  * 
 34  * Events:<br><br>
 35  *     onStartFindDevices called when starting to search for devices.
 36  *       the object returned is {controller: this}<br><br>
 37  *
 38  *     onCancelFindDevices is called when the controller is told to cancel finding
 39  *         devices {controller: this}<br><br>
 40  *
 41  *     onFinishFindDevices called when the devices are found.
 42  *       the object returned is {controller: this}<br><br>
 43  *
 44  *     onException is called when an exception occurs in a method
 45  *         object passed back is {msg: exception}<br><br>
 46  *
 47  *	   onInteractionWithNoDevice is called when the device is lazy loaded, but finds no devices,
 48  * 			yet still attempts a read/write action {controller: this}<br><br>
 49  * 
 50  *     onStartReadFromDevice is called when the controller is about to start
 51  *         reading from the device {controller: this}<br><br>
 52  * 
 53  *     onFinishReadFromDevice is called when the controller is done reading 
 54  *         the device.  the read is either a success or failure, which is 
 55  *         communicated via json.  object passed back contains 
 56  *         {success:this.garminPlugin.GpsTransferSucceeded, controller: this} <br><br>
 57  *
 58  *     onWaitingReadFromDevice is called when the controller is waiting for input
 59  *         from the user about the device.  object passed back contains: 
 60  *         {message: this.garminPlugin.MessageBoxXml, controller: this}<br><br>
 61  *
 62  *     onProgressReadFromDevice is called when the controller is still reading information
 63  *         from the device.  in this case the message is a percent complete/ 
 64  *         {progress: this.getDeviceStatus(), controller: this}<br><br>
 65  *
 66  *     onCancelReadFromDevice is called when the controller is told to cancel reading
 67  *         from the device {controller: this}<br><br>
 68  *
 69  *     onFinishWriteToDevice is called when the controller is done writing to 
 70  *         the device.  the write is either a success or failure, which is 
 71  *         communicated via json.  object passed back contains 
 72  *         {success:this.garminPlugin.GpsTransferSucceeded, controller: this}<br><br>
 73  *
 74  *     onWaitingWriteToDevice is called when the controller is waiting for input
 75  *         from the user about the device.  object passed back contains: 
 76  *         {message: this.garminPlugin.MessageBoxXml, controller: this}<br><br>
 77  *
 78  *     onProgressWriteToDevice is called when the controller is still writing information
 79  *         to the device.  in this case the message is a percent complete/ 
 80  *         {progress: this.getDeviceStatus(), controller: this}<br><br>
 81  *
 82  *     onCancelWriteToDevice is called when the controller is told to cancel writing
 83  *         to the device {controller: this}<br><br>
 84  *
 85  * @constructor 
 86  *
 87  * requires Prototype
 88  * @requires BrowserDetect
 89  * @requires Garmin.DevicePlugin
 90  * @requires Garmin.Broadcaster
 91  * @requires Garmin.XmlConverter
 92  */
 93 Garmin.DeviceControl = function(){}; //just here for jsdoc
 94 Garmin.DeviceControl = Class.create();
 95 Garmin.DeviceControl.prototype = {
 96 
 97 
 98 	/////////////////////// Initialization Code ///////////////////////	
 99 
100     /** Instantiates a Garmin.DeviceControl object, but does not unlock/activate plugin.
101      */
102 	initialize: function() {
103 		
104 		this.pluginUnlocked = false;
105 		
106 		try {
107 			if (typeof(Garmin.DevicePlugin) == 'undefined') throw '';
108 		} catch(e) {
109 			throw new Error(Garmin.DeviceControl.MESSAGES.deviceControlMissing);
110 		};
111 
112     	// check that the browser is supported
113      	if(!BrowserSupport.isBrowserSupported()) {
114     	    var notSupported = new Error(Garmin.DeviceControl.MESSAGES.browserNotSupported);
115     	    notSupported.name = "BrowserNotSupportedException";
116     	    //console.debug("Control.validatePlugin throw BrowserNotSupportedException")
117     	    throw notSupported;
118         }
119 		
120 		// make sure the browser has the plugin installed
121 		if (!PluginDetect.detectGarminCommunicatorPlugin()) {
122      	    var notInstalled = new Error(Garmin.DeviceControl.MESSAGES.pluginNotInstalled);
123     	    notInstalled.name = "PluginNotInstalledException";
124     	    throw notInstalled;			
125 		}
126 				
127 		// grab the plugin object on the page
128 		var pluginElement;
129 		if( window.ActiveXObject ) { // IE
130 			pluginElement = $("GarminActiveXControl");
131 		} else { // FireFox
132 			pluginElement = $("GarminNetscapePlugin");
133 		}
134 		
135 		// make sure the plugin object exists on the page
136 		if (pluginElement == null) {
137 			var error = new Error(Garmin.DeviceControl.MESSAGES.missingPluginTag);
138 			error.name = "HtmlTagNotFoundException";
139 			throw error;			
140 		}
141 		
142 		// instantiate a garmin plugin
143 		this.garminPlugin = new Garmin.DevicePlugin(pluginElement);
144 		 
145 		// validate the garmin plugin
146 		this.validatePlugin();
147 		
148 		// instantiate a broacaster
149 		this._broadcaster = new Garmin.Broadcaster();
150 
151 		this.getDetailedDeviceData = true;
152 		this.devices = new Array();
153 		this.deviceNumber = null;
154 		this.numDevices = 0;
155 
156 		this.gpsData = null;
157 		this.gpsDataType = null; //used by both read and write methods to track data context
158 		this.gpsDataString = "";
159 		this.gpsDataStringCompressed = "";  // Compresed version of gpsDataString.  gzip compressed and base 64 expanded.
160 		
161 		//this.wasMessageHack = false; //needed because garminPlugin.finishDownloadData returns true after out-of-memory error message is returned
162 	},
163 
164 	/** Checks plugin validity: browser support, installation and required version.
165 	 * @private
166      * @throws BrowserNotSupportedException
167      * @throws PluginNotInstalledException
168      * @throws OutOfDatePluginException
169      */
170     validatePlugin: function() {
171 		if (!this.isPluginInstalled()) {
172      	    var notInstalled = new Error(Garmin.DeviceControl.MESSAGES.pluginNotInstalled);
173     	    notInstalled.name = "PluginNotInstalledException";
174     	    throw notInstalled;
175         }
176 		if(this.garminPlugin.isPluginOutOfDate()) {
177     	    var outOfDate = new Error(Garmin.DeviceControl.MESSAGES.outOfDatePlugin1+Garmin.DevicePlugin.REQUIRED_VERSION.toString()+Garmin.DeviceControl.MESSAGES.outOfDatePlugin2+this.getPluginVersionString());
178     	    outOfDate.name = "OutOfDatePluginException";
179     	    outOfDate.version = this.getPluginVersionString();
180     	    throw outOfDate;
181         }
182     },
183     
184     /** Checks plugin for updates.  Throws an exception if the user's plugin version is
185      * older than the one set by the API.
186      * 
187      * Plugin updates are not required so use this function with caution.
188      * @see #setPluginLatestVersion
189      */
190     checkForUpdates: function() {
191     	if(this.garminPlugin.isUpdateAvailable()) {
192     		var notLatest = new Error(Garmin.DeviceControl.MESSAGES.updatePlugin1+Garmin.DevicePlugin.LATEST_VERSION.toString()+Garmin.DeviceControl.MESSAGES.updatePlugin2+this.getPluginVersionString());
193     	    notLatest.name = "UpdatePluginException";
194     	    notLatest.version = this.getPluginVersionString();
195     	    throw notLatest;
196     	}
197     },
198     
199 	/////////////////////// Device Handling Methods ///////////////////////	
200 
201 	/** Finds any connected Garmin Devices.  
202      * When it's done finding the devices, onFinishFindDevices is dispatched <br/>
203      * <br/>
204      * this.numDevices = the number of devices found<br/>
205      * this.deviceNumber is the device that we'll use to communicate with<br/>
206      * <br/>
207      * Use this.getDevices() to get an array of the found devices and 
208      * this.setDeviceNumber({Number}) to change the device. <br/>
209      * <br/>
210      * Minimum Plugin version 2.0.0.4
211      * 
212      * @see #getDevices
213      * @see #setDeviceNumber
214      */	
215 	findDevices: function() {
216 		if (!this.isUnlocked())
217 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
218         this.garminPlugin.startFindDevices();
219 	    this._broadcaster.dispatch("onStartFindDevices", {controller: this});
220         setTimeout(function() { this._finishFindDevices() }.bind(this), 1000);
221 	},
222 
223 	/** Cancels the current find devices interaction. <br/>
224 	 * <br/>
225 	 * Minimum Plugin version 2.0.0.4
226      */	
227 	cancelFindDevices: function() {
228 		this.garminPlugin.cancelFindDevices();
229     	this._broadcaster.dispatch("onCancelFindDevices", {controller: this});
230 	},
231 
232 	/** Loads device data into devices array.
233 	 * 
234 	 * Minimum Plugin version 2.0.0.4
235 	 * 
236 	 * @private
237      */	
238 	_finishFindDevices: function() {
239     	if(this.garminPlugin.finishFindDevices()) {
240             //console.debug("_finishFindDevices devXml="+this.garminPlugin.getDevicesXml())
241             this.devices = Garmin.PluginUtils.parseDeviceXml(this.garminPlugin, this.getDetailedDeviceData);
242             //console.debug("_finishFindDevices devXml="+this.garminPlugin.getDevicesXml())
243             
244             this.numDevices = this.devices.length;
245        		this.deviceNumber = 0;
246 	        this._broadcaster.dispatch("onFinishFindDevices", {controller: this});
247     	} else {
248     		setTimeout(function() { this._finishFindDevices() }.bind(this), 500);
249     	}
250 	},
251 
252 	/** Sets the deviceNumber variable which determines which connected device to talk to.
253      * @param {Number} deviceNumber The device number
254      */	
255 	setDeviceNumber: function(deviceNumber) {
256 		this.deviceNumber = deviceNumber;
257 	},
258 	
259 	/**
260 	 * Get the device number of the connected device to communicate with (multiple devices may
261 	 * be connected simultaneously, but the plugin only transfers data with one at a time).
262 	 * @return the device number (assigned by the plugin) determining which connected device
263 	 * to talk to.
264 	 */
265 	getDeviceNumber: function() {
266         return this.deviceNumber;
267 	},
268 
269 	/** Get a list of the devices found
270      * @type Array<Garmin.Device>
271      */	
272 	getDevices: function() {
273 		return this.devices;
274 	},
275 	
276 	/** Returns the DeviceXML of the current device, as a string.
277 	 */
278 	getCurrentDeviceXml: function() {
279 		return this.garminPlugin.getDeviceDescriptionXml(this.deviceNumber);		
280 	},
281 
282 	/////////////////////// Read Methods ///////////////////////
283 	
284 	/** Generic read method, supporting GPX and TCX Fitness types: Courses, Workouts, User Profiles, Activity Goals, 
285 	 * TCX activity directory, and TCX course directory reads. <br/>
286 	 * <br/>
287 	 * Fitness detail reading (one specific activity) is not supported by this read method, refer to 
288 	 * readDetailFromDevice for that. <br/>  
289 	 * 
290 	 * @param {String} fileType the filetype to read from device.  Possible values for fileType are located in Garmin.DeviceControl.FILE_TYPES--detail 
291 	 * types are not supported.
292 	 * @see #readDetailFromDevice, Garmin.DeviceControl#FILE_TYPES
293 	 * @throws InvalidTypeException, UnsupportedTransferTypeException
294      */	
295 	readDataFromDevice: function(fileType) {
296 		if (!this.isUnlocked())
297 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
298 		if (this.numDevices == 0)
299 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
300 		// Make sure filetype passed in is a valid type
301 		if ( ! this._isAMember(fileType, [Garmin.DeviceControl.FILE_TYPES.gpx,
302 		                                  Garmin.DeviceControl.FILE_TYPES.gpxDetail, 
303 										  Garmin.DeviceControl.FILE_TYPES.gpxDir,
304 		                                  Garmin.DeviceControl.FILE_TYPES.tcx, 
305 		                                  Garmin.DeviceControl.FILE_TYPES.crs, 
306 										  Garmin.DeviceControl.FILE_TYPES.tcxDir, 
307 										  Garmin.DeviceControl.FILE_TYPES.crsDir, 
308 										  Garmin.DeviceControl.FILE_TYPES.wkt, 
309 										  Garmin.DeviceControl.FILE_TYPES.tcxProfile,
310 										  Garmin.DeviceControl.FILE_TYPES.goals,
311 										  Garmin.DeviceControl.FILE_TYPES.fit,
312 										  Garmin.DeviceControl.FILE_TYPES.fitDir
313 										  ])) {
314 			var error = new Error(Garmin.DeviceControl.MESSAGES.invalidFileType + fileType);
315 			error.name = "InvalidTypeException";
316 			throw error;
317 		} 
318 		// Make sure the device supports this type of data transfer for this type
319 		if( !this.checkDeviceReadSupport(fileType) ) {
320 		    var error = new Error(Garmin.DeviceControl.MESSAGES.unsupportedReadDataType + fileType);
321     	    error.name = "UnsupportedDataTypeException";
322 			throw error;
323 		}
324 		this.gpsDataType = fileType;
325 		this.gpsData = null;		
326 		this.gpsDataString = null;
327 		this.idle = false;
328 		
329 		try {
330         	this._broadcaster.dispatch("onStartReadFromDevice", {controller: this});
331         	
332         	switch(this.gpsDataType) {        		
333 				case Garmin.DeviceControl.FILE_TYPES.gpxDir:
334         		case Garmin.DeviceControl.FILE_TYPES.gpx:
335         			this.garminPlugin.startReadFromGps( this.deviceNumber );
336         			break;
337         		case Garmin.DeviceControl.FILE_TYPES.tcx:
338         		case Garmin.DeviceControl.FILE_TYPES.crs:
339         		case Garmin.DeviceControl.FILE_TYPES.wkt:
340         		case Garmin.DeviceControl.FILE_TYPES.goals:
341         		case Garmin.DeviceControl.FILE_TYPES.tcxProfile:        		
342         			this.garminPlugin.startReadFitnessData( this.deviceNumber, this.gpsDataType );
343         			break;
344         		case Garmin.DeviceControl.FILE_TYPES.tcxDir:
345         			this.garminPlugin.startReadFitnessDirectory(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.tcx);
346         			break;
347         		case Garmin.DeviceControl.FILE_TYPES.crsDir:
348         			this.garminPlugin.startReadFitnessDirectory(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.crs);
349         			break;
350         		case Garmin.DeviceControl.FILE_TYPES.fitDir:
351                     this.garminPlugin.startReadFitDirectory(this.deviceNumber);
352         			break;
353         		case Garmin.DeviceControl.FILE_TYPES.deviceXml:
354         			this.gpsDataString = this.getCurrentDeviceXml();
355         			break;
356         	} 
357 		    this._progressRead();
358 		} catch(e) {
359 		    this._reportException(e);
360 		}
361 	},
362 	
363 	/** Generic detail read method, which reads a specific fitness activity from the device given an activity ID.  
364 	 * Supported detail types are history activities and course activities.  The resulting data read is available 
365 	 * in via gpsData as an XML DOM and gpsDataString as an XML string once the read successfully finishes. 
366 	 * Typically used after calling readDataFromDevice to read a fitness directory.<br/> 
367 	 * <br/>
368 	 * Minimum plugin version 2.2.0.2
369 	 * 
370 	 * @param {String} fileType Filetype to be read from the device.  Supported values are 
371 	 * Garmin.DeviceControl.FILE_TYPES.tcxDetail and Garmin.DeviceControl.FILE_TYPES.crsDetail
372 	 * @param {String} dataId The ID of the data to be read from the device.  The format of these values depends 
373 	 * on the type of data being read (i.e. course data or history data). The CourseName element in the course schema 
374 	 * is used to identify courses, and the Id element is used to identify history activities.
375 	 * @see #readDataFromDevice, #readHistoryDetailFromFitnessDevice, #readCourseDetailFromFitnessDevice
376 	 * @throws InvalidTypeException, UnsupportedTransferTypeException
377 	 */
378 	readDetailFromDevice: function(fileType, dataId) {
379 		if (!this.isUnlocked())
380 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
381 		if (this.numDevices == 0)
382 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
383 		if ( ! this._isAMember(fileType, [Garmin.DeviceControl.FILE_TYPES.tcxDetail, Garmin.DeviceControl.FILE_TYPES.crsDetail])) {
384 			var error = new Error(Garmin.DeviceControl.MESSAGES.invalidFileType + fileType);
385 			error.name = "InvalidTypeException";
386 			throw error;
387 		}
388 		if( !this.checkDeviceReadSupport(fileType) ) {
389 			throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedReadDataType + fileType);
390 		}
391 		
392 		this.gpsDataType = fileType;
393 		this.gpsData = null;		
394 		this.gpsDataString = null;
395 		this.idle = false;
396 		
397 		try {
398         	this._broadcaster.dispatch("onStartReadFromDevice", {controller: this});
399         	
400         	switch(this.gpsDataType) {
401         		case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
402         			this.garminPlugin.startReadFitnessDetail(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.tcx, dataId);
403         			break;
404         		case Garmin.DeviceControl.FILE_TYPES.crsDetail:
405         			this.garminPlugin.startReadFitnessDetail(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.crs, dataId);
406         			break;
407         	} 
408 		    this._progressRead();
409 		} catch(e) {
410 		    this._reportException(e);
411 		}
412 	},
413 	
414 	/** Asynchronously reads GPX data from the connected device.  Only handles reading
415      * from the device in this.deviceNumber. <br/>
416      * <br/>
417      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
418      * data is stored in this.gpsDataString and this.gpsData
419      * 
420      * @see #readDataFromDevice
421      */
422 	readFromDevice: function() {
423 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.gpx);
424 	},
425 	
426 	/** Asynchronously reads a single fitness history record from the connected device as TCX format.
427 	 * Only handles reading from the device in this.deviceNumber.<br/>
428 	 * <br/>
429      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
430      * data is stored in this.gpsDataString
431      * 
432      * Minimum plugin version 2.2.0.2
433      * 
434      * @param {String} historyId The ID of the history record on the device.
435      * 
436      * @see #readDetailFromDevice
437      */	
438 	readHistoryDetailFromFitnessDevice: function(historyId) {
439 		this.readDetailFromDevice(Garmin.DeviceControl.FILE_TYPES.tcx, historyId)
440 	},
441 	
442 	/** Asynchronously reads a single fitness course from the connected device as TCX format.
443 	 * Only handles reading from the device in this.deviceNumber. <br/>
444      * <br/>
445      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
446      * data is stored in this.gpsDataString<br/>
447      * <br/>
448      * Minimum plugin version 2.2.0.2
449      * 
450      * @param {String} courseId The name of the course on the device.
451      * 
452      * @see #readDetailFromDevice
453      */			
454 	readCourseDetailFromFitnessDevice: function(courseId){
455 		this.readDetailFromDevice(Garmin.DeviceControl.FILE_TYPES.crs, courseId)
456 	},
457 	
458 	/** Asynchronously reads entire fitness history data (TCX) from the connected device.  
459 	 * Only handles reading from the device in this.deviceNumber. <br/>
460      * <br/>
461      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
462      * data is stored in this.gpsDataString<br/>
463      * <br/>
464      * Minimum plugin version 2.1.0.3
465      * 
466      * @see #readDataFromDevice
467      */	
468 	readHistoryFromFitnessDevice: function() {	
469 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.tcx);
470 	},
471 	
472 	/** Asynchronously reads entire fitness course data (CRS) from the connected device.  
473 	 * Only handles reading from the device in this.deviceNumber<br/>
474      * <br/>
475      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
476      * data is stored in this.gpsDataString<br/>
477      * <br/>
478      * Minimum plugin version 2.2.0.1
479      * 
480      * @see #readDataFromDevice
481      */	
482 	readCoursesFromFitnessDevice: function() {
483 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.crs);
484 	},
485 	
486 	/** Asynchronously reads fitness workout data (WKT) from the connected device.  
487 	 * Only handles reading from the device in this.deviceNumber<br/>
488      * <br/>
489      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
490      * data is stored in this.gpsDataString<br/>
491      * <br/>
492      * Minimum plugin version 2.2.0.1
493      * 
494      * @see #readDataFromDevice
495      */	
496 	readWorkoutsFromFitnessDevice: function() {
497 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.wkt);
498 	},
499 	
500 	/** Asynchronously reads fitness profile data (TCX) from the connected device.
501 	 * Only handles reading from the device in this.deviceNumber<br/>
502      * <br/>
503      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
504      * data is stored in this.gpsDataString<br/>
505      * <br/>
506      * Minimum plugin version 2.2.0.1
507      * 
508      * @see #readDataFromDevice
509      */	
510 	readUserProfileFromFitnessDevice: function() {
511 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.tcxProfile);
512 	},
513 
514 	/** Asynchronously reads fitness goals data (TCX) from the connected device.
515 	 * Only handles reading from the device in this.deviceNumber<br/>
516      * <br/>
517      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
518      * data is stored in this.gpsDataString<br/>
519      * <br/>
520      * Minimum plugin version 2.2.0.1
521      * 
522      * @see #readDataFromDevice
523      */	
524 	readGoalsFromFitnessDevice: function() {
525 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.goals);
526 	},
527 	
528 	
529 	
530 	/** Returns the GPS data that was last read as an XML DOM. <br/> 
531 	 * Pre-requisite - Read function was called successfully.  <br/> 
532 	 * <br/>
533 	 * Minimum plugin version 2.1.0.3
534 	 * 
535 	 * @return XML DOM of read GPS data
536 	 * @see #readDataFromDevice
537 	 * @see #readHistoryFromFitnessDevice
538 	 * @see #readHistoryDetailFromFitnessDevice
539 	 * @see #readCourseDetailFromFitnessDevice
540 	 */
541 	getGpsData: function() {
542 		
543 		if (!this.isUnlocked())
544 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
545 		if (this.numDevices == 0)
546 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
547 		if( this.getReadCompletionState != Garmin.DeviceControl.FINISH_STATES.finished ) {
548 			throw new Error(Garmin.DeviceControl.MESSAGES.incompleteRead);
549 		}
550 		
551 		return this.gpsData;
552 	},
553 	
554 	/** Returns the GPS data that was last read as an XML string. <br/>  
555 	 * Pre-requisite - Read function was called successfully. <br/>
556 	 * <br/>
557 	 * Minimum plugin version 2.1.0.3
558 	 * 
559 	 * @return XML string of read GPS data
560 	 * @see #readDataFromDevice
561 	 * @see #readHistoryFromFitnessDevice
562 	 * @see #readHistoryDetailFromFitnessDevice
563 	 * @see #readCourseDetailFromFitnessDevice
564 	 */
565 	getGpsDataString: function() {
566 		if (!this.isUnlocked())
567 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
568 		if (this.numDevices == 0)
569 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
570 		if( this.getReadCompletionState != Garmin.DeviceControl.FINISH_STATES.finished ) {
571 			throw new Error(Garmin.DeviceControl.MESSAGES.incompleteRead);
572 		}
573 		
574 		return this.gpsDataString;
575 	},
576 	
577 	/** Returns the last read fitness data in compressed format.  A fitness read method must be called and the read must
578 	 * finish successfully before this function returns good data. <br/>
579 	 * <br/>
580 	 * Minimum plugin version 2.2.0.2
581 	 * 
582 	 * @return Compressed fitness XML data from the last successful read.  The data is gzp compressed and base64 expanded.
583 	 * @see #readDataFromDevice
584 	 * @see #readHistoryFromFitnessDevice
585 	 * @see #readHistoryDetailFromFitnessDevice
586 	 * @see #readCourseDetailFromFitnessDevice
587 	 */
588 	getCompressedFitnessData: function() {
589 		
590 		if (!this.isUnlocked())
591 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
592 		if (this.numDevices == 0)
593 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
594 		if( this.getReadCompletionState != Garmin.DeviceControl.FINISH_STATES.finished ) {
595 			throw new Error(Garmin.DeviceControl.MESSAGES.incompleteRead);
596 		}
597 
598 		try{
599 			this.garminPlugin.getTcdXmlz();
600 		}
601 		catch( aException ) {
602  			this._reportException( aException );
603 		}
604 	},
605 
606 	/** Returns the completion state of the current read.  This function can be used with
607 	 * GPX and TCX (fitness) reads.
608 	 * 
609 	 * @type Number
610 	 * @return {Number} The completion state of the current read.  The completion state can be one of the following: <br/>
611 	 *  <br/>
612 	 *	0 = idle <br/>
613  	 * 	1 = working <br/>
614  	 * 	2 = waiting <br/>
615  	 * 	3 = finished <br/>
616 	 */	
617 	getReadCompletionState: function() {
618 		switch(this.gpsDataType) {
619 			case Garmin.DeviceControl.FILE_TYPES.gpxDir:
620 			case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
621 			case Garmin.DeviceControl.FILE_TYPES.gpx:
622 				return this.garminPlugin.finishReadFromGps();
623 			case Garmin.DeviceControl.FILE_TYPES.tcx:
624 			case Garmin.DeviceControl.FILE_TYPES.crs:
625 			case Garmin.DeviceControl.FILE_TYPES.wkt:
626 			case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
627 			case Garmin.DeviceControl.FILE_TYPES.goals:
628 				return this.garminPlugin.finishReadFitnessData();
629 			case Garmin.DeviceControl.FILE_TYPES.tcxDir:
630 			case Garmin.DeviceControl.FILE_TYPES.crsDir:
631 				return this.garminPlugin.finishReadFitnessDirectory();
632 			case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
633 			case Garmin.DeviceControl.FILE_TYPES.crsDetail:
634 				return this.garminPlugin.finishReadFitnessDetail();
635 			case Garmin.DeviceControl.FILE_TYPES.fitDir:
636                 return this.garminPlugin.finishReadFitDirectory();
637 		}
638 	},
639 	
640 	/** Internal read dispatching and polling delay.
641 	 * @private
642      */	
643 	_progressRead: function() {
644 		this._broadcaster.dispatch("onProgressReadFromDevice", {progress: this.getDeviceStatus(), controller: this});
645         setTimeout(function() { this._finishReadFromDevice() }.bind(this), 200); //200		 
646 	},
647 	
648 	/** Internal read state logic. <br/>
649 	 * <br/>
650 	 * Minimum plugin version 2.0.0.4 for GPX and TCX history read.<br/>
651 	 * Minimum plugin version 2.2.0.2 for directory and detail read.<br/>
652 	 * Minimum plugin version 2.2.0.2 for compressed file get.
653 	 * 
654 	 * @private
655      */	
656 	_finishReadFromDevice: function() {
657 		var completionState = this.getReadCompletionState();
658 		
659 		//console.debug("control._finishReadFromDevice this.gpsDataType="+this.gpsDataType+" completionState="+completionState)
660         try {
661         	
662 			if( completionState == Garmin.DeviceControl.FINISH_STATES.finished ) {
663 	        	switch( this.gpsDataType ) {
664 					case Garmin.DeviceControl.FILE_TYPES.gpxDir:
665 	        		case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
666 	        		case Garmin.DeviceControl.FILE_TYPES.gpx:
667 	        			if (this.garminPlugin.gpsTransferSucceeded()) {
668 		        			this.gpsDataString = this.garminPlugin.getGpsXml();
669 							this.gpsData = Garmin.XmlConverter.toDocument(this.gpsDataString);
670 							this._broadcaster.dispatch("onFinishReadFromDevice", {success: this.garminPlugin.gpsTransferSucceeded(), controller: this});	
671 	        			}
672 						break;
673 					case Garmin.DeviceControl.FILE_TYPES.tcx:
674 					case Garmin.DeviceControl.FILE_TYPES.crs:
675 					case Garmin.DeviceControl.FILE_TYPES.tcxDir:
676 					case Garmin.DeviceControl.FILE_TYPES.crsDir:
677 					case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
678 					case Garmin.DeviceControl.FILE_TYPES.crsDetail:
679 					case Garmin.DeviceControl.FILE_TYPES.wkt:
680 					case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
681 					case Garmin.DeviceControl.FILE_TYPES.goals:
682 						if (this.garminPlugin.fitnessTransferSucceeded()) {
683 							this.gpsDataString = this.garminPlugin.getTcdXml();
684 							this.gpsDataStringCompressed = this.garminPlugin.getTcdXmlz();
685 							
686 							this.gpsData = Garmin.XmlConverter.toDocument(this.gpsDataString);
687 							this._broadcaster.dispatch("onFinishReadFromDevice", {success: this.garminPlugin.fitnessTransferSucceeded(), controller: this});										
688 						}
689 						break;
690 					case Garmin.DeviceControl.FILE_TYPES.fitDir:
691                         // TODO is there a fitness transfer succeeded with fit?
692 //					    if(this.garminPlugin.fitnessTransferSucceeded()) {
693 					        this.gpsDataString = this.garminPlugin.getDirectoryXml();
694 //							this.gpsDataStringCompressed = this.garminPlugin.getTcdXmlz();
695 							this.gpsData = Garmin.XmlConverter.toDocument(this.gpsDataString);
696 							this._broadcaster.dispatch("onFinishReadFromDevice", {success: this.garminPlugin.fitnessTransferSucceeded(), controller: this});
697 //					    }
698                         break;
699 	        	}
700 			} else if( completionState == Garmin.DeviceControl.FINISH_STATES.messageWaiting ) {
701 				var msg = this._messageWaiting();
702 				this._broadcaster.dispatch("onWaitingReadFromDevice", {message: msg, controller: this});
703 			} else {
704 	    	    this._progressRead();
705 			}
706 		} catch( aException ) {
707  			this._reportException( aException );
708 		}
709     },
710 
711 	/** User canceled the read. <br/>
712 	 * <br/>
713 	 * Minimum plugin version 2.0.0.4
714      */	
715 	cancelReadFromDevice: function() {
716 		if (this.gpsDataType == Garmin.DeviceControl.FILE_TYPES.gpx) {
717 			this.garminPlugin.cancelReadFromGps();
718 		} else {
719 			this.garminPlugin.cancelReadFitnessData();
720 		}
721     	this._broadcaster.dispatch("onCancelReadFromDevice", {controller: this});
722 	},
723 	
724 	
725 	/** Return the specified file as a UU-Encoded string
726      * <br/>
727      * Minimum version 2.6.3.1
728      * 
729      * If the file is known to be compressed, compressed should be
730      * set to false. Otherwise, set compressed to true to retrieve a
731      * gzipped and uuencoded file.
732      * 
733      * @param relativeFilePath {String} path relative to the Garmin folder on the device
734      */
735      getBinaryFile: function(deviceNumber, relativeFilePath) {
736         if (!this.isUnlocked())
737 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
738 		if(this.numDevices == 0)
739 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
740         // Attempt to detect Fit file
741         if(relativeFilePath.capitalize().endsWith(".fit")) {
742             // Capitalize makes all but first letters lowercase. I can't believe prototype doesn't have a lowercase method. :(
743             this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.fit;
744         } else {
745     		this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.binary;
746         }
747 		var success;
748 		try {
749 		    this.gpsDataString = this.garminPlugin.getBinaryFile(deviceNumber, relativeFilePath, false);
750 		    this.gpsDataStringCompressed = this.garminPlugin.getBinaryFile(deviceNumber, relativeFilePath, true);
751 //		    this.gpsData = this.garminPlugin.getBinaryFile(deviceNumber, relativeFilePath, compressed);
752 		    success = true;
753 	    } catch(e) {
754 	        success = false;
755 			this._reportException(e);
756 	    }
757 	    
758 	    this._broadcaster.dispatch("onFinishReadFromDevice", {success: success, controller: this});
759         return this.gpsData;
760      },
761 
762 	/////////////////////// Web Drop Methods (Write) ///////////////////////
763 	
764     /** Writes an address to the currently selected device.
765      * 
766      * @param {String} address The address to be written to the device. This doesn't check validity
767      */	
768 	writeAddressToDevice: function(address) {
769 		if (!this.isUnlocked())
770 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
771 		if (!this.geocoder) {
772 			this.geocoder = new Garmin.Geocode();
773 			this.geocoder.register(this);
774 		}
775 		this.geocoder.findLatLng(address);
776 	},
777 
778 	/** Handles call-back from geocoder and forwards call to onException on registered listeners.
779 	 * @private
780      * @param {Error} json error wrapped in JSON 'msg' object.
781      */
782 	onException: function(json) {
783 		this._reportException(json.msg);
784 	},
785 	
786 	/** Handles call-back from geocoder and forwards call to writeToDevice.
787 	 * Registered listeners will recieve an onFinishedFindLatLon call before writeToDevice is invoked.
788 	 * Listeners can change the 'fileName' if they choose avoiding overwritting old waypoints on
789 	 * some devices.
790 	 * @private
791      * @param {Object} json waypoint, fileName and controller in JSON wrapper.
792      */
793 	onFinishedFindLatLon: function(json) {
794 		json.fileName = "address.gpx";
795 		json.controller = this;
796 		this._broadcaster.dispatch("onFinishedFindLatLon", json);
797    		var factory = new Garmin.GpsDataFactory();
798 		var gpxStr = factory.produceGpxString(null, [json.waypoint]);
799 		this.writeToDevice(gpxStr, json.fileName);
800 	},
801 
802 	/////////////////////// More Write Methods ///////////////////////	
803     /**
804      * Generic write method for GPX and TCX file formats.  For binary write, use {@link downloadToDevice}.
805      * 
806      * @param {String} dataType - the datatype to write to device.  Possible values are located in {@link #Garmin.DeviceControl.FILE_TYPES}
807      * @param {String} dataString - the datastring to write to device.  Should be in the format of the dataType value.
808      * @param {String} fileName - the filename to write the data to on the device. File extension is not necessary, 
809      * but is suggested for device compatibility.  This parameter is ignored when the dataType value is FitnessActivityGoals (see {@link #writeGoalsToFitnessDevice}).
810      * @see #writeToDevice, #writeFitnessToDevice
811      * @throws InvalidTypeException, UnsupportedTransferTypeException
812      */ 
813     writeDataToDevice: function(dataType, dataString, fileName) {
814         if (!this.isUnlocked()) {
815 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
816         }
817         
818 		if (this.numDevices == 0) {
819 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
820         }
821 
822 		this.gpsDataType = dataType;
823         
824 		if (!this.checkDeviceWriteSupport(this.gpsDataType)) {
825 			throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedWriteDataType + this.gpsDataType);
826 		}
827 		
828 		try {
829         	this._broadcaster.dispatch("onStartWriteToDevice", {controller: this});
830             
831         	switch(this.gpsDataType) {
832             	case Garmin.DeviceControl.FILE_TYPES.gpx:
833         			this.garminPlugin.startWriteToGps(dataString, fileName, this.deviceNumber);
834         			break;
835         		case Garmin.DeviceControl.FILE_TYPES.crs:
836         		case Garmin.DeviceControl.FILE_TYPES.wkt:
837         		case Garmin.DeviceControl.FILE_TYPES.goals:
838         		case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
839         		case Garmin.DeviceControl.FILE_TYPES.nlf:                
840         			this.garminPlugin.startWriteFitnessData(dataString, this.deviceNumber, fileName, this.gpsDataType);
841         			break;
842         		default:
843 					throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedWriteDataType + this.gpsDataType);
844         	}
845 		    this._progressWrite();
846 	    } catch(e) {
847 			this._reportException(e);
848 	   	}
849     },
850     
851     /** Writes the given GPX XML string to the device selected in this.deviceNumber. <br/>
852      * <br/>
853      * Minimum plugin version 2.0.0.4
854      * 
855      * @param gpxString XML to be written to the device. This doesn't check validity.
856      * @param fileName The filename to write data to.  Validity is not checked here.
857      */	
858 	writeToDevice: function(gpxString, fileName) {
859         this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.gpx, gpxString, fileName);	    
860 	},
861 
862 	/** DEPRECATED - See {@link #writeCoursesToFitnessDevice}<br/> 
863 	 * <br/>
864 	 * Writes fitness course data (TCX) to the device selected in this.deviceNumber. <br/>
865 	 * <br/>
866 	 * Minimum plugin version 2.2.0.1
867 	 * 
868      * @param tcxString {String} TCX Course XML string to be written to the device. This doesn't check validity.
869      * @param fileName {String} filename to write data to on the device.  Validity is not checked here.
870      */	
871 	writeFitnessToDevice: function(tcxString, fileName) {
872 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.crs, tcxString, fileName);
873 	},
874 
875 	/** Writes fitness course data (TCX) to the device selected in this.deviceNumber. <br/>
876 	 * <br/>
877 	 * Minimum plugin version 2.2.0.1
878 	 * 
879      * @param tcxString {String} TCX Course XML string to be written to the device. This doesn't check validity.
880      * @param fileName {String} filename to write data to on the device.  Validity is not checked here.
881      */	
882 	writeCoursesToFitnessDevice: function(tcxString, fileName) {
883 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.crs, tcxString, fileName);
884 	},
885 
886 	/** Writes fitness goals data (TCX) string to the device selected in this.deviceNumber. All fitness goals
887 	 * are written to the filename 'ActivityGoals.TCX' in the device's goals directory, in order for the device
888 	 * to recognize the file.<br/> 
889 	 * <br/>
890 	 * Minimum plugin version 2.2.0.1
891 	 * 
892      * @param tcxString {String} ActivityGoals TCX string to be written to the device. This doesn't check validity.
893      */	
894 	writeGoalsToFitnessDevice: function(tcxString) {
895 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.goals, tcxString, '');
896 	},
897 	
898 	/** Writes fitness workouts data (XML) string to the device selected in this.deviceNumber. <br/>
899 	 * <br/>
900 	 * Minimum plugin version 2.2.0.1
901 	 * 
902      * @param tcxString XML (workouts) string to be written to the device. This doesn't check validity.
903      * @param fileName String of filename to write it to on the device.  Validity is not checked here.
904      */	
905 	writeWorkoutsToFitnessDevice: function(tcxString, fileName) {
906 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.wkt, tcxString, fileName);
907 	},
908 	
909 	/** Writes fitness user profile data (TCX) string to the device selected in this.deviceNumber. <br/>
910 	 * <br/>
911 	 * Minimum plugin version 2.2.0.1
912 	 * 
913      * @param tcxString XML (user profile) string to be written to the device. This doesn't check validity.
914      * @param fileName String of filename to write it to on the device.  Validity is not checked here.
915      */	
916 	writeUserProfileToFitnessDevice: function(tcxString, fileName) {
917 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.tcxProfile, tcxString, fileName);
918 	},
919 	
920 	/** Downloads and writes binary data asynchronously to device. <br/>
921 	 * <br/>
922 	 * Minimum plugin version 2.0.0.4
923      *
924      * @param xmlDownloadDescription {String} xml string containing information about the files to be downloaded onto the device.
925      * @param fileName {String} this parameter is ignored!  We will remove this param from the API in a future compatibility release.
926      */	
927 	downloadToDevice: function(xmlDownloadDescription) {
928 		if (!this.isUnlocked())
929 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
930 		if(this.numDevices == 0)
931 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
932 		this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.binary;
933 		try {
934 		    this.garminPlugin.startDownloadData(xmlDownloadDescription, this.deviceNumber );
935 		    this._progressWrite();
936 	    } catch(e) {
937 			this._reportException(e);
938 	    }
939 	},
940 	
941 	/** Internal dispatch and polling delay.
942 	 * @private
943      */	
944 	_progressWrite: function() {
945 		//console.debug("control._progressWrite gpsDataType="+this.gpsDataType)		
946     	this._broadcaster.dispatch("onProgressWriteToDevice", {progress: this.getDeviceStatus(), controller: this});
947         setTimeout(function() { this._finishWriteToDevice() }.bind(this), 200);
948 	},
949 	
950 	/** Internal write lifecycle handling.
951 	 * @private
952      */	
953 	_finishWriteToDevice: function() {
954         try {
955 			var completionState;
956 			var success;
957 			
958 			switch( this.gpsDataType ) {
959 				
960 				case Garmin.DeviceControl.FILE_TYPES.gpx : 
961 					completionState = this.garminPlugin.finishWriteToGps();
962 					success = this.garminPlugin.gpsTransferSucceeded();
963 					break;
964 				case Garmin.DeviceControl.FILE_TYPES.crs :
965 				case Garmin.DeviceControl.FILE_TYPES.goals :
966 				case Garmin.DeviceControl.FILE_TYPES.wkt :
967 				case Garmin.DeviceControl.FILE_TYPES.tcxProfile :
968 				case Garmin.DeviceControl.FILE_TYPES.nlf :
969 					completionState = this.garminPlugin.finishWriteFitnessData();
970 					success = this.garminPlugin.fitnessTransferSucceeded();
971 					break;
972 				case Garmin.DeviceControl.FILE_TYPES.gpi :
973 				case Garmin.DeviceControl.FILE_TYPES.fitCourse :
974 				case Garmin.DeviceControl.FILE_TYPES.fitSettings :
975 				case Garmin.DeviceControl.FILE_TYPES.fitSport :
976 				case Garmin.DeviceControl.FILE_TYPES.binary :
977 					completionState = this.garminPlugin.finishDownloadData();
978 					success = this.garminPlugin.downloadDataSucceeded();
979 					break;				
980 				case Garmin.DeviceControl.FILE_TYPES.firmware :
981 					completionState = this.garminPlugin.finishUnitSoftwareUpdate();
982 					success = this.garminPlugin.downloadDataSucceeded();
983 					break;				
984 				default:
985 					throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedWriteDataType + this.gpsDataType);
986 			}
987 			
988 			if( completionState == Garmin.DeviceControl.FINISH_STATES.finished ) {
989 				this._broadcaster.dispatch("onFinishWriteToDevice", {success: success, controller: this});											
990 			} else if( completionState == Garmin.DeviceControl.FINISH_STATES.messageWaiting ) {
991 				var msg = this._messageWaiting();
992 				this._broadcaster.dispatch("onWaitingWriteToDevice", {message: msg, controller: this});
993 			} else {
994 	    	     this._progressWrite();
995 			}
996 		} catch( aException ) {
997  			this._reportException( aException );
998 		}
999 	},
1000 
1001 	/** Cancels the current write transfer to the device. <br/>
1002 	 * <br/>
1003 	 * Minimum plugin version 2.0.0.4<br/>
1004      * Minimum plugin version 2.2.0.1 for writes of GPX to SD Card
1005      */	
1006 	cancelWriteToDevice: function() {
1007 		switch( this.gpsDataType) {
1008 			case Garmin.DeviceControl.FILE_TYPES.gpx:
1009 				this.garminPlugin.cancelWriteToGps();
1010 				break;
1011 			case Garmin.DeviceControl.FILE_TYPES.gpi:
1012 			case Garmin.DeviceControl.FILE_TYPES.binary:
1013 				this.garminPlugin.cancelDownloadData();
1014 				break;
1015 			case Garmin.DeviceControl.FILE_TYPES.firmware:
1016 				this.garminPlugin.cancelUnitSoftwareUpdate();
1017 				break;
1018 			case Garmin.DeviceControl.FILE_TYPES.crs:
1019 			case Garmin.DeviceControl.FILE_TYPES.goals:
1020 			case Garmin.DeviceControl.FILE_TYPES.wkt:
1021 			case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
1022 			case Garmin.DeviceControl.FILE_TYPES.nlf:
1023 				this.garminPlugin.cancelWriteFitnessData();
1024 				break;
1025 		}
1026 		this._broadcaster.dispatch("onCancelWriteToDevice", {controller: this});
1027 	},
1028 
1029     /**
1030 	 * Determine the amount of space available on a mass storage mode device (the
1031 	 * currently selected device according to this.deviceNumber). 
1032 	 * <br/> 
1033 	 * Minimum Plugin version 2.5.1
1034 	 * 
1035 	 * @param {String} relativeFilePath - if a file is being replaced, set to relative path on device, otherwise set to empty string.
1036 	 * @return -1 for non-mass storage mode devices.
1037 	 * @see downloadToDevice 
1038 	 */
1039 	bytesAvailable: function(relativeFilePath) {
1040 	    return this.garminPlugin.bytesAvailable(this.getDeviceNumber(), relativeFilePath);
1041 	},
1042 	
1043 	/** Download and install a list of unit software updates.  Start the asynchronous 
1044      * StartUnitSoftwareUpdate operation.
1045      * 
1046      * Check for completion with the FinishUnitSoftwareUpdate() method.  After
1047      * completion check the DownloadDataSucceeded property to make sure that all of the downloads 
1048      * were successfully placed on the device. 
1049      * 
1050      * See the Schema UnitSoftwareUpdatev3.xsd for the format of the UpdateResponsesXml description
1051      *
1052      * @see Garmin.DeviceControl.cancelWriteToDevice
1053      * @see Garmin.DevicePlugin.downloadDataSucceeded
1054      * @see Garmin.DevicePlugin._finishWriteToDevice
1055      * @version plugin v2.6.2.0
1056      */
1057     downloadFirmwareToDevice: function(updateResponsesXml) {
1058         if (!this.isUnlocked())
1059 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
1060 		if(this.numDevices == 0)
1061 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
1062 		this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.firmware;
1063 		try {
1064             this.garminPlugin.startUnitSoftwareUpdate(updateResponsesXml, this.deviceNumber);
1065 		    this._progressWrite();
1066 	    } catch(e) {
1067 			this._reportException(e);
1068 	    }
1069     },
1070 
1071 	/////////////////////// Support Methods ///////////////////////	
1072 
1073 
1074 	/** Unlocks the GpsControl object to be used at the given web address. <br/>
1075      * <br/>
1076      * Minimum Plugin version 2.0.0.4
1077      * 
1078      * @param {Array} pathKeyPairsArray baseURL and key pairs.  
1079      * @type Boolean 
1080      * @return True if the plug-in was unlocked successfully
1081      */
1082 	unlock: function(pathKeyPairsArray) {
1083 		this.pluginUnlocked = this.garminPlugin.unlock(pathKeyPairsArray);
1084 		return this.pluginUnlocked;
1085 	},
1086 
1087 	/** Register to be an event listener.  An object that is registered will be dispatched
1088      * a method if they have a function with the same dispatch name.  So if you register a
1089      * listener with an onFinishFindDevices, and the onFinishFindDevices message is called, you'll
1090      * get that message.  See class comments for event types
1091      *
1092      * @param {Object} listener Object that will listen for events coming from this object 
1093      * @see {Garmin.Broadcaster}
1094      */	
1095 	register: function(listener) {
1096         this._broadcaster.register(listener);
1097 	},
1098 
1099 	/** True if plugin has been successfully created and unlocked.
1100 	 * @type Boolean
1101 	 */
1102 	 isUnlocked: function() {
1103 	 	return this.pluginUnlocked;
1104 	 },
1105 	 
1106     /** Responds to a message box on the device.
1107      * 
1108      * Minimum version 2.0.0.4
1109      * 
1110      * @param {Number} response should be an int which corresponds to a button value from this.garminPlugin.MessageBoxXml
1111      */
1112     // TODO: this method only works with writes - should it work with reads?
1113     respondToMessageBox: function(response) {
1114         this.garminPlugin.respondToMessageBox(response ? 1 : 2);
1115         this._progressWrite();
1116     },
1117 
1118 	/** Called when device generates a message.
1119 	 * This occurs when completionState == Garmin.DeviceControl.FINISH_STATES.messageWaiting.
1120 	 * @private
1121      */	
1122 	_messageWaiting: function() {
1123 		var messageDoc = Garmin.XmlConverter.toDocument(this.garminPlugin.getMessageBoxXml());
1124 		//var type = messageDoc.getElementsByTagName("Icon")[0].childNodes[0].nodeValue;
1125 		var text = messageDoc.getElementsByTagName("Text")[0].childNodes[0].nodeValue;
1126 		
1127 		var message = new Garmin.MessageBox("Question",text);
1128 		
1129 		var buttonNodes = messageDoc.getElementsByTagName("Button");
1130 		for(var i=0; i<buttonNodes.length; i++) {
1131 			var caption = buttonNodes[i].getAttribute("Caption");
1132 			var value = buttonNodes[i].getAttribute("Value");
1133 			message.addButton(caption, value);
1134 		}
1135 		return message;
1136 	},
1137 
1138 	/** Get the status/progress of the current state or transfer
1139      * @type Garmin.TransferProgress
1140      */	
1141 	getDeviceStatus: function() {
1142 		var aProgressXml = this.garminPlugin.getProgressXml();
1143 		var theProgressDoc = Garmin.XmlConverter.toDocument(aProgressXml);
1144 		
1145 		var title = "";
1146 		if(theProgressDoc.getElementsByTagName("Title").length > 0) {
1147 			title = theProgressDoc.getElementsByTagName("Title")[0].childNodes[0].nodeValue;
1148 		}
1149 		
1150 		var progress = new Garmin.TransferProgress(title);
1151 
1152 		var textNodes = theProgressDoc.getElementsByTagName("Text");
1153 		for( var i=0; i < textNodes.length; i++ ) {
1154 			if(textNodes[i].childNodes.length > 0) {
1155 				var text = textNodes[i].childNodes[0].nodeValue;
1156 				if(text != "") progress.addText(text);
1157 			}
1158 		}
1159 		
1160 		var percentageNode = theProgressDoc.getElementsByTagName("ProgressBar")[0];
1161 		if(percentageNode != undefined) {
1162 			if(percentageNode.getAttribute("Type") == "Percentage") {
1163 				progress.setPercentage(percentageNode.getAttribute("Value"));
1164 			} else if (percentageNode.getAttribute("Type") == "Indefinite") {
1165 				progress.setPercentage(100);			
1166 			}
1167 		}
1168 
1169 		return progress;
1170 	},
1171 		
1172 	/**
1173 	 * @private
1174 	 */
1175 	_isAMember: function(element, array) {
1176 		return array.any( function(str){ return str==element; } );
1177 	},
1178 	
1179 	/** Gets the version number for the plugin the user has currently installed.
1180      * @type Array 
1181      * @return An array of the format [versionMajor, versionMinor, buildMajor, buildMinor].
1182      * @see #getPluginVersionString
1183      */	
1184 	getPluginVersion: function() {
1185 		
1186     	return this.garminPlugin.getPluginVersion();
1187 	},
1188 
1189 	/** Gets a string of the version number for the plugin the user has currently installed.
1190      * @type String 
1191      * @return A string of the format "versionMajor.versionMinor.buildMajor.buildMinor", i.e. "2.0.0.4"
1192      * @see #getPluginVersion
1193      */	
1194 	getPluginVersionString: function() {
1195 		return this.garminPlugin.getPluginVersionString();
1196 	},
1197 	
1198 	/** Sets the required version number for the plugin for the application.
1199 	 * @param reqVersionArray {Array} The required version to set to.  In the format [versionMajor, versionMinor, buildMajor, buildMinor]
1200 	 * 			i.e. [2,2,0,1]
1201 	 */
1202 	setPluginRequiredVersion: function(reqVersionArray) {
1203 		if( reqVersionArray != null ) {
1204 			this.garminPlugin.setPluginRequiredVersion(reqVersionArray);
1205 		}
1206 	},
1207 	
1208 	/** Sets the latest plugin version number.  This represents the latest version available for download at Garmin.
1209 	 * We will attempt to keep the default value of this up to date with each API release, but this is not guaranteed,
1210 	 * so set this to be safe or if you don't want to upgrade to the latest API.
1211 	 * 
1212 	 * @param reqVersionArray {Array} The latest version to set to.  In the format [versionMajor, versionMinor, buildMajor, buildMinor]
1213 	 * 			i.e. [2,2,0,1]
1214 	 */
1215 	setPluginLatestVersion: function(reqVersionArray) {
1216 		if( reqVersionArray != null ) {
1217 			this.garminPlugin.setPluginLatestVersion(reqVersionArray);
1218 		}
1219 	},
1220 	
1221 	/** Determines if the plugin is initialized
1222      * @type Boolean
1223      */	
1224 	isPluginInitialized: function() {
1225 		return (this.garminPlugin != null);
1226 	},
1227 
1228 	/** Determines if the plugin is installed on the user's machine
1229      * @type Boolean
1230      */	
1231 	isPluginInstalled: function() {
1232 		return (this.garminPlugin.getVersionXml() != undefined);
1233 	},
1234 
1235 	/** Internal exception handling for asynchronous calls.
1236 	 * @private
1237       */	
1238 	_reportException: function(exception) {
1239 		this._broadcaster.dispatch("onException", {msg: exception, controller: this});
1240 	},
1241 	
1242 	/** Number of devices detected by plugin.
1243 	 * @type Number
1244 }    */	
1245 	getDevicesCount: function() {
1246 	    return this.numDevices;
1247 	},
1248 	
1249 	/** Checks if the device lists the given datatype as a supported readable type.
1250 	 * Plugin version affects the results of this function.  The latest plugin version is encouraged.
1251 	 * 
1252 	 * Internal file type support (such as directory types) is detected based on base
1253 	 * type. i.e. tcxDir -> tcx, fitDir -> fit
1254 	 */
1255 	checkDeviceReadSupport: function( datatype ) {
1256 		
1257         // Do the plugin version check early for fit directory reading
1258 		if( datatype == Garmin.DeviceControl.FILE_TYPES.fitDir) {
1259 		    if( this.garminPlugin.getSupportsFitDirectoryRead() == false) {
1260     	        // Yeah, breaking the 1 return rule... This is still cleaner than all the other options
1261     	        // and at least eliminates confusion between other types.
1262 		        return false;
1263 		    }
1264         } 
1265         
1266 		var isDatatypeSupported;
1267 
1268 		// The selected device
1269 		var device = this._getDeviceByNumber(this.deviceNumber);
1270 		var baseDatatype;
1271 
1272 		// Internal types use base type for the support check.
1273 		switch(datatype) {
1274 			case Garmin.DeviceControl.FILE_TYPES.gpxDir:
1275 			case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
1276 			     baseDatatype = Garmin.DeviceControl.FILE_TYPES.gpx;
1277 			     break;			
1278             case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1279             case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
1280     		    baseDatatype = Garmin.DeviceControl.FILE_TYPES.tcx; 
1281     		    break;
1282             case Garmin.DeviceControl.FILE_TYPES.crsDir:
1283             case Garmin.DeviceControl.FILE_TYPES.crsDetail:
1284                 baseDatatype = Garmin.DeviceControl.FILE_TYPES.crs;
1285                 break;
1286             case Garmin.DeviceControl.FILE_TYPES.fitDir:
1287             case Garmin.DeviceControl.FILE_TYPES.fitFile:
1288                 baseDatatype = Garmin.DeviceControl.FILE_TYPES.fit;
1289                 break;
1290             default:     
1291                 baseDatatype = datatype;
1292 		}
1293 		
1294 		// Every device has a device xml and firmware
1295 		if(baseDatatype == Garmin.DeviceControl.FILE_TYPES.deviceXml 
1296 		|| baseDatatype == Garmin.DeviceControl.FILE_TYPES.firmware) {
1297 		    isDatatypeSupported = true;
1298 		} else {
1299             isDatatypeSupported = device.supportDeviceDataTypeRead(baseDatatype);
1300 		}
1301 
1302 		return isDatatypeSupported;
1303 	},
1304 	
1305 	/** Checks if the device lists the given datatype as a supported writeable type.
1306 	 * Plugin version affects the results of this function.  The latest plugin version is encouraged.
1307 	 * 
1308 	 * Internal file types (such as directory types) are NOT detected as supported write types.
1309 	 */
1310 	checkDeviceWriteSupport: function(datatype) {
1311 	    var isDatatypeSupported = false;
1312 
1313 		// The selected device
1314 		var device = this._getDeviceByNumber(this.deviceNumber);
1315 		
1316 		// Don't include types that aren't in the Device XML
1317 		if ( datatype == Garmin.DeviceControl.FILE_TYPES.binary 
1318 		  || datatype == Garmin.DeviceControl.FILE_TYPES.gpi) {
1319 		    isDatatypeSupported = true;
1320 		} else {
1321             isDatatypeSupported = device.supportDeviceDataTypeWrite(datatype);
1322 		}
1323 		
1324 		return isDatatypeSupported;
1325 	},
1326 	
1327 	/** Retrieve a device from the list of found devices by device number. 
1328 	 * @return Garmin.Device
1329 	 */
1330 	_getDeviceByNumber: function(deviceNum) {
1331 		for( var index = 0; index < this.devices.length; index++) {
1332 			if( this.devices[index].getNumber() == deviceNum){
1333 				return this.devices[index];
1334 			}
1335 		}		
1336 	},
1337 	
1338 	/** String representation of instance.
1339 	 * @type String
1340      */	
1341 	toString: function() {
1342 	    return "Garmin Javascript GPS Controller managing " + this.numDevices + " device(s)";
1343 	}
1344 };
1345 
1346 /** Dedicated browser support singleton.
1347  */
1348 var BrowserSupport = {
1349     /** Determines if the users browser is currently supported by the plugin
1350      * @type Boolean
1351      */	 
1352 	isBrowserSupported: function() {
1353 		//console.debug("Display.isBrowserSupported BrowserDetect.OS="+BrowserDetect.OS+", BrowserDetect.browser="+BrowserDetect.browser)
1354 		// TODO Extract strings to constants
1355 		// TODO Move this out to plugin layer? 
1356 		return ( (BrowserDetect.OS == "Windows" && 
1357 					(BrowserDetect.browser == "Firefox" 
1358 					|| BrowserDetect.browser == "Mozilla" 
1359 					|| BrowserDetect.browser == "Explorer"
1360 					|| BrowserDetect.browser == "Safari"))
1361 				|| (BrowserDetect.OS == "Mac" && 
1362 					(BrowserDetect.browser == "Firefox" 
1363 					|| BrowserDetect.browser == "Safari")) );
1364 	}
1365 };
1366 
1367 /** Constants defining possible errors messages for various errors on the page
1368  */
1369 Garmin.DeviceControl.MESSAGES = {
1370 	deviceControlMissing: "Garmin.DeviceControl depends on the Garmin.DevicePlugin framework.",
1371 	missingPluginTag: "Plug-In HTML tag not found.",
1372 	browserNotSupported: "Your browser is not supported to use the Garmin Communicator Plug-In.",
1373 	pluginNotInstalled: "Garmin Communicator Plugin NOT detected.",
1374 	outOfDatePlugin1: "Your version of the Garmin Communicator Plug-In is out of date.<br/>Required: ",
1375 	outOfDatePlugin2: "Current: ",
1376 	updatePlugin1: "Your version of the Garmin Communicator Plug-In is not the latest version. Latest version: ",
1377 	updatePlugin2: ", current: ",
1378 	pluginNotUnlocked: "Garmin Plugin has not been unlocked",
1379 	noDevicesConnected: "No device connected, can't communicate with device.",
1380 	invalidFileType: "Cannot process the device file type: ",
1381 	incompleteRead: "Incomplete read, cannot get compressed format.",
1382 	unsupportedReadDataType: "Your device does not support reading of the type: ",
1383 	unsupportedWriteDataType: "Your device does not support writing of the type: "
1384 };
1385 
1386 /** Constants defining possible states when you poll the finishActions
1387  */
1388 Garmin.DeviceControl.FINISH_STATES = {
1389 	idle: 0,
1390 	working: 1,
1391 	messageWaiting: 2,
1392 	finished: 3	
1393 };
1394 
1395 /** Constants defining possible file types associated with read and write methods.  File types can
1396  * be accessed in a static way, like so:<br/>
1397  * <br/>
1398  * Garmin.DeviceControl.FILE_TYPES.gpx<br/>
1399  * <br/>
1400  * NOTE: 'gpi' is being deprecated--please use 'binary' instead for gpi and other binary data. 
1401  */
1402 Garmin.DeviceControl.FILE_TYPES = {
1403 	gpx:               "GPSData",
1404 	tcx:               "FitnessHistory",
1405 	gpi:               "gpi", //deprecated, use binary instead
1406 	crs:               "FitnessCourses",
1407 	wkt:               "FitnessWorkouts",
1408 	goals:             "FitnessActivityGoals",
1409 	tcxProfile:        "FitnessUserProfile",
1410 	binary:            "BinaryData", // Not in Device XML, so writing this type is "supported" for all devices. For FIT data, use fitFile.
1411 	voices:            "Voices",
1412 	nlf:               "FitnessNewLeaf",
1413 	fit:               "FITBinary",
1414 	fitCourse:         "FIT_TYPE_6",
1415 	fitSettings:       "FIT_TYPE_2",
1416 	fitSport:          "FIT_TYPE_3",
1417 	
1418 	// The following types are internal types used by the API only and cannot be found in the Device XML.
1419 	// NOTE: When adding or removing types to this internal list, modify checkDeviceReadSupport() accordingly.
1420 	tcxDir: 		   "FitnessHistoryDirectory",
1421 	crsDir: 		   "FitnessCoursesDirectory",
1422 	gpxDir: 		   "GPSDataDirectory",
1423 	tcxDetail:         "FitnessHistoryDetail",
1424 	crsDetail: 		   "FitnessCoursesDetail",
1425 	gpxDetail:         "GPSDataDetail",
1426 	deviceXml: 	       "DeviceXml",
1427 	fitDir:     	   "FitDirectory",
1428 	fitFile:    	   "FitFile",
1429 	firmware:          "Firmware"
1430 };
1431 
1432 /** Constants defining the strings used by the Device.xml to indicate 
1433  * transfer direction of each file type
1434  */
1435 Garmin.DeviceControl.TRANSFER_DIRECTIONS = {
1436 	read:              "OutputFromUnit",
1437 	write:             "InputToUnit",
1438 	both:              "InputOutput"
1439 };
1440 
1441 /** Encapsulates the data provided by the device for the current process' progress.
1442  * Use this to relay progress information to the user.
1443  * @class Garmin.TransferProgress
1444  * @constructor 
1445  */
1446 Garmin.TransferProgress = Class.create();
1447 Garmin.TransferProgress.prototype = {
1448 	initialize: function(title) {
1449 		this.title = title;
1450 		this.text = new Array();
1451 		this.percentage = null;
1452 	},
1453 	
1454 	addText: function(textString) {
1455 		this.text.push(textString);
1456 	},
1457 
1458     /** Get all the text entries for the transfer
1459      * @type Array
1460      */	 
1461 	getText: function() {
1462 		return this.text;
1463 	},
1464 
1465     /** Get the title for the transfer
1466      * @type String
1467      */	 
1468 	getTitle: function() {
1469 		return this.title;
1470 	},
1471 	
1472 	setPercentage: function(percentage) {
1473 		this.percentage = percentage;
1474 	},
1475 
1476     /** Get the completed percentage value for the transfer
1477      * @type Number
1478      */
1479 	getPercentage: function() {
1480 		return this.percentage;
1481 	},
1482 
1483     /** String representation of instance.
1484      * @type String
1485      */	 	
1486 	toString: function() {
1487 		var progressString = "";
1488 		if(this.getTitle() != null) {
1489 			progressString += this.getTitle();
1490 		}
1491 		if(this.getPercentage() != null) {
1492 			progressString += ": " + this.getPercentage() + "%";
1493 		}
1494 		return progressString;
1495 	}
1496 };
1497 
1498 
1499 /** Encapsulates the data to display a message box to the user when the plug-in is waiting for feedback
1500  * @class Garmin.MessageBox
1501  * @constructor 
1502  */
1503 Garmin.MessageBox = Class.create();
1504 Garmin.MessageBox.prototype = {
1505 	initialize: function(type, text) {
1506 		this.type = type;
1507 		this.text = text;
1508 		this.buttons = new Array();
1509 	},
1510 
1511     /** Get the type of the message box
1512      * @type String
1513      */	 
1514 	getType: function() {
1515 		return this.type;
1516 	},
1517 
1518     /** Get the text entry for the message box
1519      * @type String
1520      */	 
1521 	getText: function() {
1522 		return this.text;
1523 	},
1524 
1525     /** Get the text entry for the message box
1526      */	 
1527 	addButton: function(caption, value) {
1528 		this.buttons.push({caption: caption, value: value});
1529 	},
1530 
1531     /** Get the buttons for the message box
1532      * @type Array
1533      */	 
1534 	getButtons: function() {
1535 		return this.buttons;
1536 	},
1537 	
1538 	getButtonValue: function(caption) {
1539 		for(var i=0; i< this.buttons.length; i++) {
1540 			if(this.buttons[i].caption == caption) {
1541 				return this.buttons[i].value;
1542 			}
1543 		}
1544 		return null;
1545 	},
1546 
1547     /**
1548 	 * @type String
1549      */	 
1550 	toString: function() {
1551 		return this.getText();
1552 	}
1553 };
1554 
1555 /*
1556  * Dynamic include of required libraries and check for Prototype
1557  * Code taken from scriptaculous (http://script.aculo.us/) - thanks guys!
1558 var GarminDeviceControl = {
1559 	require: function(libraryName) {
1560 		// inserting via DOM fails in Safari 2.0, so brute force approach
1561 		document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
1562 	},
1563 
1564 	load: function() {
1565 		if((typeof Prototype=='undefined') || 
1566 			(typeof Element == 'undefined') || 
1567 			(typeof Element.Methods=='undefined') ||
1568 			parseFloat(Prototype.Version.split(".")[0] + "." +
1569 			Prototype.Version.split(".")[1]) < 1.5) {
1570 			throw("GarminDeviceControl requires the Prototype JavaScript framework >= 1.5.0");
1571 		}
1572 
1573 		$A(document.getElementsByTagName("script"))
1574 		.findAll(
1575 			function(s) {
1576 				return (s.src && s.src.match(/GarminDeviceControl\.js(\?.*)?$/))
1577 			}
1578 		)
1579 		.each(
1580 			function(s) {
1581 				var path = s.src.replace(/GarminDeviceControl\.js(\?.*)?$/,'../../');
1582 				var includes = s.src.match(/\?.*load=([a-z,]*)/);
1583 				var dependencies = 'garmin/device/GarminDevicePlugin' +
1584 									',garmin/device/GarminDevice' +
1585 									',garmin/util/Util-XmlConverter' +
1586 									',garmin/util/Util-Broadcaster' +
1587 									',garmin/util/Util-DateTimeFormat' +
1588 									',garmin/util/Util-BrowserDetect' +
1589 									',garmin/util/Util-PluginDetect' +
1590 									',garmin/device/GarminObjectGenerator';
1591 			    (includes ? includes[1] : dependencies).split(',').each(
1592 					function(include) {
1593 						GarminDeviceControl.require(path+include+'.js') 
1594 					}
1595 				);
1596 			}
1597 		);
1598 	}
1599 }
1600 
1601 GarminDeviceControl.load();
1602  */
1603 
1604