docs: function TinyG() {
    // Squirrel away a ref to 'this' for use in callbacks.
    var self = this;
  
    //predefine
    this.serialPortControl = null;
    this.serialPortData = null;
    self.inHold = false;
    self.linesInBuffer = 0;
    self.timedSendsOnly = false;
    self.doneReading = false;
    self.lineBuffer = []; // start it out ensuring that the machine is reset;
    self.linesRequested = 0; // total number of lines to send, including self.linesSent
    self.linesSent = 0;      // number of lines that have been sent
    self.lineInLastSR = 0;
    self.ignoredResponses = 0; // Keep track of out-of-band commands to ignore responses to
    self.setupDone = false;
  
    var readBuffer = "";
    var _tinygParser = function (emitter, buffer) {
      // Collect data
      readBuffer += buffer.toString();
  
      // Split collected data by line endings
      var parts = readBuffer.split(/(\r\n|\r|\n)+/);
  
      // If there is leftover data,
      readBuffer = parts.pop();
  
      parts.forEach(function (part) {
        // Cleanup and remove blank or all-whitespace lines.
        if (part.match(/^\s*$/))
          return;
  
        // Remove stray XON/XOFF charaters that make it through the stream.
        part = part.replace(/([\x13\x11])/, "");
  
        // Mark everything else with a bullet
        // console.log('part: ' + part.replace(/([\x00-\x20])/, "•"));
  
        self.emit('data', part);
  
        if (part[0] == "{" /* make the IDE happy: } */) {
          try {
            jsObject = JSON.parse(part);
          } catch(err) {
            self.emit('error', new TinyGError("Parser", util.format('Unable to parse "%s": %s', part, err), {err: err, part: part}));
            return;
          }
  
          // We have to look in r/f for the footer due to a bug in TinyG...
          if (jsObject.hasOwnProperty('r')) {
            var footer = jsObject.f || (jsObject.r && jsObject.r.f);
            if (footer !== undefined) {
              if (footer[1] == 108) {
                self.emit('error', new TinyGError(
                  "Response",
                  util.format("TinyG reported an syntax error reading '%s': %d (based on %d bytes read)", JSON.stringify(jsObject.r), footer[1], footer[2]),
                  jsObject
                ));
              }
  
              else if (footer[1] == 20) {
                self.emit('error', new TinyGError(
                  "Response",
                  util.format("TinyG reported an internal error reading '%s': %d (based on %d bytes read)", JSON.stringify(jsObject.r), footer[1], footer[2]),
                  jsObject
                ));
              }
  
              else if (footer[1] == 202) {
                self.emit('error', new TinyGError(
                  "Response",
                  util.format("TinyG reported an TOO SHORT MOVE on line %d", jsObject.r.n),
                  jsObject
                ));
              }
  
              else if (footer[1] == 204) {
                self.emit('error', new TinyGError(
                  "InAlarm",
                  util.format("TinyG reported COMMAND REJECTED BY ALARM '%s'", part),
                  jsObject
                ));
              }
  
              else if (footer[1] != 0) {
                self.emit('error', new TinyGError(
                  "Response",
                  util.format("TinyG reported an error reading '%s': %d (based on %d bytes read)", JSON.stringify(jsObject.r), footer[1], footer[2]),
                  jsObject
                ));
              }
  
              // Remove the object so it doesn't get parsed anymore
              // delete jsObject.f;
              // if (jsObject.r) {
              //   delete jsObject.r.f;
              // }
            }
  
            self.emit("response", jsObject.r, footer);
  
            jsObject = jsObject.r;
          }
  
          if (jsObject.hasOwnProperty('er')) {
            self.emit("errorReport", jsObject.er);
          }
          else if (jsObject.hasOwnProperty('sr')) {
            self.emit("statusChanged", jsObject.sr);
          }
          else if (jsObject.hasOwnProperty('gc')) {
            self.emit("gcodeReceived", jsObject.gc);
          }
  
          if (jsObject.hasOwnProperty('rx')) {
            self.emit("rxReceived", jsObject.rx);
          }
  
          // if (jsObject.hasOwnProperty('qr')) {
          //   self.emit("qrReceived", jsObject, footer); // Send the whole thing -- qr is a sibling of others in the report
          // }
        }
      } // parts.forEach function
      ); // parts.forEach
    }; // _tinygParser;
  
    this._baseOptions = {
      baudRate: 115200,
      flowcontrol: ['RTSCTS'],
      // Provide our own custom parser:
      parser: _tinygParser,
      timedSendsOnly: false
    };
  }