In appendixes 2 and 3 you were shown how to add a button to your driver's device manager or point manager. You did this by creating a special agent class that featured a doInvoke callback method. This callback is called within the Java Virtual Machine (JVM) that is running on the client-side. This is probably the same JVM that is running the Workbench application. If you need to perform some operations within the Java Virtual Machine that is running on the station-side (probably in a Jace) then here are two options:
From the doInvoke callback on your device or point manager button agent...
The second option is to create a special job class. Then make the doInvoke callback submit an instance of the job to run in the server-side virtual machine that runs the station. After that, make the doInvoke method associate the instance of the job with the job bar that is located towards the top of the device manager and point manager.
The first option may seem simpler to implement than the second option. However, the first option is limited in its ability to keep the end-user informed. Since the doSomething method is running in the server-side virtual machine, it does not have access to the end-user's copy of Workbench. This is why it is difficult to communicate back to the end-user if you choose to implement the first option. Following the second option will help you to see the bigger picture and implement a more comprehensive solution for the end-user.
The second option is more complicated in that it requires a job class to be created. However, the job can report progress, such as percent complete, to the end user. The job can also send text messages to the job bar that the end-user is looking at. The user can also click the Cancel button on the device or point manager to abort the job.
The first option is pretty straight-forward to implement. Please refer to Appendix 1 for details on creating the action and to Appendixes 2 and 3 for details on creating the device or point manager button agent. The general idea is that actions allow client-server communications. A client VM calls an action method, optionally passes in a parameter, the action method performs network communication to the server side VM, and waits while the corresponding do action method is executed on the server-side VM. The server-side VM returns the optional return value back to the client-side. Finally, the client-side logic that called the action method is awakened with the return value.
The rest of this appendix further describes the second option in detail. Here are the steps required for the second option that is suggested. Sample Java source code is included below.
|
Note: The return value from the doSubmitSomething method that executes on the station-side will be automatically and synchronously dispatched to the Java code that calls the submitSomething method on the client-side. Place the action on the network, device, or proxy ext. If this is to occur on the Device Manager then the action will need to be placed on either the network or the device. If this is to occur in the Point Manager then this action will need to placed on the device or the proxy extension.
Admittedly, there is a moderate amount of development required for lesson. However, by following these steps, the end-user will have a much better experience and user-control when clicking the button that you add to the device or point manager.
Following is some sample source code that you may use to help you with this lesson.
package com.testCompany.testDriver; import javax.baja.io.ByteBuffer; import javax.baja.job.BJobState; import javax.baja.job.BSimpleJob; import javax.baja.job.JobCancelException; import javax.baja.sys.BBlob; import javax.baja.sys.BajaRuntimeException; import javax.baja.sys.Context; import javax.baja.sys.Sys; import javax.baja.sys.Type; import javax.baja.util.Lexicon; import com.testCompany.testDriver.comm.req.BTestDriverBackupJobPageRequest; import com.testCompany.testDriver.comm.req.BTestDriverBackupJobStartRequest; import com.testCompany.testDriver.comm.rsp.BTestDriverBackupJobPageResponse; import com.testCompany.testDriver.comm.rsp.BTestDriverBackupJobStartResponse; import com.testCompany.testDriver.identify.BTestDriverDeviceId; import com.tridium.ddf.comm.req.util.DdfRequestUtil; import com.tridium.sys.station.Station; /** * This is an example of a free-form job that is performed in the * background. * * This job is kicked off from a button on the device manager. * * This job shows a hypothetical situation where a field-device's * internal firmware file is being backed up to the station database. * * @author lperkins * */ public class BTestDriverBackupJob extends BSimpleJob { /*- class BTestDriverBackupJob { } -*/ /*+ ------------ BEGIN BAJA AUTO GENERATED CODE ------------ +*/ /*@ $com.testCompany.testDriver.BTestDriverBackupJob(3756993399)1.0$ @*/ /* Generated Thu Jan 17 09:55:44 EST 2008 by Slot-o-Matic 2000 (c) Tridium, Inc. 2000 */ //////////////////////////////////////////////////////////////// // Type //////////////////////////////////////////////////////////////// public Type getType() { return TYPE; } public static final Type TYPE = Sys.loadType(BTestDriverBackupJob.class); /*+ ------------ END BAJA AUTO GENERATED CODE -------------- +*/ //////////////////////////////////////////////////////////////// // Construction //////////////////////////////////////////////////////////////// /** * Empty constructor - required for client-side proxy of job. */ public BTestDriverBackupJob() { this.network=null; this.device=null; this.deviceId=null; } /** * Fully specified constructor - called by submitTestBackupJob action * on the BTestDriverDevice. */ public BTestDriverBackupJob(BTestDriverNetwork net, BTestDriverDevice d) { this.network=net; this.device=d; this.deviceId = (BTestDriverDeviceId)d.getDeviceId(); } //////////////////////////////////////////////////////////////// // BSimpleJob //////////////////////////////////////////////////////////////// /** * This method is called on the station. The station-side Job * service makes this happen. */ public void run(Context c) throws Exception { // Starts the firmware backup and reports initial progress startBackup(); // If the 'startBackup' method was unable to start the firmware backup process if (startRsp==null) { // If the 'startBackup' method was unable to start the firmware backup process // then this throws an exception. The entity that calls this 'run' method // will send the exception's internal string representation (LEX_STARTING_FAILED) // to the job log and set the job state to finished and error throw new BajaRuntimeException(LEX_STARTING_FAILED); } else { // Else, the 'startBackup' was successful. Let's proceed to read the firmware // file bytes. backupFirmware(); } } //////////////////////////////////////////////////////////////// // Limited Access //////////////////////////////////////////////////////////////// /** * Sends out a request to initiate a read of the device firmware. */ void startBackup() { // If the end-user cancels the job, then this stops the job by unwinding the call-stack if (getJobState() == BJobState.canceling) { throw new JobCancelException(); // Terminates this job by unwinding the stack } // Else, the end-user has not clicked the 'Cancel' button on the device manager. This is // a good thing! Therefore, this proceeds with the job. // Keeps user interface informed log().message( lexStarting( deviceId.getUnitNumber() ) ); // Instantiates the first request BTestDriverBackupJobStartRequest startReq = new BTestDriverBackupJobStartRequest(); // Assigns the device id to the request startReq.setDeviceId( (BTestDriverDeviceId)deviceId.newCopy() ); // Transmits the request and waits for the response or timeout DdfRequestUtil.communicateSync(network.getDdfCommunicator(), startReq); // Gets the response to the startReq or null upon timeout or exception startRsp = startReq.getRsp(); } /** * Backs up the firmware after the 'doRun' method starts the backup. */ void backupFirmware() throws Exception { int numPages = startRsp.getNumPages(); int pageSize = startRsp.getPageSize(); // Status reporting - First update of the status bar // Uses a double to prevent Java from dropping the fraction // during mixed integer & floating point mathematical operations. // One step for each of the numPages, plus three more steps -- one for // the start req that we just sent, one for saving the firmware file // to the device in the database, and one for saving the station // database to persistent storage. double totalStepsRequired = 3.0 + numPages; // Start by reporting that the first step has completed (starting backup complete) progress( (int)Math.round((1.0 / totalStepsRequired) * 100.0) ); boolean pageSuccess = true; for (int i=0; i<numPages && pageSuccess; i++) { // If the end-user cancels the job, then this stops the job by unwinding the call-stack if (getJobState() == BJobState.canceling) { throw new JobCancelException(); } // Else, the end-user has not clicked the 'Cancel' button on the device manager so // this continues on. else { // Informs user interface using local language text log().message( lexReadPage(i+1,numPages) ); // Reads the next firmware page and updates progress. The moment // that any page fails, pageSuccess is set to false, which causes // the for loop to terminate. pageSuccess = readNextPage(i, pageSize, totalStepsRequired); } } // If the for loop did not terminate due to a failure while reading // pages of the firmware. if (pageSuccess) { saveFirmwareBackup(totalStepsRequired); } else // The 'for' loop terminated after a failed attempt to read a firmware page { // This throws a flavor of runtime exception that will unroll the stack all // the way back to the method that calls this job's 'run' method. That method // catches the exception, updates the job's log with the exception's text, and // flags the job as "finished with error". throw new BajaRuntimeException(LEX_PAGE_FAILED); } } /** * Saves the firmware backup bytes to the database and writes the database to persistent storage. * * @param totalStepsRequired from the 'backupFirmware' method */ void saveFirmwareBackup(double totalStepsRequired) throws Exception { // If the end-user cancels the job, then this stops the job by unwinding the call-stack if (getJobState() == BJobState.canceling) { throw new JobCancelException(); // Terminates this job by unwinding the stack } // Else, the end-user has not clicked the 'Cancel' button on the device manager. This is // a good thing! Therefore, this proceeds with the job. // Sends some text to the user interface log().message(LEX_WRITING_FIRMWARE_TO_DB); // Stores the firmware bytes in the database with the entry for the // device. Note that we could have stored this in a file too but then // we'd have to decide how to name the files to accomodate the possibility // of multiple devices and even then, what if a device's unitNumber is // changed??? It would make it difficult to track down the firmware // backup. device.setBackupFirmware( BBlob.make(firmwareBuffer.toByteArray())); // Reports that the second to last step just occurred progress( (int)Math.round((((totalStepsRequired-1.0) / totalStepsRequired) * 100.0))); // If the end-user cancels the job, then this stops the job by unwinding the call-stack if (getJobState() == BJobState.canceling) { throw new JobCancelException(); // Terminates this job by unwinding the stack } // Else, the end-user has not clicked the 'Cancel' button on the device manager so // this proceed to perform the final step. // Sends some text to the user interface log().message(LEX_SAVING_STATION_DB); // Saves the station database. Otherwise, if the Jace were to loose power // before the default, three-hour auto-save, then the device's firmware backup // would be lost. Station.saveSync(); // Reports 100% completion progress(100); } /** * Reads the next page of firmware. This method is called inside a for loop * in the 'backupFirmware' method. * * @param i from the 'backupFirmware' method. * @param pageSize from the 'backupFirmware' method. * @param totalStepsRequired from the 'backupFirmware' method. * * @return true if the next page was read successfully, false if not */ boolean readNextPage(int i, int pageSize, double totalStepsRequired) { // Instantiates a page request to read the next page BTestDriverBackupJobPageRequest pageReq = new BTestDriverBackupJobPageRequest(); // Passes all relevant data to serialize the page request and // parse the page response pageReq.setDeviceId( (BTestDriverDeviceId)deviceId.newCopy() ); // The protocol is one-based when referring to pages but our 'for' // loop is zero based. pageReq.setPageNumber(i+1); pageReq.setPageSize( pageSize ); // Transmits the page request and waits for the response, a timeout, or // an exception DdfRequestUtil.communicateSync(network.getDdfCommunicator(), pageReq); // Gets the response (or null upon timeout or exception) BTestDriverBackupJobPageResponse pageRsp = pageReq.getRsp(); if (pageRsp==null) { // Returns false to indicate failure return false; } else { // Adds the page's bytes to the firmware file buffer. firmwareBuffer.write( pageRsp.getPageData() ); // Reports progress from step 2 through 2 + i (which will be all but // the last two steps) progress( (int)Math.round(((2.0 + (double)i) / totalStepsRequired) * 100.0) ); // Returns true to indicate success return true; } } //////////////////////////////////////////////////////////////// // Attributes //////////////////////////////////////////////////////////////// // The network as passed to the constructor final BTestDriverNetwork network; // The device as passed to the constructor final BTestDriverDevice device; // The deviceId as passed to the constructor final BTestDriverDeviceId deviceId; // This reference is assigned from the 'startBackup' method BTestDriverBackupJobStartResponse startRsp; // Let's plan to store the device's firmware in a javax.baja.io.ByteBuffer. This // variable is used most in the 'backupFirmware' and 'readNextPage' methods ByteBuffer firmwareBuffer = new ByteBuffer(); //////////////////////////////////////////////////////////////// // Localization //////////////////////////////////////////////////////////////// // Gains access to the Niagara AX lexicon for the jar file that declares // This class static final Lexicon LEX = Lexicon.make(BTestDriverBackupJob.class); // Message to user interface if the starting sequence fails (see 'startBackup' method) static final String LEX_STARTING_FAILED = LEX.getText("BTestBackupJob.Starting.Failed"); // Looks up the text "Starting to backup device x" from the localized lexicon, replacing x // with the appropriate unitNumber (see 'startBackup' method) static final String lexStarting(int unitNumber) { return LEX.getText( "BTestBackupJob.Starting", new Object[]{ Integer.toString(unitNumber) }); } // Looks up the text "Reading page x of n." from the localized lexicon, replacing i and n // with appropriate values. This is called from from the 'backupFirmware' method // each pass through a 'for' loop static final String lexReadPage(int pageI, int ofN) { return LEX.getText( "BTestBackupJob.ReadPage", new Object[]{ Integer.toString(pageI), Integer.toString(ofN)}); } // Message to user interface if the firmware backup fails during a read page request static final String LEX_PAGE_FAILED = LEX.getText("BTestBackupJob.Page.Failed"); // Message to user interface when starting to write the firmware data to the database. // This happens after retrieving all firmware pages from the device. static final String LEX_WRITING_FIRMWARE_TO_DB = LEX.getText("BTestBackupJob.WritingBackupToDatabase"); // Message to user interface when starting the final step -- saving the station database // (with the newly written firmware data) to persistent storage. static final String LEX_SAVING_STATION_DB = LEX.getText("BTestBackupJob.SavingStationDatabase"); }
package com.testCompany.testDriver; import javax.baja.naming.BOrd; import javax.baja.sys.Action; import javax.baja.sys.BBlob; import javax.baja.sys.Flags; import javax.baja.sys.Property; import javax.baja.sys.Sys; import javax.baja.sys.Type; import com.testCompany.testDriver.identify.BTestDriverDeviceId; import com.tridium.ddf.identify.BDdfIdParams; import com.tridium.ddfSerial.BDdfSerialDevice; public class BTestDriverDevice extends BDdfSerialDevice { /*- class BTestDriverDevice { properties { deviceId : BDdfIdParams -- This plugs in an instance of testDriver's -- device id as this device's deviceId default {[new BTestDriverDeviceId()]} slotfacets{[MGR_INCLUDE]} points : BTestDriverPointDeviceExt -- Adds the special point device extension -- component to the property sheet and the -- Niagara AX navigation tree. This special -- component will contain and process all -- control points for YourDriver default{[new BTestDriverPointDeviceExt()]} backupFirmware : BBlob -- Stores a backup copy of the firmware bytes -- in the device. See the BTestBackupJob for -- details as to how and when this happens. flags{hidden} default{[BBlob.DEFAULT]} } actions { submitBackupFirmwareJob():BOrd -- Runs a BTestDeviceBackupJob in the station VM. -- Returns a BOrd to the BTestDeviceBackupJob that -- is started in the station VM. } } -*/ /*+ ------------ BEGIN BAJA AUTO GENERATED CODE ------------ +*/ /*@ $com.testCompany.testDriver.BTestDriverDevice(2646598044)1.0$ @*/ /* Generated Wed Jan 16 17:26:50 EST 2008 by Slot-o-Matic 2000 (c) Tridium, Inc. 2000 */ //////////////////////////////////////////////////////////////// // Property "deviceId" //////////////////////////////////////////////////////////////// /** * Slot for thedeviceIdproperty. * This plugs in an instance of testDriver's device id * as this device's deviceId * @see com.testCompany.testDriver.BTestDriverDevice#getDeviceId * @see com.testCompany.testDriver.BTestDriverDevice#setDeviceId */ public static final Property deviceId = newProperty(0, new BTestDriverDeviceId(),MGR_INCLUDE); /** * Get thedeviceIdproperty. * @see com.testCompany.testDriver.BTestDriverDevice#deviceId */ public BDdfIdParams getDeviceId() { return (BDdfIdParams)get(deviceId); } /** * Set thedeviceIdproperty. * @see com.testCompany.testDriver.BTestDriverDevice#deviceId */ public void setDeviceId(BDdfIdParams v) { set(deviceId,v,null); } //////////////////////////////////////////////////////////////// // Property "points" //////////////////////////////////////////////////////////////// /** * Slot for thepointsproperty. * Adds the special point device extension component to the property sheet and the Niagara AX navigation tree. This special component will contain and process all control points for YourDriver * @see com.testCompany.testDriver.BTestDriverDevice#getPoints * @see com.testCompany.testDriver.BTestDriverDevice#setPoints */ public static final Property points = newProperty(0, new BTestDriverPointDeviceExt(),null); /** * Get thepointsproperty. * @see com.testCompany.testDriver.BTestDriverDevice#points */ public BTestDriverPointDeviceExt getPoints() { return (BTestDriverPointDeviceExt)get(points); } /** * Set thepointsproperty. * @see com.testCompany.testDriver.BTestDriverDevice#points */ public void setPoints(BTestDriverPointDeviceExt v) { set(points,v,null); } //////////////////////////////////////////////////////////////// // Property "backupFirmware" //////////////////////////////////////////////////////////////// /** * Slot for thebackupFirmwareproperty. * Stores a backup copy of the firmware bytes in the device. See the BTestBackupJob for details as to how and when this happens. * @see com.testCompany.testDriver.BTestDriverDevice#getBackupFirmware * @see com.testCompany.testDriver.BTestDriverDevice#setBackupFirmware */ public static final Property backupFirmware = newProperty(Flags.HIDDEN, BBlob.DEFAULT,null); /** * Get thebackupFirmwareproperty. * @see com.testCompany.testDriver.BTestDriverDevice#backupFirmware */ public BBlob getBackupFirmware() { return (BBlob)get(backupFirmware); } /** * Set thebackupFirmwareproperty. * @see com.testCompany.testDriver.BTestDriverDevice#backupFirmware */ public void setBackupFirmware(BBlob v) { set(backupFirmware,v,null); } //////////////////////////////////////////////////////////////// // Action "submitBackupFirmwareJob" //////////////////////////////////////////////////////////////// /** * Slot for thesubmitBackupFirmwareJobaction. * Runs a BTestDeviceBackupJob in the station VM. Returns * a BOrd to the BTestDeviceBackupJob that is started * in the station VM. * @see com.testCompany.testDriver.BTestDriverDevice#submitBackupFirmwareJob() */ public static final Action submitBackupFirmwareJob = newAction(0,null); /** * Invoke thesubmitBackupFirmwareJobaction. * @see com.testCompany.testDriver.BTestDriverDevice#submitBackupFirmwareJob */ public BOrd submitBackupFirmwareJob() { return (BOrd)invoke(submitBackupFirmwareJob,null,null); } //////////////////////////////////////////////////////////////// // Type //////////////////////////////////////////////////////////////// public Type getType() { return TYPE; } public static final Type TYPE = Sys.loadType(BTestDriverDevice.class); /*+ ------------ END BAJA AUTO GENERATED CODE -------------- +*/ public Type getNetworkType() { return BTestDriverNetwork.TYPE; } //////////////////////////////////////////////////////////////// // Station-side implementation of Action "submitBackupFirmwareJob" //////////////////////////////////////////////////////////////// /** * This is the station-side implementation of the 'submitBackupFirmwareJob' action. */ public BOrd doSubmitBackupFirmwareJob() { // Instantiates a BTestBackupJob BTestDriverBackupJob backupJob = new BTestDriverBackupJob((BTestDriverNetwork)getNetwork(), this ); // Submits the BTestBackupJob to the station Job service BOrd ordToMountedJob = backupJob.submit(null); // Returns a BOrd that refers to the BTestBackupJob that is now // mounted an running through the station Job service. return ordToMountedJob; } }
package com.testCompany.testDriver.ui; import javax.baja.job.BJob; import javax.baja.naming.BOrd; import javax.baja.sys.BComponent; import javax.baja.sys.BajaRuntimeException; import javax.baja.sys.Sys; import javax.baja.sys.Type; import javax.baja.ui.CommandArtifact; import com.testCompany.testDriver.BTestDriverDevice; import com.tridium.ddf.BDdfNetwork; import com.tridium.ddf.ui.DdfMgrControllerUtil.DdfDeviceMgrAgentCommand; import com.tridium.ddf.ui.device.BDdfDeviceManager; import com.tridium.ddf.ui.device.BDdfDeviceMgrAgent; public class BTestDriverBackupFirmwareButton extends BDdfDeviceMgrAgent { /*- class BTestDriverBackupFirmwareButton { } -*/ /*+ ------------ BEGIN BAJA AUTO GENERATED CODE ------------ +*/ /*@ $com.testCompany.testDriver.ui.BTestDriverBackupFirmwareButton(3947811060)1.0$ @*/ /* Generated Thu Jan 17 09:55:44 EST 2008 by Slot-o-Matic 2000 (c) Tridium, Inc. 2000 */ //////////////////////////////////////////////////////////////// // Type //////////////////////////////////////////////////////////////// public Type getType() { return TYPE; } public static final Type TYPE = Sys.loadType(BTestDriverBackupFirmwareButton.class); /*+ ------------ END BAJA AUTO GENERATED CODE -------------- +*/ /** * This method is called on the client-side VM (probably Workbench) when a user clicks * this agent's button. */ public CommandArtifact doInvoke(BDdfDeviceManager deviceManager, BDdfNetwork network) { // Gets all items that are selected in the 'Database' pane BComponent[] selectedDbItems = deviceManager.getController().getSelectedComponents(); // This backs-up the firmware on the first selected device if (selectedDbItems[0] instanceof BTestDriverDevice) // Sanity check. { // Gets the selected device. BTestDriverDevice selectedDevice = (BTestDriverDevice)selectedDbItems[0]; // Please note that this call-back happens on the client-side VM (probably in Workbench) // This kicks-off a job and runs it on the station-side VM (probably in a Jace). Since // the job is mounted on the station, the following method returns an ord that can be // used to access the job from the client-side VM. BOrd referenceToJobRunningInStation = selectedDevice.submitBackupFirmwareJob(); // Ensures that the Device Manager is in 'learnMode'. Otherwise, the job bar will // not be visible. deviceManager.getController().learnMode.setSelected(true); try { // This takes the job that was just initiated on the station-side VM and associates // it with the progress bar that is located towards the top of the device manager. deviceManager.getLearn().setJob(referenceToJobRunningInStation); // Updates the buttons that are available (eventually calls the 'update' method on // this agent too. deviceManager.getController().updateCommands(); } catch (Exception e) { // In the unlikely event that some exception should be thrown while associating the // job with the progress bar, this wraps the exception in a BajaRuntimeException. // The logic that calls this method will tolerate this and show the message to the // end-user. throw new BajaRuntimeException(e); } } // Although this method technically returns a 'CommandArtifact', this returns null. // The purpose of the 'CommandArtifact'is to implement an 'undo'. However, the firmware // backup cannot be undone. Returning null is perfectly acceptable, although it will // cause the 'undo' button to not be available. return null; } /** * This method provides a root key into the myTest lexicon where a button name, icon, * and description are defined in a localizable fashion. */ public String getUiName() { return "BTestBackupJob"; } /** * This method is called quite often to update the state of the various buttons that are on the * device manager. */ public void update(BDdfDeviceManager deviceManager, DdfDeviceMgrAgentCommand agentCommand) { BJob currentJob = deviceManager.getLearn().getJob(); // If there is currently no job assigned to the device manager or if the current // job is completely finished then enable both the 'Discover' and 'Backup' buttons if (currentJob == null || currentJob.getJobState().isComplete()) { // Enables the 'Discover' button deviceManager.getController().discover.setEnabled(true); // Enables this agent's button if a single row is selected in the "Database" list. agentCommand.setEnabled( deviceManager.getController().getSelectedRows().length == 1 ); } else { // Else, there currently a job that has not completed. This disables both // the 'Discover' and 'Backup' buttons deviceManager.getController().discover.setEnabled(true); agentCommand.setEnabled(false); } } }
<type name="TestDriverBackupFirmwareButton" class="com.testCompany.testDriver.ui.BTestDriverBackupFirmwareButton"> <agent> <on type="myTest:TestDriverNetwork"/> </agent> </type>
BTestBackupJob.label=Backup Firmware BTestBackupJob.icon=module://icons/x16/backup.png BTestBackupJob.accelerator=CTRL+SHIFT+ALT+B BTestBackupJob.description=Saves the device\'s internal firmware to the station database.
Copyright © 2000-2016 Tridium Inc. All rights reserved.