ExacTsoft C++ framework is kind of creature, that was born back in 2000s, when I realized, that products, I developed back there, need common functional codebase.

What was needed (click to unfold):

There was set of criteria, that gradually grew over time, which motivated me to create the basic framework skeleton at first, then add considerable amount of functional 'meat', sometimes reinventing the wheels, sometimes inventing the square-wheeled bicycles, sometimes doing something completely new (for me, of course).

  1. Strict separation of non-visual, and visual (GUI) areas of functionality
  2. Some generic cross-platform implementations, for file primitives, for instance
  3. Extendability
  4. String class + its satellites, like I18N, locale, misc encoding converters, tokenizer, RegEx
  5. File, Path, etc. primitives
  6. Streams
  7. Basic XML support
  8. Math, advanced math
  9. Communications
  10. Reflection
  11. Scripting

The final framework structure, that crystallized from all that years in development, was like the following:

escore - All basic stuff, including reflection engine, date-time, string and support classes, powerful variant, extensible COM-like interface support, RegEx, files, streams, object serialization support. Many important classes are reflected. 

escomm - Communication primitives. UARTs, FTDI direct driver control, Bluetooth, Bluetooth LE, Sockets. Channel abstractions, implemented over these different types of IO. All classes functionality is reflected.

esmath - Basic and advanced math classes, all reflected.

esscript - A scripting engine, configurable compiler, compiled 'binaries' serialization, virtual machine for execution. Script uses reflection to invoke all C++ functionality exposed from escore, escomm, esmath to be available from scripting language, with ability to load an additional binaries, or lightweight scriptlets, to be compiled on demand.

Script compiler is the only place, where boost is used :) I tried to eliminate all possible external dependencies everywhere else.

escore may be configured to use different RTLs, as needed, for instance it may use parts of Embarcadero RTL for some specific internal stuff, like ZIP compression-decompression, RegEx, etc. Otherwise, it may use boost implementation(s) of RegEx, like xpressive, if available, or the boost classic one. Otherwise, an internal ZLIB implementation may be used in escore.

The GUI framework part is development environment - specific, for instance, historically I used 4 GUI frameworks, under different conditions: MFC, wxWidgets, VCL, and, finally, Firemonkey.

The proverb says "one look is worth a thousand words", so here are small sample snippets related to miscellaneous aspects of ES framework usage. Scripting examples may be directly cut-and-pase into scripting host tool application, available for the download below, compiled, and executed right away.

ES script examples

NMEA0183 parser object

NMEA0183 parser object, implemented in ES script language

// NMEA183 parser
//
// -----------------------------------------------------------------------------------
// Talker sentences
// 
// $TTSSS[,D0,D1,....][*HH]<CR><LF>
// TT - talker ID
// SSS - sentence ID
// D0..DN - data fields
// *HH - optional hex checksum byte = All sentence chars between $ and *, XORED.
//
// Sentence length (between $ and <CR><LF>) is variable, up to max value of 80 bytes.
//
// -----------------------------------------------------------------------------------
// Query sentences
//
// $TTLLQ,SSS[*HH][<CR>][<LF>] 
// TT - talker (requester) ID
// LL - listener ID (device, being queried)
// Q - literal query sentence identifier
//
// -----------------------------------------------------------------------------------
// Proprietary sentences
//
// $PMMM[MD0..MDN][,D0,D1,...][*HH]<CR><LF>
// P - literal proprietary sentence identifier
// MMM - manufacturer ID
// MD0..MDN - manufacturer-specific data
// The rest of the sentence is formatted the same way as the generic one.
//

object EsNmea0183
{
  var m_re,     //< Sentence regex parser 
    m_tok,      //< Sentence tokenizer
    m_buff,     //< Receive buffer
    m_handler;  //< NMEA event handler object
  
  // Reset reception buffer
  function receptionBufferReset()
  {
    m_buff = EsVar::as(EsVariantType$$VAR_BIN_BUFFER);
  }
  
  // Calculate sentence checksum, given the input sentence string.
  function checksumGet(sentence)
  var sum = 0, char;
  {
    foreach(char in sentence)
      sum ^= char#asByte();
    
    return sum#asByte();
  }
  
  // Helper method - string send over the channel
  function strSend(chnl, str)
  var buff = EsStr::toByteString("$" + str);
  {
    return chnl.putBytes( 
      buff,
      chnl.txTimeEstimateGet(
        buff#countGet()
      )
    ) == buff#countGet();  
  }
  
  // Send query sentence
  function querySend(chnl, talkerId, listenerId, sentenceId, withChecksum, withCrLf)
  var cs, str;
  {
    str = talkerId#asString() + listenerId#asString() + "Q," + sentenceId#asString();

    if( withChecksum )
    {
      cs = checksumGet(str);
      str += EsStr::format("*%02X", cs);
    }
    
    if( withCrLf )
    {
      str += "\r\n";
    }
    
    return strSend(chnl, str);
  }
  
  // Send proprietary sentence
  function proprietarySend(chnl, manufacturerId, manufacturerData, data, withChecksum, withCrLf)
  var cs, str;
  {
    str = "P" + manufacturerId#asString() + manufacturerData#asString();
    
    if( !data#isEmpty() )
    {
      if( data#isCollection() )
        str += EsStr::fromArray(data, ",", ",", "");
      else
        str += "," + data#asString();
    }
    
    if( withChecksum )
    {
      cs = checksumGet(str);
      str += EsStr::format("*%02X", cs);
    }
    
    if( withCrLf )
    {
      str += "\r\n";
    }
    
    return strSend(chnl, str);
  }
  
  // Parse sentence(s) from input string
  function parseString(input)
  var rngSentence, 
    rngID1, rngID2, 
    rngData,
    rngChsum,
    cs, csReceived, csOk,
    endPos, data;
  {
    if( !input#isString() )
      throw "Wrong parser input type. String expected.";
  
    if( m_handler#isEmpty() || input#countGet() < 8 )
      return;
  
    m_re$text = input;
    
    while( m_re$matches )
    {
      csOk = true;
      rngSentence = m_re.matchRangeGet(0); //< Entire sentence match range
      rngData = m_re.matchRangeGet(7);
      rngChsum = m_re.matchRangeGet(8);
      
      // Check if sentence checksum is OK
      if( rngChsum )
      {
        cs = input#sliceGet(rngChsum[0], rngChsum[1]);
        csReceived = (EsStr::hexToBinNibble(cs[0]) << 4) + 
          EsStr::hexToBinNibble(cs[1]);
        
        endPos = rngSentence[1]-1;
        if( '\n' == input[endPos] )
          --endPos;
        if( '\r' == input[endPos] )
          --endPos;
        endPos -= 2;  

        cs = checksumGet(
          input#sliceGet(rngSentence[0]+1, endPos)
        );
        
//        EsScriptDebug::log("inCS = %d; calcCS '%s' = %d", csReceived, input#sliceGet(rngSentence[0]+1, endPos), cs);
        csOk = cs == csReceived;
      }
      
      if( csOk )
      {
        // Tokenize data, if any
        if( rngData )
        {
          m_tok$text = input#sliceGet(rngData[0], rngData[1]);
          
//          EsScriptDebug::log( input#sliceGet(rngData[0], rngData[1]) );
          
          data = [];
          while( m_tok$moreTokens )
            data += m_tok$nextToken;
        }
        else
          data = null;

        rngID1 = m_re.matchRangeGet(3);   //< Match 3..4 is common sentence
        if( rngID1 )
        {
          rngID2 = m_re.matchRangeGet(4);
          
          m_handler.onNmeaTalkerSentence(          
            input#sliceGet(rngID1[0], rngID1[1]),
            input#sliceGet(rngID2[0], rngID2[1]),
            data
          );
          
          goto nextMatch;
        }

        rngID1 = m_re.matchRangeGet(5);   //< Match 5..6 is proprietary sentence
        if( rngID1 )
        {
          rngID2 = m_re.matchRangeGet(6);
          
          m_handler.onNmeaProprietarySentence(          
            input#sliceGet(rngID1[0], rngID1[1]),
            input#sliceGet(rngID2[0], rngID2[1]),
            data
          );
          
          goto nextMatch;
        }
        
        rngID1 = m_re.matchRangeGet(1);   //< Match 1..2 is query sentence
        if( rngID1 )
        {
          rngID2 = m_re.matchRangeGet(2);
          
          m_handler.onNmeaQuerySentence(          
            input#sliceGet(rngID1[0], rngID1[1]),
            input#sliceGet(rngID2[0], rngID2[1]),
            data
          );
        }
      }
      
    label nextMatch:      
      m_re$offset = rngSentence[1];
    }
    
    return m_re$offset;
  }
  
  // Parse sentence(s) from input byte buffer
  function parseBinBuffer(bb)
  {
    if( EsVariantType$$VAR_BIN_BUFFER != bb#typeGet() )
      throw "Wrong parser input type. Byte buffer is expected";
      
    return parseString(
      EsStr::fromByteString(bb, EsStrByteEncoding$$ASCII)
    );
  }
  
  // Parse sentence(s) from input string|string collection|byte buffer)
  function parse(input)
  var item;
  {    
    if( input#isCollection() )
    {
      foreach(item in input)
      {
        if( EsVariantType$$VAR_BIN_BUFFER == item#typeGet() )
          parseBinBuffer( item );
        else
          parseString( item#asString() );
      }
    }
    else if( EsVariantType$$VAR_BIN_BUFFER == input#typeGet() )
      parseBinBuffer( input );
    else
      parseString( input#asString() );  
  }
  
  // Read sentence(s) from the IO channel, and parse them
  function sentencesReceive(chnl)
  var tmp, 
    chEstimate = chnl#isEmpty() ? 1 : chnl.txTimeEstimateGet(1),
    parseEnd;
  {
    if( m_buff#find(B'$')#isEmpty() )
    {
      // Try to read $ from channel, given N retries, and chEstimate ms per each try
      if( EsByteReadStatus$$Success == 
          EsCommUtils::specificByteReceive(
            chnl, 
            B'$', 
            256, 
            chEstimate
          ) 
      )
      {
        m_buff += B'$';
//        EsScriptDebug::log("Sentence start synchronized");
      }
      else
      {
//        EsScriptDebug::log("Sentence start not synchronized");      
        return;
      }
    }
    
    tmp = chnl.bytesGet(128, 128*chEstimate);
    if( !tmp#isEmpty() )
    {
      m_buff += tmp;
      parseEnd = parseBinBuffer(m_buff);
      if( !parseEnd#isEmpty() ) //< Exclude parsed sequence part from the buffer
        m_buff = m_buff#sliceGet(parseEnd, m_buff#countGet());
      else //< Nothing was parsed - reset the buffer
        receptionBufferReset();
    }
  }

  new()
  {
    m_re = new EsRegEx(
      "\\$(?:(?:([A-Z]{2})([A-Z]{2})Q)|(?:([A-Z]{2})([A-Z]{3}))|(?:P([A-Z]{3})([^\\*,\\r\\n]+)?)),?([^\\*\\r\\n\\$]+)?(?:\\*([0-9A-F]{2}))?\\r?\\n",
      EsRegExCompileFlag$$DEFAULT
    );
    
    m_tok = new EsStringTokenizer();
    m_tok$separators = ",";
    m_tok$skipMultipleSeparators = false;
    
    receptionBufferReset();
  }
  
  // Properties
  //
  
  // NMEA event handler
  property handler;
  read: { return m_handler; }
  write: 
  { 
    if( __value )
      // Assign weak reference to the handler variable, to avoid circular referencing
      m_handler = __value$weakReference;
    else
      m_handler = null;
  }
}

NMEA0183 parser object extension for MTK

NMEA0183 parser object, implemented in ES script language

// MTK3333 NMEA custom packets extension
//
##require("NMEA183.ess");

// MTK chips && supported commands
//
enum MTK_DIRECTION
{
  IN  = 0;
  OUT = 1;
}

const c_MTK_CMDS = [
  // Command item is as follows:
  //[CMD, IN|OUT, FLD_COUNT, Descr]
  [ 
    3333,
    [
      [0,     MTK_DIRECTION$$OUT,   0,    "Test"],
      [1,     MTK_DIRECTION$$IN,    2,    "ACK response"],
      [220,   MTK_DIRECTION$$OUT,   1,    "Set POS fix rate"],
      [251,   MTK_DIRECTION$$OUT,   1,    "Set NMEA baudrate"],
      [300,   MTK_DIRECTION$$OUT,   5,    "Set fix control"],
      [314,   MTK_DIRECTION$$OUT,   19,   "Set NMEA sentence refresh intervals"],
      [330,   MTK_DIRECTION$$OUT,   1,    "Set datum"],
      [386,   MTK_DIRECTION$$OUT,   1,    "Set static navigation threshold"],
      [400,   MTK_DIRECTION$$OUT,   0,    "Fix control query"],
      [414,   MTK_DIRECTION$$OUT,   0,    "NMEA sentence refresh intervals query"],
      [430,   MTK_DIRECTION$$OUT,   0,    "Datum query"],
      [500,   MTK_DIRECTION$$IN,    5,    "Fix control data"],
      [514,   MTK_DIRECTION$$IN,    19,   "NMEA sentence refresh intervals"],
      [605,   MTK_DIRECTION$$OUT,   0,    "Firmware release info query"],
      [705,   MTK_DIRECTION$$IN,    4,    "Firmware release info"]
    ]
  ]
];

// NMEA0183 extensions for MTK platform
//
object EsNmeaMtk extends EsNmea0183
{
  var 
    m_chip,
    m_cmds;

  // Initialize commands available for the currently selected chip
  function commandsInit()
  var entry;
  {
    m_cmds = null;
    foreach(entry in c_MTK_CMDS)
    {
      if( m_chip == entry[0] )
      {
        m_cmds = entry[1];
        break;
      }
    }  
  }

  // Check if chip is supported
  function chipCheck(chip)
  var entry;
  {
    foreach(entry in c_MTK_CMDS)
    {
      if( chip == entry[0] )
        return;
    }
    
    throw EsStr::format("MTK Chip %d is not supported", chip);
  }
  
  // Check if command code is supported, requested direction is right,
  // and data filed count is as expected by the command
  //
  function cmdCheck(cmd, dir, data)
  var cmdEntry, fldCnt;
  {
    foreach(cmdEntry in m_cmds)
    {
      if( cmd == cmdEntry[0] )
      {
        if( dir != cmdEntry[1] )
          throw EsStr::format(
            "Wrong MTK command '%03d' direction.",
            cmd
          );
        
        fldCnt = cmdEntry[2];
        if( 0 == fldCnt && !data#isEmpty() )
          throw EsStr::format(
            "No data expected for MTK command '%03d'",
            cmd
          );
        else if( 0 < fldCnt )
        {
          if( data#isEmpty() )
            throw EsStr::format(
              "Expected %d data items for MTK command '%03d'",
              fldCnt,
              cmd
            );
          else if( fldCnt != data#countGet() )
            throw EsStr::format(
              "Expected %d data items for MTK command '%03d', got %d instead",
              fldCnt,
              cmd,
              data#countGet()
            );
        } 
        return;
      }
    }
    
    throw EsStr::format(
      "Unsupported MTK command '%03d' for chip '%d'",
      cmd,
      m_chip
    );
  }
  
  new(chip)
  {
    EsNmea0183::new();
    chipCheck(chip);
    m_chip = chip;
    commandsInit();
  }

  // Default ctor - use 3333 chip family by default
  new()
  {
    m_chip = 3333;
    commandsInit();
  }
  
  // Send proprietary packet to the MTK chip
  function cmdSend(chnl, cmd, data, withCrLf)
  {
    cmdCheck(cmd, MTK_DIRECTION$$OUT, data);
    return proprietarySend(
      chnl, 
      "MTK", 
      EsStr::format("%03u", cmd), 
      data,
      true, 
      withCrLf
    );
  }
  
  // Explicit formatting for special cmd 314 form
  function nmeaRefreshIntervalsReset(chnl, withCrLf)
  {
    return proprietarySend(
      chnl, 
      "MTK", 
      314, 
      [-1],
      true, 
      withCrLf
    );
  }
  
  // Properties
  //
  
  property chip;
  read: { return m_chip; }
  
  property commands;
  read: { return m_cmds; }
}

ES script dynamic object layout example

Script object dynamic layout feature demonstration.

When executed in ES script console tool, it outputs the following:

test0 object size = 3
test1 object size = 3
test1 object properties = type;attributeNames;propertyNames;persistentPropertyNames;weakReference;size;offset;classAttributeNames;fieldNames;buffer;tag;u8
test1 object fields = f_u8;f_u16
test1 object size = 15
test1 object properties = type;attributeNames;propertyNames;persistentPropertyNames;weakReference;size;offset;classAttributeNames;fieldNames;buffer;tag;u8;dt;float
test1 object fields = f_u8;f_u16;f_dt;f_float
// ES script dynamic data object layout
//
object DLobject
{
 // Object variable(s)
 // Do not influence the object size 
 var m_tag;

 // Object data fields, these form the object data buffer, and
 // its size. Supported are signed and unsigned ints, 8-64 bit wide
 // 32 bit floats, 64 bit date time, and arrays.
 //
 esU8 f_u8;
 esU16 f_u16;

 if( !m_tag#isEmpty() && m_tag > 128 )
 {
   esDT f_dt;
   esF  f_float;

   function addFloat(f)
   {
     f_float += f;
   }

   property dt;
   read: { return f_dt; }
   write: { f_dt = __value; }

   property float;
   read: { return f_float#asDouble(); }
 }

 // Object CTOR 
 new( tag )
 {
   m_tag = tag;
 }

 property tag;
 write: { m_tag = __value; }

 property u8;
 read: { return f_u8; }
}

var test0 = new DLobject(null), test1 = new DLobject(11);

EsScriptDebug::log( "test0 object size = %d", test0$size );
EsScriptDebug::log( "test1 object size = %d", test1$size );
EsScriptDebug::log( "test1 object properties = %s", test1$propertyNames );
EsScriptDebug::log( "test1 object fields = %s", test1$fieldNames );

// Assign the new tag value, the object test1 changes its layout dynamically 
test1$tag = 400; // Or test1.m_tag = 400;

EsScriptDebug::log( "test1 object size = %d", test1$size );
EsScriptDebug::log( "test1 object properties = %s", test1$propertyNames );
EsScriptDebug::log( "test1 object fields = %s", test1$fieldNames );

ES script NXP ISP code excerpt

NXP LPC MCU ISP firmware loader example using ESS scripting language

// NXP LPC MCU ISP firmware loader
//

##require("esMcuDbNXP.ess");

// ISP statuses
//
enum EsMcuprogNxpIspStatus
{
  CMD_SUCCESS           = 0;
  INVALID_COMMAND        = 1, "Invalid command";
  SRC_ADDR_ERROR        = 2, "SRC_ADDR_ERROR: Source address is not on word boundary";
  DST_ADDR_ERROR         = 3, "DST_ADDR_ERROR: Destination address is not on a correct boundary";
  SRC_ADDR_NOT_MAPPED    = 4, 
    "SRC_ADDR_NOT_MAPPED: Source address is not mapped in the memory map; count value is taken into consideration where applicable";
  DST_ADDR_NOT_MAPPED    = 5, 
    "DST_ADDR_NOT_MAPPED: Destination address is not mapped in the memory map; count value is taken into consideration where applicable";
  COUNT_ERROR            = 6,
    "COUNT_ERROR: Byte count is not multiple of 4 or is not a permitted value";
  INVALID_SECTOR        = 7,
    "INVALID_SECTOR: sector number is invalid or end sector number is greater than start sector number";
  SECTOR_NOT_BLANK      = 8;
  SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION = 9,
    "SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION: Command to prepare sector for write operation was not executed";
  COMPARE_ERROR          = 10, "COMPARE_ERROR: Source and destination data not equal";
  BUSY                  = 11, "BUSY: Flash programming hardware interface is busy";
  PARAM_ERROR            = 12, "PARAM_ERROR: Insufficient number of parameters or invalid parameter";
  ADDR_ERROR            = 13, "ADDR_ERROR: Address is not on word boundary";
  ADDR_NOT_MAPPED        = 14, "ADDR_NOT_MAPPED: Address is not mapped in the memory map; count value is taken in to consideration where applicable";
  CMD_LOCKED            = 15;
  INVALID_CODE          = 16, "INVALID_CODE: Unlock code is invalid";
  INVALID_BAUD_RATE      = 17, "INVALID_BAUD_RATE: Invalid baud rate setting";
  INVALID_STOP_BIT      = 18, "INVALID_STOP_BIT: Invalid stop bit setting";
  CRP_ENABLED            = 19, "Code read protection enabled";
}

const c_4_45_block      = 180;
const c_responseLenMax   = 128;

// LPC firmware flags
enum EsMcuprogNxpFirmwareFlag
{
  WIPE         = 0x01, "Wipe device before programming";
  VERIFY      = 0x02, "Verify data written to the device during programming";
  DONT_SYNC    = 0x04, "Do not synchronize with bootloader";
  DETECT_ONLY = 0x08, "Just detect device ID, do not program flash";
  NO_ECHO      = 0x10, "Disable ISP echo responses during programming";
}

const c_uuencodeTable = B" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";

// LPC programmer object
object EsMcuprogNxp
{
  var m_regex,
      m_flags,
			m_noEcho,
      m_syncRetries,
      m_bootloaderVer,
      m_mcuInfoIdx, 
      m_moniker,
      m_fw;

  function mcuIdxCheck()
  {
    if( 0 == m_mcuInfoIdx )
      throw "MCU is not identified";
  }
  
  function binaryCheck()
  {
    if( m_fw#isEmpty() || m_fw#countGet() < c_4_45_block )
      throw "Firmware image is too small";
  }
  
  function ramStartGet()
  {
    mcuIdxCheck();
    return c_mcuprogNxpRamInfoData[
      c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$chipVariant]
    ][0];
  }
  
  function ramBaseGet()
  {
    mcuIdxCheck();
    return c_mcuprogNxpRamInfoData[
      c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$chipVariant]
    ][1];
  }  
  
  function ispErrorStringGet(errorCode)
  {
    if( errorCode in EsMcuprogNxpIspStatus )
      return EsMcuprogNxpIspStatus.valueLabelGet(errorCode);
    else
      return EsStr::format("Unknown error: %d", errorCode);
  }
  
  function requestStringSend(chnl, requestString)
  var sent, toSend, request = EsStr::toByteString(requestString);
  {
    toSend = request#countGet();
    sent = chnl.bytesPut(request, chnl.txTimeEstimateGet(toSend));
		// EsScriptDebug::log("requestStringSend sent %d, from: '%s'", sent, requestString);
    if( sent != toSend )
      throw EsStr::format("Could not send bootloader command '%s'; to send %d, sent %d bytes", 
        requestString, toSend, sent);

    return sent;
  }
  
  function dataReceive(chnl, maxDataLen, wantedNrs, timeOutMilliseconds)
  var idx, data, dataLen = 0, tmpData, tmpDataLen, 
      tm = 0, nr = 0, prev = 0, b;
  {
    do
    {
      tmpData = chnl.bytesGet(maxDataLen-dataLen, 1);
      if( !tmpData#isEmpty() )
      {
        if( data#isEmpty() )
          data = tmpData;
        else
          data += tmpData;
        tmpDataLen = tmpData#countGet();
        
        for(idx = dataLen; idx < dataLen+tmpDataLen; ++idx)
        {
          b = data[idx];
          if( 0x0A == b && prev != b ) // exclude duplicate newlines
            ++nr;
          else if( b & 0x80 )
            idx = dataLen+tmpDataLen; // eof; do break for loop, but execute prev = b before it
            
          prev = b;
        }
        
        dataLen += tmpDataLen;
      }
      
      ++tm;
    } while( dataLen < maxDataLen && 
             tm < timeOutMilliseconds && 
             nr < wantedNrs );
    
		// EsScriptDebug::log("dataReceive(chnl, maxDataLen: %d, wantedNrs: %d, timeOutMilliseconds: %d) '%s'", maxDataLen, wantedNrs, timeOutMilliseconds, data);
    return data;
  }
  
  function requestSendCmdResponseGetAndVerify(chnl, requestString, errFmt)
  var response, wantedNrs, errId;
  {
    if( requestStringSend(chnl, requestString + "\n") )
    {
      if( m_noEcho )
      {
        m_regex$pattern = "([0-9]+)\r\n";
        wantedNrs = 1;
      }
      else
      {
        m_regex$pattern = requestString + "(?:\r|\n)\n?([0-9]+)\r\n";
        wantedNrs = 2;
      }
      
      response = dataReceive(chnl, c_responseLenMax, wantedNrs, 5000);
      m_regex$text = EsStr::fromByteString(response);
      // EsScriptDebug::log("cmd_request '%s'; cmd_response '%s'; pattern: '%s'", requestString, m_regex$text, m_regex$pattern);

      if( !m_regex$matches )
        throw EsStr::format(errFmt, "Wrong answer");
      
      errId = m_regex.matchGet(1)#asULong();
      if( EsMcuprogNxpIspStatus$$CMD_SUCCESS != errId )
        throw EsStr::format(errFmt,  ispErrorStringGet(errId));
    }
    
    return response;
  }
  
  function binaryIvtChecksumPrepare()
  var idx, checksum = 0, checksumOffs, progressTask;
  {
    binaryCheck();
    progressTask = new EsProgressMonitorTask("binaryIvtChecksumPrepare");
    progressTask.pulse("Preparing IVT checksum...");
    progressTask.attachTo(__scriptHost$progressMonitor);

    if( EsMcuprogNxpChipVariant$$LPC2XXX == c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$chipVariant] )
      checksumOffs = 0x14;
    else
      checksumOffs = 0x1C;

    // Patch checksumOffs, otherwise it is not running and jumps to boot mode
    // Clear the vector at checksumOffs so it doesn't affect the checksum:
    for(idx = 0; idx < 4; ++idx)
      m_fw[idx + checksumOffs] = 0;

    // Calculate a native checksum of the little endian vector table:
    for(idx = 0; idx < 32;) 
    {
      checksum += m_fw[idx++];
      checksum += m_fw[idx++] << 8;
      checksum += m_fw[idx++] << 16;
      checksum += m_fw[idx++] << 24;
    }

    // Negate the result and place in the vector at checksumOffs as little endian
    // again. The resulting vector table should checksum to 0
    checksum = ~checksum + 1;
    checksum = checksum#asULong();
    for(idx = 0; idx < 4; ++idx)
    {
      m_fw[idx + checksumOffs] = checksum & 0xFF;
      checksum >>= 8;
    }
  }
  
  function responseStringExtract(response)
  var lastQuestionpos;
  {
    lastQuestionpos = response#findLastOf('?');
    if( lastQuestionpos#isEmpty() )
      lastQuestionpos = 0;
    else
      ++lastQuestionpos;
      
    return response#sliceGet(lastQuestionpos, response#countGet());
  }
  
  function syncWithBootloader(chnl, syncRetries, doThrow)
  var requestStr, response, wdSeconds, chars, ticks, tries, synched = false, progressTask;
  {
    if( EsMcuprogNxpFirmwareFlag$$DONT_SYNC == (m_flags & EsMcuprogNxpFirmwareFlag$$DONT_SYNC) )
      return true;
    
    progressTask = new EsProgressMonitorTask("syncWithBootloader");
    progressTask.pulse("Syncing with device bootloader...");
    progressTask.attachTo(__scriptHost$progressMonitor);
    
    for( tries = 0; tries < syncRetries; ++tries )
    {
      requestStringSend(chnl, "?");
      response = dataReceive(chnl, c_responseLenMax, 1, 100);
      response = responseStringExtract(response);
		//	EsScriptDebug::log("responseStringExtract '%s'", response);
      if( !response#isEmpty() )
      {
        if( B"Bootloader\r\n" == response )
        {
          chars = (17 * m_fw#countGet() + 1) / 10;
          wdSeconds = (10 * chars + 5) / chnl$baud + 10;
          ticks = wdSeconds * ((m_moniker.m_xtalHz + 15) / 16);
          
          requestStr = "T " + ticks;
          requestStringSend(chnl, requestStr+"\n");
          response = dataReceive(chnl, c_responseLenMax, 1, 100);
          if( B"OK\r\n" != response )
          {
            if( doThrow )
              throw "No answer on 'WDT Set (" + requestStr + ")' command";
            else
              break;
          }
          
          requestStringSend(chnl, "G 10356\r\n");
          EsThreadWorker::sleep(200);
          tries = 0;
        }
        else if( B"Synchronized\r\n" == response )
        {
          requestStr = "Synchronized";
          synched = true;
          break;
        }
      }
    }
    
    if( !synched )
    {
      if( doThrow )
        throw "No answer on QM ('?')";
    }
    else
    {
      requestStringSend(chnl, requestStr+"\n");
      response = dataReceive(chnl, c_responseLenMax, 2, 1000);

      m_regex$pattern = requestStr + "(?:\r|\n)\n?OK\r\n";
      m_regex$text = EsStr::fromByteString(response);
      if( !m_regex$matches )
      {
        synched = false;
        if( doThrow )
          throw "No answer on 'Synchronized'";
      }
    }
    
    return synched;
  }
  
  function checkIfInBoot(chnl)
  {
    return syncWithBootloader(chnl, 20, false);
  }
  
  function oscCommandSend(chnl)
  var requestStr, response;
  {
    requestStr = m_moniker.m_xtalHz/1000;
    requestStringSend(chnl, requestStr+"\n");
    response = dataReceive(chnl, c_responseLenMax, 2, 1000);

    m_regex$pattern = requestStr+"(?:\r|\n)\n?OK\r\n";
    m_regex$text = EsStr::fromByteString(response);
    if( !m_regex$matches )
      throw "No answer on 'Oscillator' command";
  }
  
  function flashUnlock(chnl)
  {
    requestSendCmdResponseGetAndVerify(chnl, "U 23130", "Error executing 'Unlock' command: '%s'");
  }
  
  function bootcodeVerRead(chnl)
  var requestStr = "K", response;
  {
    requestStringSend(chnl, requestStr + "\n");
    response = dataReceive(chnl, c_responseLenMax, 4, 5000);
    
    m_regex$pattern = requestStr+"(?:\r|\n)\n?0\r\n([0-9]+)\r\n([0-9]+)";
    m_regex$text = EsStr::fromByteString(response);
    if( !m_regex$matches )
      throw "Wrong answer on Bootcode version read command";

    m_bootloaderVer = m_regex.matchGet(1) + "." + m_regex.matchGet(2);
  }
  
  function partIdRead(chnl)
  var requestStr = "J", response, 
      responseStr, id, info, infoIdx = 0, found = false;
  {
    requestStringSend(chnl, requestStr+"\n");
    response = dataReceive(chnl, c_responseLenMax, 3, 5000);
    responseStr = EsStr::fromByteString(response);
    m_regex$pattern = requestStr+"(?:\r|\n)\n?0\r\n([0-9]+)";
    m_regex$text = responseStr;
    
    if( !m_regex$matches )
      throw "No answer on Part ID read command";

    id = m_regex.matchGet(1)#asULong();
    foreach(info in c_mcuprogNxpMcuInfoData)
    {
      if( id == info[EsMcuprogNxpDeviceInfo$$id] )
      {
        m_mcuInfoIdx = infoIdx;
        found = true;
        break;
      }
      ++infoIdx;
    }
    
    if( !found )
      throw EsStr::format("Unknown device ID: '0x%08X'", id);
  }

  function flashPrepare(chnl, start, end)
  var cmd = EsStr::format("P %d %d", start, end);
  {
    requestSendCmdResponseGetAndVerify(chnl,
      cmd, "Error executing 'Flash Prepare (" + cmd + ")' command: '%s'");
  }
  
  function flashErase(chnl, start, end)
  var cmd = EsStr::format("E %d %d", start, end);
  {  
    requestSendCmdResponseGetAndVerify(chnl,
      cmd,  "Error executing 'Flash Erase (" + cmd + ")' command: '%s'");
  }  
  
  function flashPrepareAndErase(chnl)
  var endSector = 0;
  {
    if( m_flags & EsMcuprogNxpFirmwareFlag$$WIPE )
    {
      mcuIdxCheck();
      endSector = c_mcuprogNxpMcuInfoData[m_mcuInfoIdx][EsMcuprogNxpDeviceInfo$$flashSectors]-1;
    }
    
    flashPrepare(chnl, 0, endSector);
    flashErase(chnl, 0, endSector);
  }
  
  function flashBlockWriteRequest(chnl, blockLen)
  var cmd = EsStr::format("W %d %d", ramBaseGet(), blockLen);
  {
    requestSendCmdResponseGetAndVerify(chnl, 
      cmd, "Error executing 'Write (" + cmd + ")' command: '%s'");
  }
    
  function blockWrite(chnl, pos, block, blockCrc)
  var b, bCnt = 0, bb = B"\0\0\0", tmpPos, blockOffs, 
      response, responseStr, uue24 = 0,
      requestStr = "M"; // uuencoded block length (45 + ' '(\32) = 77 = M)
  {
    // Uuencode one 45 byte block
    for(blockOffs = 0; blockOffs < 45; ++blockOffs)
    {
      if( m_moniker$binaryOffs < ramStartGet() )
      { // Flash: use full memory
        tmpPos = pos + block * 45 + blockOffs;
      }
      else
      { // RAM: Skip first 0x200 bytes, these are used by the download program in LPC21xx
        tmpPos = pos + block * 45 + blockOffs + 0x200;
      }
        
      // check for proper mem access restrictions!!!
      if( tmpPos < m_fw#countGet() )
        b = m_fw[tmpPos];                        
      else 
        b = 0;
      
      bb[bCnt++] = b;
      blockCrc += b#asULong();
      if( bCnt > 2 )
      {
        bCnt = 0;
        uue24 = (bb[0]#asULong() << 16) + (bb[1]#asULong() << 8) + bb[2]#asULong();
        requestStr += c_uuencodeTable[(uue24 >> 18) & 63]#asChar();
        requestStr += c_uuencodeTable[(uue24 >> 12) & 63]#asChar();
        requestStr += c_uuencodeTable[(uue24 >>  6) & 63]#asChar();
        requestStr += c_uuencodeTable[ uue24        & 63]#asChar();
      }  
    }

    requestStr += "\n";
    //EsScriptDebug::log("blockWrite '%s'", requestStr);
    requestStringSend(chnl, requestStr);
    if( !m_noEcho )
    {
      response = dataReceive(chnl, c_responseLenMax, 1, 5000);
      responseStr = EsStr::fromByteString(response);
      if( responseStr != requestStr )
        throw "Error writing data at " + tmpPos + ", sent '" + requestStr#asEscapedString() + "', got '" + responseStr#asEscapedString() + "'";
    }
    
    return blockCrc;
  }
  
  function blockCrcWrite(chnl, crc)
  var requestStr, expectedResponse, response, expectedNrs;
  {
    requestStr = EsStr::format("%d\r\n", crc);
    if( m_noEcho )
    {
      expectedNrs = 1;
      expectedResponse = B"OK\r\n";
    }
    else
    {
      expectedNrs = 2;
      expectedResponse = EsStr::toByteString(requestStr) + B"OK\r\n";
    }
    
    //EsScriptDebug::log("blockCrcWrite_request '%s'", requestStr);
    requestStringSend(chnl, requestStr);
    response = dataReceive(chnl, c_responseLenMax, expectedNrs, 5000);
    //EsScriptDebug::log("blockCrcWrite_response '%s'", EsStr::fromByteString(response));
    if( response != expectedResponse )
      throw "Error writing block CRC";
  }
  
  function copyCommandSend(chnl, pos, copyLen)
  var cmd = EsStr::format("C %d %d %d", pos, ramBaseGet(), copyLen);
  {
    requestSendCmdResponseGetAndVerify(chnl,
      cmd, "Error executing 'Copy (" + cmd + ")' command: '%s'");
  }
  
  function verifyCommandSend(chnl, sectorStart, sectorOffs, copyLen)
  var requestStr;
  {
    //Avoid comparing first 64 bytes.
    //Because first 64 bytes are re-mapped to flash boot sector,
    //and the compare result may not be correct.
    if( sectorStart + sectorOffs < 64 )
      requestStr = EsStr::format("M %d %d %d", 64, ramBaseGet()+(64 - sectorStart - sectorOffs), copyLen-(64 - sectorStart - sectorOffs));
    else
      requestStr = EsStr::format("M %d %d %d", sectorStart + sectorOffs, ramBaseGet(), copyLen);

    requestSendCmdResponseGetAndVerify(chnl, requestStr, "Error executing 'Compare (" + requestStr + ")' command: '%s'");  
  }
  
  function copyLenAlign(copyLen, sectorLen, destInfo)
  {
    // Round copyLen up to one of the following values: 512, 1024,
    // 4096, 8192; but do not exceed the maximum copy size (usually
    // 8192, but chip-dependent)
    if(copyLen < 512)
      copyLen = 512;
    else if(sectorLen < 1024)
      copyLen = 1024;
    else if(sectorLen < 4096)
      copyLen = 4096;
    else
      copyLen = 8192;
    if(copyLen > destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize])
      copyLen = destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize];
      
    return copyLen;
  }
  
  function echoDisable(chnl)
  {
    requestSendCmdResponseGetAndVerify(chnl, "A 0", "Error executing 'Echo OFF' command: '%s'");
		m_noEcho = true;
  }
  
  function program(chnl, isAlreadySynched)
  var destInfo, sectorTable,
    sector, sectorLen, sectorStart, sectorOffs, sectorChunk,
    copyLen, blockCrc, pos, line, block, copyResidual, fwSize,
    progressTask;
  {
    // copy firmware binary buffer
    m_fw = m_moniker$binaryFw;
    fwSize = m_fw#countGet();
    binaryCheck();
    
    // EsScriptDebug::log("Programming, already synched: %s, don't sync flag: %s", isAlreadySynched, (m_flags & EsMcuprogNxpFirmwareFlag$$DONT_SYNC));
    if( !isAlreadySynched )
      syncWithBootloader(chnl, m_syncRetries, true);

    oscCommandSend(chnl);
    flashUnlock(chnl);
    bootcodeVerRead(chnl);
    partIdRead(chnl);
			
    if( m_flags & EsMcuprogNxpFirmwareFlag$$NO_ECHO )
      echoDisable(chnl);

    if( !(m_flags & EsMcuprogNxpFirmwareFlag$$DETECT_ONLY) )
    {
      binaryIvtChecksumPrepare();
    
      destInfo = c_mcuprogNxpMcuInfoData[m_mcuInfoIdx];
      sectorTable = c_mcuprogNxpSectorTableData[destInfo[EsMcuprogNxpDeviceInfo$$sectorTable]];
      // In case of a download to RAM, use full RAM for downloading
      // set the flash parameters to full RAM also.
      // This makes sure that all code is downloaded as one big sector
      if( m_moniker$binaryOffs >= ramStartGet() )
      {
        destInfo[EsMcuprogNxpDeviceInfo$$flashSectors] = 1;
        destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize] = destInfo[EsMcuprogNxpDeviceInfo$$ramSize]*1024 - (ramBaseGet() - ramStartGet());
        sectorTable = [];
        sectorTable += destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize];
      }
      // Start with sector 1 and go upward... sector 0 containing the interrupt vectors
      // will be loaded last, since it contains a checksum and device will re-enter
      // bootloader mode as long as this checksum is invalid.
      if( sectorTable[0] >= fwSize )
      {
        sector = 0;
        sectorStart = 0;
      }
      else
      {
        sectorStart = sectorTable[0];
        sector = 1;
      }
      flashPrepareAndErase(chnl);
      
      progressTask = new EsProgressMonitorTask("program");
      progressTask.initialize("Uploading firmware...", fwSize, 0);
      progressTask.attachTo(__scriptHost$progressMonitor);
      
      while(1)
      {
        if(sector >= destInfo[EsMcuprogNxpDeviceInfo$$flashSectors])
          throw "Program too large, running out of Flash sectors";

        if( m_moniker$binaryOffs < ramStartGet() ) // skip Erase when running from RAM
        {
          flashPrepare(chnl, sector, sector);
          if( EsMcuprogNxpFirmwareFlag$$WIPE != (m_flags & EsMcuprogNxpFirmwareFlag$$WIPE) && 0 != sector ) // already erased
            flashErase(chnl, sector, sector);
        }

        sectorLen = sectorTable[sector];
        if(sectorLen > fwSize - sectorStart)
          sectorLen = fwSize - sectorStart;

        for(sectorOffs = 0; sectorOffs < sectorLen; sectorOffs += sectorChunk)
        {
          // If the Flash ROM sector size is bigger than the number of bytes
          // we can copy from RAM to Flash, we must "chop up" the sector and
          // copy these individually.
          // This is especially needed in the case where a Flash sector is
          // bigger than the amount of SRAM.
          sectorChunk = sectorLen - sectorOffs;
          if(sectorChunk > destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize])
            sectorChunk = destInfo[EsMcuprogNxpDeviceInfo$$maxCopySize];

          // Write multiple of 45 * 4 Byte blocks to RAM, but copy maximum of on sector to Flash
          // In worst case we transfer up to 180 bytes to RAM
          // but then we can always use full 45 byte blocks and length is multiple of 4
          copyLen = sectorChunk;
          copyResidual = copyLen % c_4_45_block;
          if( 0 != copyResidual )
            copyLen += c_4_45_block - copyResidual;
          flashBlockWriteRequest(chnl, copyLen);
          blockCrc = 0;
          line = 0;

          // Transfer blocks of 45 * 4 bytes to RAM
          for(pos = sectorStart + sectorOffs; (pos < sectorStart + sectorOffs + copyLen) && (pos < fwSize); pos += c_4_45_block)
          {
            for (block = 0; block < 4; ++block)  // Each block 45 bytes
            {
              blockCrc = blockWrite(chnl, pos, block, blockCrc);
              ++line;

              if(20 == line)
              {
                blockCrcWrite(chnl, blockCrc);
                line = 0;
                blockCrc = 0;
              }                  
            }

            progressTask$position = pos;          
          }

          if( line != 0 )
            blockCrcWrite(chnl, blockCrc);

          if( m_moniker$binaryOffs < ramStartGet() )
          {
            flashPrepare(chnl, sector, sector);
            copyLen = copyLenAlign(copyLen, sectorLen, destInfo);
            copyCommandSend(chnl, sectorStart + sectorOffs, copyLen);

            if( m_flags & EsMcuprogNxpFirmwareFlag$$VERIFY )
              verifyCommandSend(chnl, sectorStart, sectorOffs, copyLen);
          }
        }

        if( (sectorStart + sectorLen) >= fwSize && sector !=0 )
        {
          sector = 0;
          sectorStart = 0;
          // finalize programming with sector 0
          progressTask.initialize("Finalizing firmware upload...", sectorTable[sector], 0);
        }
        else if( sector == 0 )
          break;
        else 
        {
          sectorStart += sectorTable[sector++];
          progressTask$position = sectorStart;
        }
      }
    }
  }
  
  // default ctor
  new(flags, retries, moniker)
  {
    m_regex = new EsRegEx("", EsRegExCompileFlag$$DEFAULT);
    m_flags = flags#asULong();
    m_syncRetries = retries#asULong();
    m_moniker = moniker;
		m_noEcho = false;
  }
}

The current stable version documentation is here: 

The current development version of ES scripting console is here:

Short ES script cookbook is here: