You are reading a single comment by @allObjects and its replies. Click here to read the full conversation.
  • Give this code a try... I used emulation code to emulate the i2c / sensor and embedded it all into an html. You can run it right off the link of the uploaded/download file. If opened in Chrome and inspected, you should get about the the same view as attached when clicking the buttons init(), logStatus(), and readTemp().

    The AtlasSensor code is based on your initial code that extracts the commands into command specifications. I used longer variable names than usual for documentation purposes and added also some comments.
    Methods starting with underscore (_) hint privacy and are not to be called 'from outside'.

    var p; // prototype var
    
    var STATE =
    { SUCCESS: 1
    , FAILURE: 2
    , INVALID_CMD: 201 // other than what already taken
    , PENDING: 254 // command in execution / pending request
    , NO_DATA: 255 // no command sent / no pending request
    };
    
    var tempDescr = 
      // temp sensor descriptor, most used first
      // pack everything in an anonymous function
      // to allow reusable functions without
      // polluting global name
    (function() {
    
    // reusable functions to be used in command descriptor / cmds
    // - none yet so far
    
    // setup commandSpecifications for tempDescriptor
    // one by one to make one by one executable
    var cmdSpecs = []; 
      // [0] in cmdSpec is command string
      // [1] properties the cmd result is setting
      // [2] function to interpret/handle read raw data
      // [3] time after cmd / before result (rawData) can be read
    cmdSpecs[0] =
        [ "R"     ,   ["temp", "tempTs"]
        , function(rawData) {
            this.temp = rawData * 1;
            this.tempTs = getTime();
          }, 300
        ]
        ;
    cmdSpecs[1] =  
        [ "STATUS",   ["restartCode", "volts", "statusTs"]
        , function(rawData, cmdDescr) {
            var dates = rawData.split(",");
            this.restartCode = dates[1];
            this.volts = dates[2] * 1;
            this.statusTs = getTime();
          }, 300
        ]
        ;
    
    // return descriptor
    return (
    
    { id: "AtlasTempSensor"
    , cmdSpecs: cmdSpecs
    }
    
    ); // /return expression 
    })();
    
    var AtlasSensor = function(id, i2c, addr, descr) {
      this.cmdEvtQueue = [];
      currCmdEvt = null; 
      lastCmdStr = "";
      this.id = id;
      this.i2c = i2c;
      this.addr = addr;
      this.descr = descr;
      lastCmdTs = getTime();
      lastCmdState = -1;
      lastRawData = "";
      descr.cmdSpecs.forEach(function(cmdSpec)­{ // for each command...
          cmdSpec[1].forEach(function(prop){ // ...setup obj with...
              this.prop = null; // ...sensor and cmd specific...
            }, this); // ...properties
        }, this);
    };
    
    AtlasSensor.prototype.cmd = function(cmd, argOrArgsOrCallback, callback) {
      this.cmdEvtQueue.push([cmd, argOrArgsOrCallback, callback]);
      if (!this.currCmdEvt) {
        this.currCmdEvt = this.cmdEvtQueue.splice(0,1)[0];
        this._cmd.apply(this, this.currCmdEvt);
      } 
    };
    
    AtlasSensor.prototype.getLastReading = function(cmd) {
      var cmdSpec = this._getCmdSpec(cmd);
      return ((cmdSpec) 
        ? this._getData(cmdSpec, STATE.SUCCESS)
        : { state: STATE.INVALID_CMD }
        );
    };
    
    AtlasSensor.prototype._cmd = function(cmd, argOrArgsOrCallback, callback) {
      var cmdSpec = this._getCmdSpec(cmd), arg, cb, args = [], cmdStr;
      if ("function" === typeof (arg = argOrArgsOrCallback)) {
        cb = arg;
      } else if ("array" === typeof arg) {
        args = arg;
      } else if ((arg != undefined) && (arg !== null)) {
        args = [arg];
      };
      cb = (cb) ? cb : callback;
      if (cmdSpec) {
        cmdStr = cmdSpec[0];
        args.forEach(function(arg){ cmdStr += "," + arg; });
        this.i2c.writeTo(this.addr, this.lastCmdStr = cmdStr);
        setTimeout(
            this._read.bind(this) // deferred function
          , cmdSpec[3] // defer time
          , cmdSpec, cb // params
          );
      } else { // invalid command
        this.lastCmdTs = getTime();
        this.lastCmdState = STATE.INVALID_CMD;
        this.lastRawData = null;
        if (cb) {
          setTimeout(
              cb
            , 1
            , { state: this.lastCmdState }
            );
        }
        this._nextCmd();
      }
    };
    
    AtlasSensor.prototype._read = function(cmdSpec, callback) {
      var reading = this._readRaw();
      if (reading.state === STATE.SUCCESS) {
        cmdSpec[2].bind(this)(reading.data);
        reading = this._getData(cmdSpec, STATE.SUCCESS);
      }
      this._nextCmd();
      if (callback) { callback(reading); }
    };
    
    AtlasSensor.prototype._nullProps = function(cmdSpec) {
      cmdSpec[1].forEach(function(prop){ this[prop] = null; }, this);
    };
    
    AtlasSensor.prototype._getData = function(cmdSpec, state) {
      var data = { state: state };
      if (cmdSpec) {
        cmdSpec[1].forEach(function(prop){
            data[prop] = this[prop];
          }, this);
      }
      return data;
    };
    
    AtlasSensor.prototype._getCmdSpec = function(cmd) { // get cmdSpec by cmd
      var cmdSpec, cmdSpecs = this.descr.cmdSpecs, 
        idx = 0, idxMax = cmdSpecs.length - 1;
      while (idx <= idxMax) {
        if ((cmdSpec = cmdSpecs[idx])[0] === cmd) { return cmdSpec; }
        idx++;
      }
      return null;
    };
    
    AtlasSensor.prototype._nextCmd = function() {
      if (this.cmdEvtQueue.length > 0) {
        this.currCmdEvt = this.cmdEvtQueue.splice(0,1)[0];
        this._cmd.apply(this, this.currCmdEvt);
      } else {
        this.currCmdEvt = null;
      } 
    }
    
    AtlasSensor.prototype._readRaw = function() {
      var state = -1; // init read status
      var data = null; // init read data
      var byte; // read buffer
      byte = this.i2c.readFrom(this.addr,1)[0]; // read first byte (state)
      if ((state = byte) === STATE.SUCCESS) { // success of command
        data = "";
        byte = this.i2c.readFrom(this.addr,1)[0];
        while (byte !== 0) { // data, not null / ending yet
          data += String.fromCharCode(byte);
          byte = this.i2c.readFrom(this.addr,1)[0];
        }
      } else { // anything else then success
        this.i2c.readFrom(this.addr,1); // consume ending null
        // just let the state pass through
      }
      this.lastCmdState = state;
      this.lastRawData = data;
      return { state: state, data: data };
    };
    
    // *** Test Code
    var tempSensor;
    function init() {
      tempSensor = new AtlasSensor("temp1", tempI2CEmulator, 1, tempDescr);
    }
    function logStatus() {
      tempSensor.cmd("STATUS",function(reading­){
          console.log(reading);
        });
    }
    function readTemp() {
      tempSensor.cmd("R",function(reading){
          console.log(reading);
        });
    }
    

    Executed as part of the test code in Espruino html emulation, the delay of 0.300 seconds in the response is clearly noticeable.

    The emulation code (embedded into html to run in browser):

    <html><head><title>atlas temp sensor emulation / test</title></head>
    <body>
    <script>
    
    // ============ Atlas Sensor code and test code goes here ========
    
    // *** Espruino Emulation / Test Code
    function getTime() { return new Date().getTime() / 1000; }
    
    // *** Atlas Temp Sensor Emulation Code
    var tempI2CEmulator =
    { state: 1 // success
    , cmd: null
    , cmdTs: 0
    , NO_DATA: String.fromCharCode(STATE.NO_DATA) + String.fromCharCode(0)
    , PENDING: String.fromCharCode(STATE.PENDING) + String.fromCharCode(0)
    , readTs: 0
    , readIdx: -1
    , readProperty: null
    , R: String.fromCharCode(1) + "70.25" + String.fromCharCode(0), R_time: 0.300 
    , STATUS: String.fromCharCode(1) + "?STATUS,P,5.038" + String.fromCharCode(0), STATUS_time: 0.300
    , writeTo: function(addr, cmdStr) {
        console.log(cmdStr);
        this.cmdTs = getTime();
        var cmdElts =  cmdStr.split(",");
        this.cmd = cmdElts[0];
        this.readTs = this.cmdTs + this[this.cmd + "_time"];
        this.readIdx = -1;
        this.readProperty = this[this.cmd];
      }
    , readFrom(addr, byteCount) {
        if (this.readProperty === null) {
          this.readProperty = this.NO_DATA;
        } else {
          if (    (this.readIdx === -1) 
               && (getTime() < this.readTs  ) ) {
            readProperty = this.PENDING;
          }
        }
        this.readIdx++;
        var byte = this.readProperty.charCodeAt(this.readId­x);
        if (byte === 0) {
          this.cmd = null;
          this.cmdTs = 0;
          this.readTs = 0;
          this.readIdx = -1;
          this.readProperty = null;
        }
        return [byte];
      }
    };
    // *** /Atlas Temp Sensor Emulation Code
    
    </script>
    <h3>atlas temp sensor emulation / test</h3>
    <button onclick='init()'>init()</button>
    <button onclick='logStatus()'>logStatus()</butto­n>
    <button onclick='readTemp()'>readTemp()</button>­
    </body>
    </html>
    

    Now it is your turn to add the calibration command implementation...

    You may have noticed that all requests go into a queue to make sure the writes and reads are nicely paired. This queuing allows to place multiple requests in a row without having to worry about timings.

    For cascading and combination with other sensors that need values from other sensors, callback implementation gets cumbersome; a promise implementation would look much cleaner.


    2 Attachments

About

Avatar for allObjects @allObjects started