Chapter 17 - Create a Write Request For Your Driver

The developer driver framework will use your driver's write request to automatically update point values in your equipment when the value in the corresponding control point component changes from within Niagara AX. Please follow these steps to create a write-request for your driver.

While following the examples in this chapter, please replace the text jarFileName, yourDriver and yourCompany as previously described in the Preparation section):

  1. Further review your equipment's protocol documentation. If you are working directly for the manufacturer of the equipment, then they should be able to provide you with one or more documents that describes the way in which the equipment communicates, plus the structure of the data that the equipment expects to see on the field-bus. If you have purchased this equipment then you will need to negotiate with the equipment's manufacturer in order to gain access to the equipment's protocol.
  2. Pick a message from your protocol that updates one or more of the data point values that you are interested in, preferably one or more of the data point values that you retrieved in day 2's lesson. After reviewing the equipment's protocol documentation, choose a message from the protocol that looks like the simplest message that accomplishes this task.

    Some protocols are designed so that one write message can update more than one data point in the equipment. Other protocols are designed with a one-to-one relationship between write messages and point values. The developer driver framework accommodates both of these scenarios.

  3. Make a Java class that extends BDdfWriteRequest. Name it BYourDriverWriteRequest. Create this in a package named com.yourCompany.yourDriver.comm.req. Also add an empty slotomatic comment immediately after the opening brace for the class declaration.

    To do this, create a text file named BYourDriverWriteRequest.java in the jarFileName/src/com/yourCompany/yourDriver/comm/req folder. Inside the text file, start with the following text:

    package com.yourCompany.yourDriver.comm.req;
    
    import com.tridium.ddf.comm.req.*;
    import javax.baja.sys.*;
    
    public class BYourDriverWriteRequest
      extends BDdfWriteRequest
    {
      /*-
      class BYourDriverWriteRequest
      {
      }
      -*/
    }
    

  4. Override the toByteArray method. Build the byte array following your protocol's write message.

    Inside the body of the toByteArray method, you will need to construct a Java byte array and return it. The next step will further describe how to do this.

    To add the toByteArray method, add the following lines of text after the slotomatic comment of the class:

      public byte[] toByteArray()
      {
      }
    

  5. Assume that any data you need in order to construct the write message itself, ignoring any details about which particular data value to write, is a frozen property on a given write parameters structure. Just as in previous lessons, you will create this structure later in the day's lesson. This will help you determine what information needs to be included in the write parameters structure. Suffice it to say, you will soon create another component that we will call the write parameters. On this component, you will define one or more frozen properties that uniquely identifies a particular write request message on your equipment's field-bus.

    As mentioned during the lesson for day 1, frozen properties are a special kind of Niagara AX property on a Niagara AX component that you can easily access from Java source code. In subsequent chapters, you will make the class for the write parameters structure. For now, please assume that you have already created it.

    Please recall that to create frozen properties on a component, you add a special comment just after the class statement in the Java file. After doing that, you will run a Niagara AX development utility called slotomatic that will parse the special comment and add some Java code to your file -- the Java code necessary to add the property to the Niagara AX component that the Java file defines. This automatically generated Java code includes a method (function) called getMyProperty (where myProperty is the name of the frozen property, as you would have declared in the special comment).

    In light of all this discussion, please finish updating the toByteArray method to return a byte array that matches the description that your protocol document defined for the message that you chose to be the write request. Please follow this example as a guide:

    public byte[] toByteArray()
    {
      // In the dev driver framework, all requests are automatically
      // Assigned a deviceId when they are created. In addition to the
      // DeviceId, write requests are automatically assigned an instance
      // Of a Write Parameters structure when they are
      // Created. The dev driver framework calls the toByteArray method
      // (function) after it creates the write request, therefore this
      // Particular request has already been assigned a device id and
      // a Write parameters structure. The deviceId will
      // Be an instance of BYourDriverDeviceId, the writeParameters
      // Structure will be an instance of BYourDriverWriteParameters,
      // That is how dev driver works! This happens automatically.
    
      BYourDriverDeviceId deviceId =
        (BYourDriverDeviceId)getDeviceId();
    
      BYourDriverWriteParams writeParams =
        (BYourDriverWriteParams)getWriteParameters();
    
      final byte SOH = 0x01;
      final byte EOT = 0x04;
      // In this hypothetical example, the protocol document
      // Indicates that all requests start with a hex 01 byte and
      // All requests end with a hex 04 byte.
      // After the hex 01, the protocol expects a number between
      // 0 and 255 to identify the device, followed by the ASCII
      // Characters "Write " or "Force Write" followed by one or more of the
      // following:
      //   ao{X}={signed int}
      //   do{X}=On
      //   do{X}=Off
      // Where:
      //   {X} = a sequence of ASCII digits '0'-'9' to identify which
      //         analog or digital output to change.
      //
      //   {signed int} = a sequence of ASCII digits '0'-'9' preceded
      //                  optionally by a minus sign
      //
      // If more than one of these sequences are present then they are
      // delimited by commas.
      // The message ends with a hex 04 terminator byte.
    
      // ByteArrayOuptutStream is a standard Java class in package java.io
      // Let's use it to help us build the byte array that we will return
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      // Writes the hex 01 start character to bos, our Java stream of
      // bytes.
      bos.write(SOH);
    
      // Writes the unit number onto bos, our Java stream of bytes.
      bos.write(deviceId.getUnitNumber());
    
    
      // Possibly writes the ASCII bytes for "force" followed by a space to
      // bos, our Java stream of bytes
      if (writeParams.getForceWrite())
      {
        bos.write('f'); bos.write('o'); bos.write('r'); bos.write('c'); bos.write('e');
        bos.write(' ');
      }
      // Writes the ASCII bytes for "write" followed by a space to
      // bos, our Java stream of bytes
      bos.write('w'); bos.write('r'); bos.write('i'); bos.write('t'); bos.write('e');
      bos.write(' ');
    
    
      // Loops through all control points that are to be updated by this request.
      // The dev driver framework will try to coalesce all control points
      // Under a device that have equivalent write parameters into a single write
      // Request. You can get the array of these items by calling the method
      // named getWritableSource. It returns an array of IDdfWritable -- these
      // will be the proxy extensions of your driver's control points.
      IDdfWritable pointsToUpdate[] = getWritableSource();
      for (int i=0; i<pointsToUpdate.length;i++)
      { // This is a good thing to check in case dev driver adds support for
        // Writing components that are not proxy extensions
        if (pointsToUpdate[i] instanceof BYourDriverProxyExt)
        { // Casts the IDdfWritable to BYourDriverProxyExt
          BYourDriverProxyExt updateProxy = (BYourDriverProxyExt)pointsToUpdate[i];
          // Gets the read parameters structure, it helped BYourDriverReadRequest
          // know whether to read analog or digital. We can make optimal use of this
          // property by re-using it here also.
          BYourDriverReadParams proxyReadParams =
            (BYourDriverReadParams)updateProxy.getReadParameters();
          // Gets the point id structure for the particular point. We previously
          // defined it to contain an offset. We can make optimal use of this offset
          // by re-using it here also.
          BYourDriverPointId proxyPointId =
            (BYourDriverPointId)updateProxy.getPointId();
          // This boolean helps our logic remember whether the point is digital
          boolean digitalProxy=false;
          // The getRawValue method is available for your convenience to convert any
          // IDdfWritable among the writable source into a double precision value.
          double proxyValue = getRawValue(updateProxy);
          if (proxyReadParams.getTypeString().equalsIgnoreCase("analog"))
          { // If updating an analog output on the field-device
            bos.write('a'); bos.write('o');
            digitalProxy=false;
          }
          else if (proxyReadParams.getTypeString().equalsIgnoreCase("digital"))
          { // Else, if updating a digital output on the field-device
            bos.write('d'); bos.write('o');
            digitalProxy=true;
          }
          else // Sanity check
            throw new RuntimeException("Oops! Writing type string '"+
              proxyReadParams.getTypeString()+"' is not supported.");
          // Writes the point index as a string
          bos.write(Integer.toString(proxyId.getOffset()).getBytes());
          bos.write('=');
          // Writes the new value
          if (digitalProxy)          // If the point is digital
          {
            if (proxyValue>0)        // If the rawValue is greater than 0
            {
              bos.write('o'); bos.write('n'); // Writes "on"
            }
            else                     // Else, rawValue must be 0
            {                        // Writes "off"
              bos.write('o'); bos.write('f'); out.write('f');
            }
          }    // Else, the point is analog
          else // The Java Long.toString method outputs a sequence of ASCII digits
            bos.write(Long.toString(Math.round(proxyValue))) // Possibly preceded by '-'
    
          if(i+1<pointsToUpdate.length) // If not the last point in the loop
            bos.write(',');             // Then this writes a comma
        } // End of if  instanceof etc.
      } // End of for loop
      // Writes the byte that according to our hypothetical protocol,
      // Indicates the end of the message on the field-bus.
      bos.write(EOT);
      // Converts the stream of bytes in our Java stream of bytes into
      // An actual Java byte array and returns it.
      return bos.toByteArray();
    }
    

  6. Override the processReceive method and return a new instance of your write response class (to be created in a subsequent chapter).

    To recap part of day 1's lesson, the developer driver framework calls the toByteArray method (function), transmits the resulting byte array onto the field-bus, looks for incoming data frames, and passes them to this method (until this method returns a response (not null), throws an exception, or times out.

    Please implement the processReceive using the following Java code as a guide:

    public BIDdfResponse processReceive(IDdfDataFrame recieveFrame)
       throws DdfResponseException
    { // We coded our hypothetical receiver such that received frames always have
      // at least 2 bytes. So lets not worry about checking for that
      String responseStatus = new String( // Constructs a string from bytes
        receiveFrame.getFrameBytes(),     // In the receive frame buffer
        2,                                // Starting at index 2, taking
        receiveFrame.getFrameSize()-3);   // All chars except SOH, unitNbr, and EOT
      // According to our hypothetical protocol, the device responds 'OK' if the
      // Write succeeds. Any other string denies the write. In the event of a
      // Denial, the string itself describes the denial in plain English.
      if (responseStatus.equalsIgnoreCase("OK"))
        return new BYourDriverWriteResponse();
      else
        throw new DdfResponseException("Equipment Nak - "+responseStatus);
    }