• Well, that took a long time - I'd been hoping to use the Arduino code as a base but something just seemed a bit 'off' with it. Ended up using https://github.com/pimylifeup/MFRC522-py­thon as the datasheet itself doesn't really seem to provide enough info by itself.

    Ok, this is a bit hacky at the moment - I need to turn it into a proper module that works the same way as the old one, but:

    var exports = {};
    
    /* Copyright (c) 2019 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. */
    // based on https://github.com/pimylifeup/MFRC522-py­thon
    exports.connect = function(spi, cs) {
      var m = new MFRC522(spi, cs);
      m.init();
      return m;
    };
    
    MI_OK = "ok";
    MI_ERR = "err";
    
    var PCD = {
        IDLE : 0x00,
        AUTHENT : 0x0E,
        RECEIVE : 0x08,
        TRANSMIT : 0x04,
        TRANSCEIVE : 0x0C,
        RESETPHASE : 0x0F,
        CALCCRC : 0x03
    };
    
    var PICC = {
        REQIDL : 0x26,
        REQALL : 0x52,
        ANTICOLL : 0x93,
        SElECTTAG : 0x93,
        AUTHENT1A : 0x60,
        AUTHENT1B : 0x61,
        READ : 0x30,
        WRITE : 0xA0,
        DECREMENT : 0xC0,
        INCREMENT : 0xC1,
        RESTORE : 0xC2,
        TRANSFER : 0xB0,
        HALT : 0x50
    };
    
    var R = {
      Command:2,
      CommIEn:4,
      DivlEn:6,
      CommIrq:8,
      DivIrq:10,
      Error:12,
      Status1:14,
      Status2:16,
      FIFOData:18,
      FIFOLevel:20,
      WaterLevel:22,
      Control:24,
      BitFraming:26,
      Coll:28,
      Mode:34,
      TxMode:36,
      RxMode:38,
      TxControl:40,
      TxAuto:42,
      TxSel:44,
      RxSel:46,
      RxThreshold:48,
      Demod:50,
      Mifare:56,
      SerialSpeed:62,
      CRCResultH:66,
      CRCResultL:68,
      ModWidth:72,
      RFCfg:76,
      GsN:78,
      CWGsP:80,
      ModGsP:82,
      TMode:84,
      TPrescaler:86,
      TReloadH:88,
      TReloadL:90,
      TCounterValueH:92,
      TCounterValueL:94
    };
    
    function MFRC522(spi, cs) {
      this.spi = spi;
      this.cs = cs;
    }
    
    /// The access key to use - this is set to the default for mifare tags 
    MFRC522.prototype.KEY = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF];
    
    /// read from a register
    MFRC522.prototype.r = function(addr) {
      if (!addr) throw new Error("invalid register");
      return this.spi.send([addr|0x80,0], this.cs)[1];
    };
    /// multiple read from a register
    MFRC522.prototype.ra = function(addr, c) {
      if (!addr) throw new Error("invalid register");
      var a = new Uint8Array(c+1);
      a.fill(addr|0x80);
      a[c]=0;
      return this.spi.send(a, this.cs).slice(1);
    };
    /// write to a register
    MFRC522.prototype.w = function(addr,data) {
      if (!addr) throw new Error("invalid register");
      if (data===undefined) throw new Error("invalid data");
      this.spi.write(addr, data, this.cs);
    };
    /// turn antenna on/off
    MFRC522.prototype.antenna = function(on) {
      if (on) this.w(R.TxControl, this.r(R.TxControl) | 0x03);
      else this.w(R.TxControl, this.r(R.TxControl) & ~0x03);
    };
    /// reset and initialise the MFRC522 - this is done automatically when `connect` is called
    MFRC522.prototype.init = function() {
      // soft reset
      this.w(R.Command, PCD.RESETPHASE);
      var timeout=50;
      while (--timeout && (this.r(R.Command) & 16));
      if (!timeout) throw new Error("SW Timeout");
      // setup
      this.w(R.TMode, 0x8D);
      this.w(R.TPrescaler, 0x3E);
      this.w(R.TReloadH, 0);
      this.w(R.TReloadL, 30);
      this.w(R.TxAuto, 0x40);
      this.w(R.Mode, 0x3D);
      this.antenna(1);
    };
    
    
    
    /// returns {status, data, bitCount}
    MFRC522.prototype.MFRC522_ToCard = function(command, sendData) {
      //console.log("MFRC522_ToCard ",arguments);
      var backData = [];
      var backLen = 0;
      var status = MI_ERR;
      var irqEn = 0x00;
      var waitIRq = 0x00;
      var lastBits;
    
      if (command == PCD.AUTHENT) {
          irqEn = 0x12;
          waitIRq = 0x10;
      }
      if (command == PCD.TRANSCEIVE) {
          irqEn = 0x77;
          waitIRq = 0x30;
      }
      
      this.w(R.Command, PCD.IDLE);//g
      this.w(R.CommIEn, irqEn | 0x80);
      //this.w(R.CommIrq, 0x7F); // g clear IRQs
      this.w(R.CommIrq, this.r(R.CommIrq)&~0x80);
      this.w(R.FIFOLevel, 0x80); // g clear fifo
      //this.w(R.FIFOLevel, this.r(R.FIFOLevel)|0x80);
    
      //this.w(R.Command, PCD.IDLE);
      this.w(R.FIFOData, sendData);
    
      this.w(R.Command, command);
    
      if (command == PCD.TRANSCEIVE)
        this.w(R.BitFraming, this.r(R.BitFraming)|0x80);
    
      var timeout = 500;
      var n;
      while (--timeout) {
        n = this.r(R.CommIrq);
        if (n&waitIRq) break;
        if (n&1) throw new Error("HW Timeout");
      }
      if (!timeout) throw new Error("SW Timeout "+n);
      this.w(R.BitFraming, this.r(R.BitFraming)&~0x80);
      if ((this.r(R.Error) & 0x1B) == 0x00) {
        status = MI_OK;
        if (command == PCD.TRANSCEIVE) {
          n = this.r(R.FIFOLevel);
          lastBits = this.r(R.Control) & 0x07;
          if (lastBits)
              backLen = (n - 1) * 8 + lastBits;
          else
              backLen = n * 8;
          if (n == 0) n = 1;
          if (n > 16) n = 16;
          backData = this.ra(R.FIFOData, n);
        } 
      } else status = MI_ERR;
    
      var r = {status:status, data:backData, bitCount:backLen};
    
      //console.log("MFRC522_ToCard > ",r);
      return r;
    }
    
    
    // returns {status,tagType}
    MFRC522.prototype.MFRC522_Request = function(reqMode) {
      var TagType = [];
    
      this.w(R.BitFraming, 0x07);
    
      TagType.push(reqMode);
      var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, TagType);
      if ((r.status != MI_OK) | (r.bitCount != 0x10))
          r.status = MI_ERR;
    
      //console.log("MFRC522_Request > "+r.status);
      return { status : r.status, tagType : r.bitCount };
    };
    
    
    MFRC522.prototype.MFRC522_Anticoll = function() {
      
      this.w(R.BitFraming, 0x00);
      var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, [PICC.ANTICOLL,0x20]);
    
      if (r.status == MI_OK) { 
        if (r.data.length == 5) {
          var serNumCheck = 0;
          for (var i=0;i<4;i++)
            serNumCheck = serNumCheck ^ r.data[i];
          if (serNumCheck != r.data[4])
            r.status = MI_ERR;
        }
      } else
        r.status = MI_ERR;
      var r = {status:r.status, uid:r.data};
      //console.log("MFRC522_Anticoll > ",r);
      return r;
    };
    
    /// Given data returns a 2 byte array of the CRC
    MFRC522.prototype.CalulateCRC = function(pIndata) {
      this.w(R.DivIrq, this.r(R.DivIrq)&~0x04);
      this.w(R.FIFOLevel, this.r(R.FIFOLevel)|0x80);
      this.w(R.FIFOData, pIndata);
    
      this.w(R.Command, PCD.CALCCRC);
      var timeout = 200;
      var n;
      while (--timeout) {
        n = this.r(R.DivIrq);
        if (n&4) break;
        if (n&1) throw new Error("HW Timeout");
      }
      if (!timeout) throw new Error("SW Timeout "+n);  
      return [this.r(R.CRCResultL), this.r(R.CRCResultH)];
    };
    
    /// Select the tag - returns the size?
    MFRC522.prototype.MFRC522_SelectTag = function(serNum) {
        var backData = [];
        var buf = [PICC.SElECTTAG, 0x70];
        for (var i=0;i<5;i++)
          buf.push(serNum[i]);
        var pOut = this.CalulateCRC(buf);
        buf.push(pOut[0]);
        buf.push(pOut[1]);
        var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, buf);
        if ((r.status == MI_OK) && (r.bitCount == 0x18)) {
            return r.data[0];
        }
    };
    
    MFRC522.prototype.MFRC522_Auth = function(authMode, BlockAddr, Sectorkey, serNum) {
      var buff = [];
    
      // First byte should be the authMode (A or B)
      buff.push(authMode);
    
      // Second byte is the trailerBlock (usually 7)
      buff.push(BlockAddr);
    
      // Now we need to.push the authKey which usually is 6 bytes of 0xFF
      for (var i=0;i<Sectorkey.length;i++)
          buff.push(Sectorkey[i]);
    
      // Next we.push the first 4 bytes of the UID
      for (var i=0;i<4;i++)
          buff.push(serNum[i]);
    
      // Now we start the authentication itself
      var r = this.MFRC522_ToCard(PCD.AUTHENT, buff);
    
      // Check if an error occurred
      if (r.status != MI_OK)
          throw new Error("AUTH ERROR!!");
      if ((this.r(R.Status2) & 0x08) == 0)
          throw new Error("AUTH ERROR(status2reg & 0x08) != 0");
    
      // Return the status
      return {status:r.status};
    };
    MFRC522.prototype.MFRC522_StopCrypto1 = function() {
      this.w(R.Status2, this.r(R.Status2)&~0x08);
    };
    
    /// Read the given block address, return the data
    MFRC522.prototype.MFRC522_Read = function(blockAddr) {
      var recvData = [];
      recvData.push(PICC.READ);
      recvData.push(blockAddr);
      pOut = this.CalulateCRC(recvData);
      recvData.push(pOut[0]);
      recvData.push(pOut[1]);
      var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, recvData);
      if (r.status != MI_OK)
          throw new Error("Error while reading!");
      if (r.data.length == 16) {
          return r.data;
      }
    };
    
    /// Write the given block address
    MFRC522.prototype.MFRC522_Write = function(blockAddr, writeData) {
      var buf = [PICC.WRITE,blockAddr];
      crc = this.CalulateCRC(buf);
      buf = buf.concat(crc);
      var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, buf);
      if ((r.status != MI_OK) || (r.bitCount != 4) || ((r.data[0] & 0x0F) != 0x0A))
        r.status = MI_ERR;
      //console.log(r.bitCount,"backdata &0x0F == 0x0A ",r.data[0] & 0x0F);
      if (r.status == MI_OK) {
        buf = [];
        // pad with 0s
        for (var i=0;i<16;i++)
          buf.push(0|writeData[i]);
        crc = this.CalulateCRC(buf);
        buf = buf.concat(crc);
        r = this.MFRC522_ToCard(PCD.TRANSCEIVE, buf);
        if ((r.status != MI_OK) || (r.bitCount != 4) || ((r.data[0] & 0x0F) != 0x0A))
          throw new Error("Error while writing");
      }
      return {status:r.status};
    };
    
    
    MFRC522.prototype.read = function(blockAddr) {
      var r = this.MFRC522_Request(PICC.REQIDL);
      if (r.status != MI_OK)
          return;
      r = this.MFRC522_Anticoll();
      if (r.status != MI_OK)
          throw new Error("Anticoll failed");
      this.MFRC522_SelectTag(r.uid);
      r = this.MFRC522_Auth(PICC.AUTHENT1A, 11, this.KEY, r.uid);
      if (r.status != MI_OK)
          throw new Error("Auth failed");
      var data = this.MFRC522_Read(blockAddr);
      nfc.MFRC522_StopCrypto1();
      return data;
    };
    
    MFRC522.prototype.write = function(blockAddr, data) {
      var r = this.MFRC522_Request(PICC.REQIDL);
      if (r.status != MI_OK)
          return;
      r = this.MFRC522_Anticoll();
      if (r.status != MI_OK)
          throw new Error("Anticoll failed");
      this.MFRC522_SelectTag(r.uid);
      r = this.MFRC522_Auth(PICC.AUTHENT1A, 11, this.KEY, r.uid);
      if (r.status != MI_OK)
          throw new Error("Auth failed");
      
      data = E.toUint8Array(data);
      blockAddr = 0|blockAddr;
      var blocks = (data.length+15)>>4;
      for (var b=0;b<blocks;b++) {
        r = nfc.MFRC522_Write(blockAddr+b, new Uint8Array(data.buffer, b*16, 16));
        if (r.status != MI_OK)
          throw new Error("Write to block "+(blockAddr+b)+" failed");
      }  
      nfc.MFRC522_StopCrypto1();
      return r.status;
    };
    
    var spi = new SPI();
    spi.setup({sck:A6, miso:B1, mosi:A7 });
    var nfc = exports.connect(spi, A5/*CS*/);
    nfc.write(8,"This is a test")
    //="ok"
    print(JSON.stringify(E.toString(nfc.read­(8))));
    //="This is a test\0\0"
    

    So using the standard reader and tags, it seems you can only really do stuff with pages 8 and over, which are 16 bytes each?

    Be careful - I think I 'bricked' a tag by writing lots to it and maybe overwriting the 'auth key' with something else. Definitely stick to pages 8/9/0 and 16 bytes each and you're fine though.

    Also, there will be exceptions thrown every so often - sometimes a read will fail and you'll get a "HW Timeout" - but it's easy enough to catch it.

About

Avatar for Gordon @Gordon started