Listing 1: The SDI-12 class interface

#ifndef _SDI-12_H_
#define _SDI-12_H_

#include "dphport.h"    // POLLED SERIAL PORT OBJECT CLASS HEADER

// CONSTANTS
const int MAX_BUF_SIZE = 80; // size of one screen's width

//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//   The SDI12_UART class inherits from the PolledSerialPort class in order
//   to implement SDI-12 specific commands.  This leaves the serial port
//   class unsullied by extraneous behavour which it shouldn't have to
//   implement, and therefore it is free to be used by other classes or
//   programs which need a plain serial port class.
//        copyright David Perelman-Hall 1995
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------

class SDI12_UART : public PolledSerialPort
{
   friend ostream& operator << (ostream& os, const SDI12_UART& m) {
      return os << (const PolledSerialPort&)m;
   }

   // CAN'T COPY--MAKES NO SENSE TO COPY A SERIAL PORT
   SDI12_UART( const SDI12_UART& );
   SDI12_UART& operator=( const SDI12_UART& );

   public:

      // CTOR
      SDI12_UART() : PolledSerialPort() {}

      // DTOR
      virtual ~SDI12_UART() {}

      // USAGE WHICH EMPLOYS THE UART--THESE FOLLOWING FOUR FUNCTIONS
      // MUST BE DEFINED IN ANY CLASS WHICH IS TO BE USED TO
      // INSTANTIATE THE SDI-12 COMMUNICATION METHOD CLASS TEMPLATE
      void SendCommand(const String& command) const;
      int GetTerminatedString( String& str, int delayTime, char terminator, 
                               int length=100 ) const;
      int IsValid() const { return portIsOpen==1; }
      void Setup( const String& );
};

//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//   The template class below must be instantiated when constructing an
//   SDI12_Logger object.  It gets instantiated with the class which
//   actually does the talking to the SDI-12 sensor.  In the case of this
//   application, it is the SDI12_UART class which does the talking, and 
//   which inherits from a very simple polled (not irq-driven) serial port
//   class intended to be used in standard PCs.  Other applications might
//   implement the communication method code in firmware or EPROM, talking
//   to a dedicated set of lines.  In that case, the CommunicationMethod
//   object may not be a UART in a PC, and the template will need to be
//   instantiated with a class which captures the dedicated line's
//   communication method.
//   The four functions which must be implemented in the Method class are:
//
//   The first 2 are used by the template class in TalkToSDI
//   1) void SendCommand( const String& command ) const;
//   2) int GetTerminatedString( String& str, int delayTime, char 
        terminator, int replyLength ) const;
//   
//   The last 2 are used by the SDI12_Logger class
//   3) int IsValid() const;
//   4) void Setup( const String& someSetupStuff );
//        copyright David Perelman-Hall 1995
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------

template < class Method >class CommunicationMethod
{
   friend ostream& operator << (ostream& os, const 
CommunicationMethod&ltMethod>& m) {
      return os << *(m.method);
   }

   // CAN'T COPY
   CommunicationMethod( const CommunicationMethod< Method >& );
   CommunicationMethod& operator=( const CommunicationMethod< Method >& );

   public:
      // CTOR & ASSIGNMENT
      CommunicationMethod(): method( new Method() ) { assert( method!=0 ); }

      // DTOR
      virtual ~CommunicationMethod() { delete method; }

      // USAGE
      String TalkToSDI( const String& command, int time=1 ) const;

   protected:
      // POINTER TO CLASS WHICH ACTUALLY DOES COMMUNICATING
      Method *method; 
};


template < class Method >
String CommunicationMethod< Method >::TalkToSDI( const String& command,
                                                 int time=10 )
const
{
   cout << "TalkToSDI sending out string [" << command << ']' 
   << " and waiting " << time << " seconds for reply" << endl;

   // SEND COMMAND STRING.  
   // CLASS INSTANTIATING THE CommunicationMethod TEMPLATE CLASS 
   // MUST IMPLEMENT SendCommand FUNCTION 
   method-&gtSendCommand( command );

   // GET REPLY.
   // CLASS INSTANTIATING THE CommunicationMethod TEMPLATE CLASS 
   // MUST IMPLEMENT GetTerminatedString FUNCTION
   String reply;
   method-&gtGetTerminatedString( reply, time, LF, MAX_BUF_SIZE );

   // REMOVE BACK OF REPLY BEYOND THE DELIMITING &ltCR>&ltLF>   unsigned pos = reply.rfind( "\r\n" );
   if( pos != NPOS )
      reply.resize( pos );
   else {
      reply.resize(0); // null out the value as being invalid
      return reply;
   }

   // PARSE OFF THE COMMAND FROM THE FRONT OF THE DATA
   pos = reply.find( "!" );
   if( pos != NPOS ) {
      reply.remove( 0, pos+1 );
   }

   cout << "TalkToSDI replied [" << reply << ']' << endl;
   return reply;
}

//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//   The SDI12_Logger class class inherits privately from the
//   CommunicationMethod template class.  Inheritance from a template allows 
//   for a wide family of communication methods.  Private inheritance models
//   the "has-a" relationship instead of the "is-a" relationship, which
//   means that the base class is not accessible to the users of the
//   SDI12_Logger class.  This is good because SDI12_Loggers certainly
//   are not communication methods.  Nevertheless, it simplifies use of 
//   the communication method because of the inheritance.
//        copyright David Perelman-Hall 1995
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------


class SDI12_Logger : private CommunicationMethod< SDI12_UART > 
{
   // READ IN AN SDI12_Logger FROM INPUT STREAM REFERENCE "istream& is"
   friend istream& operator >> (istream& is, SDI12_Logger& sdiO) {
      getline( is, sdiO.model );
      is >> sdiO.address;
      is >> sdiO.header;
      is >> sdiO.measure;
      return is;
   }
   // WRITE OUT AN SDI12_Logger ONTO OUTPUT STREAM REFERENCE "ostream& os"
   friend ostream& operator << (ostream& os, const SDI12_Logger& sdiO) {
      os << "SDI-12 MODEL....: " << sdiO.model << endl
         << "SDI-12 IDENTITY.: " << sdiO.identity << endl
         << "SDI-12 VALUE....: " << sdiO.value << endl
         << "SDI-12 HEADER...: " << sdiO.header
         << "\t\tSDI-12 ADDRESS..: " << sdiO.address << endl
         << *(sdiO.method);
      return os;
   }

   // CAN'T COPY
   SDI12_Logger( const SDI12_Logger& );
   SDI12_Logger& operator=( const SDI12_Logger& );

   public:

     // ENUM DESIGNATING STATES APPLICABLE WHEN WAITING ON SERVICE REQUEST
     enum SDI12_REPLY { REPLY_FAIL, REPLY_OK, REPLY_TIMEOUT };

      // CTOR
      SDI12_Logger()
         : replyTerminal( "\r\n" ),
         address( "0" ),
         model( "unspecified" ),
         value( "none"  ),
         header( "none"  ),
         measure( "M" ),
         identity( "unidentified" ),
         serviceRequestTime(-1),   // construct with known bad value
         numberValues(-1)          // construct with known bad value
         { method-&gtLowerRTS(); }   // RTS low for NR Systems SDI-12 Verifier

      // DTOR
      virtual ~SDI12_Logger() {}

      // SDI-12 USAGE
      String Verify() const;
      String Acknowledge() const;
      String Measure( const String& consecutive = "" ) const;
      String Identify();
      String Data( const String& consecutive="0" );
      SDI12_REPLY DataReady( const String& );
      int ParseReply( const String& );

      // CONST ACCESS TO DATA MEMBERS
      const String& Model() const { return model; }
      const String& Value() const { return value; }
      const String& Header() const { return header; }
      const String& MeasureCommand() const { return measure; }
      const String& Address() const { return address; }
      int ServiceRequestTime() const { return serviceRequestTime; }
      int NumberValues() const { return numberValues; }

      // CLASS INSTANTIATING THE CommunicationMethod TEMPLATE CLASS 
      // MUST IMPLEMENT THE IsValid MEMBER FUNCTION
      int IsValid() const { return method-&gtIsValid(); }

      // CLASS INSTANTIATING THE CommunicationMethod TEMPLATE CLASS 
      // MUST IMPLEMENT THE Setup MEMBER FUNCTION
      void Setup( const String& str ) { method-&gtSetup( str ); }

   private:
      const String replyTerminal;
      String address, model, value, header, measure, identity;
      int serviceRequestTime, numberValues;
};

#endif //_SDI-12_H_


#ifndef DPH_PORT_H
#define DPH_PORT_H

#include &ltdos.h>        // BORLAND STRINGS CLASS HEADER
#include &ltcstring.h>    // BORLAND STRINGS CLASS HEADER
#include "dphtime.h"    // TIME CLASS HEADER
#include "dphlist.h"    // LIST TEMPLATE CLASS HEADER

// #DEFINE String & typedef String list template
#define String string
typedef ListOf&ltString> StringList;

// TYPEDEF THE UNSIGNED INT FOR PORT USAGE
typedef unsigned short WORD;

// MASKS TO DETECT NUMBER OF EXISITING PORTS
const BIOS_4PORTS_MASK = 2048;
const BIOS_3PORTS_MASK = 1536;
const BIOS_2PORTS_MASK = 1024;
const BIOS_1PORTS_MASK = 512;
const BIOS_0PORTS_MASK = 0;

// UART REGISTER MASKS
const unsigned BAUD300 = 384;       // DIVISOR FOR 300 BAUD OPERATION
const unsigned BAUD1200 = 96;       // DIVISOR FOR 1200 BAUD OPERATION
const unsigned BAUD2400 = 48;       // DIVISOR FOR 2400 BAUD OPERATION
const unsigned BAUD4800 = 24;       // DIVISOR FOR 4800 BAUD OPERATION
const unsigned BAUD9600 = 12;       // DIVISOR FOR 9600 BAUD OPERATION
const unsigned _DATABITS8 = 3;      // MASK FOR  8 BITS OF DATA
const unsigned _DATABITS7 = 2;      // MASK FOR  7 BITS OF DATA
const STOPBITS1 = 1;
const STOPBITS2 = 2;
const _DLAB = 0x0080;      // MASK FOR DIVISOR LATCH ACCESS BIT
const _THRE = 0x0020;      // MASK FOR TRANSMIT HOLDING REGISTER EMPTY BIT
const _DTR = 0x0001;       // MASK FOR DATA TERMINAL READY
const _RTS = 0x0002;       // MASK FOR REQUEST TO SEND
const BREAK_ON = 64;       // MASK TO TURN ON BREAK BIT IN LCR
const BREAK_OFF = 191;     // MASK TO TURN OFF BREAK BIT IN LCR


//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//   class PolledSerialPort
//   Implements the basics of a PC serial communications port, including
//   setting port address, baud, parity, stop, data, and the ability
//   to condition registers in the UART.  It does not make any use of IRQs.
//   copyright David Perelman-Hall 1995
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------

class PolledSerialPort {
   friend istream& operator >> (istream& is, PolledSerialPort& sp);
   friend ostream& operator << (ostream& os, const PolledSerialPort& sp);

   // CAN'T COPY
   PolledSerialPort(const PolledSerialPort& p);
   PolledSerialPort& operator=(const PolledSerialPort& p);

   public:
      // CTOR, DEFAULT SETS UP PORT W/OUT ADDRESS OR BAUD RATE
      PolledSerialPort(WORD port=0, unsigned baud=0);

		// DTOR
		virtual ~PolledSerialPort() {}

      // GET PORT SETTINGS
      WORD Address() const { return baseAddress; }
      WORD Data() const { return data; }
      WORD Parity() const { return parity; }
      WORD Stop() const { return stop; }
      WORD Baud() const { return baud; }
      const String& BaudText() const { return baudAsText; }
      const String& PortText() const { return portAsText; }

      // SET PORT SETTINGS
      void SetBaud( const String& baudStr );
      void SetData( const String& dataStr );
      void SetStop( const String& stopStr );
      void SetParity(const String& parityStr );
      void SetPort( const String& portStr );
      void SetAddress( const WORD address ) { baseAddress=address; }
      void SetBaudText( const String& baud ) { baudAsText=baud; }
      void SetPortText( const String& port ) { portAsText=port; }

      // REGISTER VALUES
      WORD RBR() const { return baseAddress; }    // Receive Buffer
      WORD THR() const { return baseAddress; }    // Transmit Hold
      WORD DLAB() const { return baseAddress; }   // Divisor Latch Bit
      WORD DLBM() const { return baseAddress+1; } // Divisor Latch Bit M
      WORD IER() const { return baseAddress+1; }  // Interrupt Enable
      WORD LCR() const { return baseAddress+3; }  // Line Control
      WORD MCR() const { return baseAddress+4; }  // Modem Control
      WORD LSR() const { return baseAddress+5; }  // Line Status
      WORD MSR() const { return baseAddress+6; }  // Modem Status

      // LINE USAGE
      void RaiseDTR() const  {
         unsigned char val=inportb(MCR()); 
         outportb(MCR(), val|1);
      }

      void LowerDTR() const {
         unsigned char val=inportb(MCR()); 
         outportb(MCR(),val&254);
      }

      void RaiseRTS() const {
         unsigned char val=inportb(MCR()); 
         outportb(MCR(),val|2);
      }

      void LowerRTS() const {
         unsigned char val=inportb(MCR()); 
         outportb(MCR(),val&253);
      }

      // PORT USAGE
      virtual void OpenPort(); // does work of talking to UART registers
      virtual void ClearPort() const;
      virtual void SetBaudRate();
      int ReadyToSendChar() const { return (inportb(LSR())& _THRE)==_THRE; }
      int CharWaiting() const { return inportb(LSR()) & 0x0001; }
      void SendChar( const unsigned char& ch ) const { outportb(THR(), ch); }
      unsigned char ReadChar() const { return inportb(RBR()); }
      void SendString( const String& ) const;
      void BreakOn() const;
      void BreakOff() const;

   protected:
      static StringList portsInUse;
      String baudAsText, portAsText;
      WORD baseAddress, data, parity, stop, baud;
      DPH_Time timer;
      int portIsOpen;
};

#endif // DPH_PORT_H