Networking Intelligent Devices

Novell's Embedded Systems Technology (NEST) lets you incorporate network protocols and client services into embedded systems. Gil uses NEST to put an intelligent coffee maker online, then controls it with a Windows-hosted menu program.


February 01, 1996
URL:http://www.drdobbs.com/embedded-systems/networking-intelligent-devices/184409827

Figure 2

Figure 3

Figure 2

Figure 3

FEB96: Networking Intelligent Devices

Networking Intelligent Devices

NEST brings client/server to embedded systems

Gil Gameiro

Gil is an engineer with Novell's NEST group. He can be contacted at [email protected] or http://www.vii.com/~bird/.


As any coffee aficionado will tell you, there's more to a good cup of cappuccino than freeze-dried flakes and hot water. Today's coffee makers, in fact, do just about everything but drink the java for you. The espresso machine in Figure 1, for instance, grinds beans, compacts a wafer, and serves a good strong cup of espresso--then discards the grounds in a garbage drawer.

However, what really makes this espresso maker unique is that I've connected it to a Novell network and control it with a Windows application I call "Expresso Maker." Figure 2 shows how the application lets you remotely select a coffee maker, heat the water, specify the serving size and strength, make the coffee, and gather real-time information about the process and statistics about usage--all across the network. The program provides system administration feedback for the resident "javameister"; see Figure 3.

Just Another Embedded System

In many ways, the Expresso Maker system is nothing more than a typical, networked embedded system. The system designer has to provide a secure connection, make it possible for the supplier to perform diagnostics and maintenance tasks, and enable a broker service so that end users can locate any given item/service, check for availability, and find the closest supplier.

At the heart of the Expresso Maker application is Novell's Embedded Systems Technology (NEST), which is designed to let you incorporate network protocols and client services into embedded systems. Even though my application runs under Windows, I could have written it for OS/2 PM because the library for NEST remote clients is given in source format. The development of the NEST target is hosted on whatever environment an operating-system vendor supports. For WindRiver's VxWorks on an AMD29K processor, for instance, I use a SunSPARC workstation. For FlexOS, I use DOS and Watcom C. Byte-order dependencies and alignment differences are therefore reconciled, and all system-level functions for memory and process management and event, timer, interrupt, and I/O services are specified in an API that's available for implementation on any kernel or operating system that supports preemptive multitasking.

The NEST architecture consists of the following:

Developing NEST-based applications (like Expresso Maker) requires the NEST SDK. This toolkit is DOS based, although the SPX and IPX test tools included with it are Windows hosted. Also included is a Print test tool for testing embedded NEST printers, which requires a server with NetWare 3.x or 4.x, a Windows workstation, and the embedded printer to test.

The NEST requester supports--but does not require--connection to NetWare 3.x and 4.x servers. It includes support for packet signatures, packet burst, RSA login authentication, and directory services. NEST also supports the NetWare protocol stack. C source for SPX/IPX, LSL, and MLID is based on the ODI model standard with open interfaces to the transport services for applications and the MLID to accommodate different media types. The LSL supports Ethernet 302.2, Ethernet 802.3, Ethernet II, and Ethernet SNAP frame types. The next version of NEST will also come with source code for IP, TCP, UDP, SNMP, NP, IPX, SPX, NCP, and NDS protocols.

To connect the off-the-shelf coffee maker to the network, I had to design, build, and install a custom interface card. The original espresso maker (a "Super Automatica" model from SAECO, an Italian company) was designed around a mechanical sequencer. I had to redesign the coffee maker from the ground up, since it did not provide the easy hooks needed for microprocessor-based embedded devices. At the heart of the hardware is a 286-based board (in the PC104 form with 1 MB of RAM; I could have used less but this amount of memory was on board) and a 27C010 256-KB EPROM.

For the network adapter, I used an AMD79C960 (PCNet/ISA) chip, mainly because it is a one-chip adapter and NEST provides a sample HSM that can be modified to support the PCNet/ISA. The network chip does not require additional RAM and needs only a little decoding logic, a 256-byte EPROM (mostly for the MAC address), and the line transformer.

Finally, I designed an I/O interface based on an 8255 Programmable I/O chip, an 8253 3-channel counter timer (to count the debit of water being served), a 2x16 LCD display, some push-buttons, and six opto-triacs with zero-crossing detection. These opto-triacs are boosted by TO-220AB case triacs to control the power elements in the maker--the grinder, drop-dose solenoid, a motor that compacts the grounds in a wafer, a pump and gate for the water, and a water heater that keeps the machine ready at all times to serve a cup of brew in seconds.

I used FlexOS Lite, a multitasking, real-time operating system from ISI (FlexOS was originally developed by Digital Research). Since ISI's implementation does not provide ROMing tools, I had to write a utility that links the compiled operating system with the NESTed application, compresses it, and generates an Intel Hex file along with a loader header. The loader code presents a valid signature to the ROM POST that initializes it. It then handles the boot process by loading the operating system along with its NEST driver application without returning.

Even with all this, the most difficult part of the job was cutting a square hole on the thick metal back of the coffee maker to insert the RJ45 10baseT network connection.

Building the Expresso Maker Program

Central to any NEST app is the NEST Protocol (NP), a "light" protocol especially developed for embedded devices allowing for secure remote configuration and operation. NP's design puts the connection burden on the remote client, minimizing the code on the NEST side, while maintaining a secure connection based on a login with a randomly generated access key and encrypted password. Moreover, each packet contains a running public-key signature. NP works on top of the IPX layer to minimize the required protocol stack. It wants less than 10 KB of code on the embedded device. This protocol does not provide real-time management. It was aimed at browsing and maintaining settings of predefined configurable items (given that the logged-in user has relevant access rights).

When building Expresso Maker, I wanted NP to transfer information other than remote-configuration data. Fortunately, I found an undocumented function, NPRegisterClassHandler(), used by the remote configuration component to register its services with the NP daemon. The call requires a pointer to a function (void (*)(NPConnection*, ECB*, ECB*)) and an integer that is a function number of NP to manage.

NP is composed of an NPHeader and IPX; see Listing One. The Function and SubFunction fields give the type of information carried by the data following the header. When the NP daemon receives a request from a remote client, it matches the Function number to known services and normally discards the packet if the service is unknown--unless it matches a user-defined registered handler (also confusingly referred to as "Class Number" in the source comments). Listing Two handles my extended class of services.

The ClassHandler of incoming data is rather simple, but its expected behavior is rather tricky: It takes a pointer to a ConnectionHandle, a structure maintained by the NP for every logged-in and active connection. (I strongly recommend not modifying any of the contents of this structure, as it is apparently present for the ClassHandler in charge of the login and authentication process.)

The two remaining parameters are pointers to event control block (ECB) structures that send and receive network packets. The first ECB* is the received ECB for the request, and the second is an ECB preallocated and formatted for a reply to the request. You can assume that the ECB_Fragment[1].FragmentAddress will always contain the full NP data, including the header, and that any remaining lower-level protocol (IPX) would be taken in the ECB_Fragment[0]. Supposedly, future releases of NP will run on top of UDP as well as IPX. For now, however, forget about the protocol used, since it has been preformatted by the NP daemon.

The preallocated buffer for the NP data is big enough to contain the largest amount of data (504 bytes) supported by NP. Although IPX/Ethernet can transmit up to 1514 data bytes, Novell limited the protocol size to a total of 576 bytes because NP was designed to remain as small as possible; as such, it cannot disassemble and reassemble large packets. In fact, 576 is the largest number of bytes that can be transmitted between two IPX/Ethernet nodes in a heterogeneous network (ARCNet suffers this limit, for example). All together, SNAP (a larger frame type than most common 802.2), IPX, and the NPHeader make up close to 504 bytes.

The class-handler routine extracts the pointer in the ECB's Fragment[1] by casting it to a pointer to an NPRequestBuffer. The SubFunction number in the NP header allows routing and processing of the request made by the remote client.

A Few Caveats

Since NP was designed to be small and secure, it combines system-maintenance packets with user data. In other words, when the remote client sends a request to be handled by the class handler, the system acknowledgment is also the packet that will contain the user-reply data should the request generate data on the reply (unlike SPX).

Moreover, the way the class handler retrieves the number of OEM data bytes in the request and specifies the amount of data in the reply is still awkward: That's probably why NPRegisterClassHandler is undocumented.

Example 1 (from my class handler) shows how the amount of data in the request is retrieved. The field length of the NPHeader is converted to the CPU native order using the NSwapHiLo16() macro. The result is then increased by 4 and subtracted from SIZEOF_NPHeader. The reason for the addition is that the Length field describes the total length of the NP packet excluding the first two fields of the NPHeader (that is, CheckSum and Length). Subtracting SIZEOF_NPHeader (a define equal to the actual size of the NPHeader) gives the amount of data bytes placed in the packet for OEM usage.

Another caveat concerns declaring the amount of data attached to the reply. The class handler must not set the Length field of the NPHeader in the reply ECB; it must instead set the actual size of data to be transmitted plus SIZEOF_NPHeader in the ECB_Fragment[1].FragmentSize. The NPDaemon will modify the NPHeader's Length field to reflect the proper size.

The FragmentCount must be set to 2; otherwise no response is generated. This step is mandatory for proper use of the undocumented ClassHandlers: Failure to do so will make the remote client retry the same request repeatedly, causing your handler to be called until a timeout happens.

A last rule of thumb is to process the packet as quickly as possible: Ideally, this should take no more than 200 ms or so (if no error code is to be returned because the action takes longer, a success should be sent right back with no data, and some other mechanism should be used for reporting the actual error).

Inside the Expresso Maker Code

The class-handler code extracts the SubFunction number in the NP header and routes the request to the appropriate code, which returns an error code. The NSwapHiLo16() macro is defined to convert a 16-bit value from the CPU native format to HiLo or vice versa: It swaps or does nothing, depending on whether LITTLE_ENDIAN is defined. You can use your data the way you want, but NPHeader 16-bit data is carried in the HiLo format for portability.

The error code returned by my function is placed on the Status field of the NPHeader in the reply ECB. There, too, the NSwapHiLo16() macro is used for portability. The error code must match the philosophy of the NEST protocol: 0 is success, 70xx is an error from NP, 71xx is an error from the remote configuration. For errors in my module, I used 76xx and kept the low byte in predefined NP error code.

When SubFunction is beyond what you expect, it is reasonable to use the error in the NP range (NPER_UNKNOWN_REQUEST), as this is what NP would have otherwise returned.

The next challenge was determining how to notify the remote client of an asynchronous event, provided NP is based on a client request (NP ack+reply scheme).

Novell tipped me that the Sequence number was also to be used when the NEST side takes the initiative to notify a remote client, along with a flag 0x1000 set in the NP Flags field. This way, it can notify the client's library that a packet is not an ack/reply and requires a reply from the client. The Flags are set with that same bit, plus another 1, to show that it is a reply: That is how the sequence number is matched to either the previous request or previous notification.

Novell plans to ship this code with NEST's next release; unfortunately, when I originally designed this project, I had to write it myself. The modification of the client library was rather easy, requiring an existing internal function (makeAckPacket) to reply (without forgetting to turn on the extra bit NP_FLAG_IS_ACK with the appropriate swapping macro on the client side) and the use of the Windows PostMessage() API. The modification of the NEST side was a little harder, as I had to create Timeout and Retry for every asynchronous request. We'll dispense with the low-level details of creating asynchronous retries on the NEST side without a specific thread because Novell plans to provide this functionality in future versions of NEST.

The client deals with sending OEM extended requests by using a DLL call (undocumented at the time): NPSendExtendedNP(ConnHandle*, EDB*) that allows for sending extended NP packets on an existing connection. It takes a ConnHandle* (obtained by the NPLoginToNestDevice() API call) and an EDB* (a new structure). The EDB will probably be used for two tasks in future releases of the pEST protocol library: describing an extended packet for the call I am about to explain, and notifying events asynchronous to the application that's using the DLL (like the code I wrote on the NEST side). Toward this end, the program must post a few EDBs waiting for asynchronous events using the NPListenForEDB() API call. (When I looked at the DLL code, NPListenForEDB() was in an #if 0 condition block and I had to enable it.)

For now, we need consider only the following fields in the EDB: edbFunction and edbSubFunction must be filled with the functions we made our NEST device aware of, while edbDataBlock[] and edbDataSize contain eventual user data to attach to the NP system header. No special care is required with the byte ordering of edbFunction and edbSubFunction: The DLL swaps those two to network HiLo order. However, the data block is up to the OEM. If your NEST device is based on, say, an AMD29K CPU and the client is Windows, you have to swap entities other than bytes. If you stick to the network order (HiLo format), you'll always know what you are looking at, even when you use a LanAlyzer to look at network traffic.

According to Novell, the current release of the NPDLL library will complete all the calls synchronously, so the EDB used for an extended OEM NP message will come back modified with the answer from the NEST device (if the latter generates user data attached to the NP system ack). The next version of the DLL will complete an NPSendExtendedNP() by posting an EDB message back to the application instead of modifying the one used for the request. This change means Expresso Maker will be incompatible with future versions of the NPDLL, which will allow you to work synchronously or asynchronously by requesting the operating mode after logging into a NEST device (with the NPSetConnFlags() API, which is also in an #if 0 block in the preliminary version of the DLL).

Conclusion

The main purpose of environments such as NEST is to provide more services so that a network device actually becomes a "server." Such a device does not necessarily need to authenticate to a tree, but rather to exchange data and services with a remote client (which could even be another embedded device). NP is a small, reliable, secure protocol that can be leveraged to provide more than just remote configuration without paying the price of large code.

Acknowledgment

I'd like to thank computer journalist Philippe Guichon for his invaluable assistance in writing this article.

Figure 1: Off-the-shelf espresso maker that's modified for network control.

Figure 2: Expresso Maker user menu.

Figure 3: Expresso Maker sysadm screen.

Example 1: How data is retrieved.

int msgSize = NSwapHiLo16(reqECB->Header.Length);
msgSize -= (SIZEOF_NPHeader - 4);

Listing One

typedef struct {
   nuint16   CheckSum;
   nuint16   Length;
   nuint16   ConnectionID;
   nuint16   SequenceNumber;
   nuint16   PacketVersionID;
   nuint16   Reserved;
   nuint16   Function;
   nuint16   SubFunction;
   nuint16   Flags;
   nuint16   Status;
} NPHeader;
/* NPHeader structure is part of every NP packet going on the wire. */
void doNPEspressoMaker(NPConnection *hConn, ECB *reqECB, ECB *replyECB);
{
  /* Get a pointer to the request */
  NPRequestBuffer *stdReq = reqECB->ECB_Fragment[1].FragmentAddress;
  /* Get a pointer to the reply packet */
  NPReplyBuffer *stdReply = replyECB->ECB_Fragment[1].FragmentAddress;
  /* Extract the SubFunction number from the NP Header */
  int replySize = 0;
  int SubFunction = NSwapHiLo16(stReq->Header.SubFunction);
  switch (SubFunction) {
     /* Remotely fake a local control pannel key press */
     case EX_MAKER_FAKE_KEY: {
        /* the key ID to fake is in the first byte */
        nuint8 key = stdReq->DataBuffer[0];
        espressoAddKeyToQueue(key);
        break;
     } 
     /* Remote client is requesting the current status */
     case EX_MAKER_REQUEST_STATUS: {
        memcpy(stdReq->DataBuffer, espressoGetStatus(),
               replySize = SIZEOF_EspressoStatus);
        break;
     }
     /* Display a message on the Maker LCD Display */
     case EX_MAKER_POST_MESSAGE: {
        /* Get NP Lenght field, convert HiLo to Native */
        int msgSize = NSwapHiLo16(reqECB->Header.Length);
        msgSize -= (SIZEOF_NPHeader - 4);
        espressoPostLCD(stdReq->DataBuffer, msgSize);
     }
     /* don't forget to report an error otherwise */
     default: stdReply->Header.Status = NSwapHiLo16(NPER_UNKNOWN_REQUEST);   
  };
  /* Always reply */
  replyECB.ECB_FragmentCount = 2;
  replyECB->ECB_Fragment[1].FragmentLength = replySize + SIZEOF_NPHeader;
}

Listing Two

typedef struct _eventDataBlock {
   struct _eventDataBlock  *edbNext;
   void                    *edbConenction;
   int                      edbFunction;
   int                      edbSubFunction;
   int                      edbStatus;
   int                      edbRequestID;
   long                     edbDataSize;
   nuint8                   edbDataBlock[420];
} EDB;
/* The EDB Structure as defined in the NPCAPI.H file; the DLL API. */
int TMainWindow::sendNP(int subFunction, unsigned char *req, int reqSize, char *reply)
{
  EDB edb;
  memset(&edb, 0, sizeof(edb));
  edb.edbFunction = EX_ESPRESSO_CLASS;
  edb.edbSubFunction = subFunction;
  if (edb.edbDataSize = reqSize)
     memcpy(edb.edbDataBlock, req, reqSize);
  int error = ::NPSendExtendedNP(hConn, &edb);
  // If that request generated a reply, transfer it to the user
  if (!error && edb.edbDataSize && reply)
     // that guy's buffer better be big enough!
     memcpy(reply, edb.edbDataBlock, edb.edbDataSize);
  return error;
}
int TMainWinow::fakeKeyPress(int keyID)
{
  unsigned char key = (unsigned)keyID;
  sendNP(EX_MAKER_FAKE_KEY, &key, 1, NULL);
}

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.