gobot.io/x/gobot/v2@v2.1.0/drivers/common/mfrc522/mfrc522_picc.go (about)

     1  package mfrc522
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  const piccDebug = false
     8  
     9  // Commands sent to the PICC, used by the PCD to for communication with several PICCs (ISO 14443-3, Type A, section 6.4)
    10  const (
    11  	// Activation
    12  	piccCommandRequestA = 0x26 // REQuest command type A, 7 bit frame, invites PICCs in state IDLE to go to READY
    13  	piccCommandWakeUpA  = 0x52 // Wake-UP command type A, 7 bit frame, invites PICCs in state IDLE and HALT to go to READY
    14  	// Anticollision and SAK
    15  	piccCommandCascadeLevel1 = 0x93 // Select cascade level 1
    16  	piccCommandCascadeLevel2 = 0x95 // Select cascade Level 2
    17  	piccCommandCascadeLevel3 = 0x97 // Select cascade Level 3
    18  	piccCascadeTag           = 0x88 // Cascade tag is used during anti collision
    19  	piccUIDNotComplete       = 0x04 // used on SAK call
    20  	// Halt
    21  	piccCommandHLTA = 0x50 // Halt command, Type A. Instructs an active PICC to go to state HALT.
    22  	piccCommandRATS = 0xE0 // Request command for Answer To Reset.
    23  	// The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9)
    24  	// Use MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on
    25  	// the sector. The read/write commands can also be used for MIFARE Ultralight.
    26  	piccCommandMFRegAUTHRegKEYRegA = 0x60 // Perform authentication with Key A
    27  	piccCommandMFRegAUTHRegKEYRegB = 0x61 // Perform authentication with Key B
    28  	// Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight.
    29  	piccCommandMFRegREAD = 0x30
    30  	// Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight.
    31  	piccCommandMFRegWRITE     = 0xA0
    32  	piccWriteAck              = 0x0A // MIFARE Classic: 4 bit ACK, we use any other value as NAK (data sheet: 0h to 9h, Bh to Fh)
    33  	piccCommandMFRegDECREMENT = 0xC0 // Decrements the contents of a block and stores the result in the internal data register.
    34  	piccCommandMFRegINCREMENT = 0xC1 // Increments the contents of a block and stores the result in the internal data register.
    35  	piccCommandMFRegRESTORE   = 0xC2 // Reads the contents of a block into the internal data register.
    36  	piccCommandMFRegTRANSFER  = 0xB0 // Writes the contents of the internal data register to a block.
    37  	// The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/dataRegsheet/MF0ICU1.pdf, Section 8.6)
    38  	// The piccCommandMFRegREAD and piccCommandMFRegWRITE can also be used for MIFARE Ultralight.
    39  	piccCommandULRegWRITE = 0xA2 // Writes one 4 byte page to the PICC.
    40  )
    41  
    42  const piccReadWriteAuthBlock = uint8(11)
    43  
    44  var piccKey = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
    45  var piccUserBlockAddresses = []byte{8, 9, 10}
    46  
    47  var piccCardFromSak = map[uint8]string{0x08: "Classic 1K, Plus 2K-SE-1K(SL1)", 0x18: "Classic 4K, Plus 4K(SL1)",
    48  	0x10: "Plus 2K(SL2)", 0x11: "Plus 4K(SL2)", 0x20: "Plus 2K-SE-1K(SL3), Plus 4K(SL3)"}
    49  
    50  // IsCardPresent is used to poll for a card in range. After an successful request, the card is halted.
    51  func (d *MFRC522Common) IsCardPresent() error {
    52  	d.firstCardAccess = true
    53  
    54  	if err := d.writeByteData(regTxMode, rxtxModeRegReset); err != nil {
    55  		return err
    56  	}
    57  	if err := d.writeByteData(regRxMode, rxtxModeRegReset); err != nil {
    58  		return err
    59  	}
    60  	if err := d.writeByteData(regModWidth, modWidthRegReset); err != nil {
    61  		return err
    62  	}
    63  
    64  	answer := []byte{0x00, 0x00} // also called ATQA
    65  	if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
    66  		return err
    67  	}
    68  
    69  	if piccDebug {
    70  		fmt.Printf("Card found: %v\n\n", answer)
    71  	}
    72  	if err := d.piccHalt(); err != nil {
    73  		return err
    74  	}
    75  	return nil
    76  }
    77  
    78  // ReadText reads a card with the dedicated workflow: REQA, Activate, Perform Transaction, Halt/Deselect.
    79  // see "Card Polling" in https://www.nxp.com/docs/en/application-note/AN10834.pdf.
    80  // and return the result as text string.
    81  // TODO: make this more usable, e.g. by given length of text
    82  func (d *MFRC522Common) ReadText() (string, error) {
    83  	answer := []byte{0x00, 0x00}
    84  	if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
    85  		return "", err
    86  	}
    87  
    88  	uid, err := d.piccActivate()
    89  	if err != nil {
    90  		return "", err
    91  	}
    92  
    93  	if piccDebug {
    94  		fmt.Printf("uid: %v\n", uid)
    95  	}
    96  
    97  	if err := d.piccAuthenticate(piccReadWriteAuthBlock, piccKey, uid); err != nil {
    98  		if piccDebug {
    99  			fmt.Println("authenticate failed for address", piccReadWriteAuthBlock)
   100  		}
   101  		return "", err
   102  	}
   103  
   104  	var content []byte
   105  	for _, block := range piccUserBlockAddresses {
   106  		blockData, err := d.piccRead(block)
   107  		if err != nil {
   108  			if piccDebug {
   109  				fmt.Println("read failed at block", block)
   110  			}
   111  			return "", err
   112  		}
   113  		content = append(content, blockData...)
   114  	}
   115  	if piccDebug {
   116  		fmt.Println("content:", string(content[:]), content)
   117  	}
   118  
   119  	if err := d.piccHalt(); err != nil {
   120  		return "", err
   121  	}
   122  
   123  	return string(content[:]), d.stopCrypto1()
   124  }
   125  
   126  // WriteText writes the given string to the card. All old values will be overwritten.
   127  func (d *MFRC522Common) WriteText(text string) error {
   128  	answer := []byte{0x00, 0x00}
   129  	if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
   130  		return err
   131  	}
   132  
   133  	uid, err := d.piccActivate()
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	if piccDebug {
   139  		fmt.Printf("uid: %v\n", uid)
   140  	}
   141  
   142  	if err := d.piccAuthenticate(piccReadWriteAuthBlock, piccKey, uid); err != nil {
   143  		if piccDebug {
   144  			fmt.Println("authenticate failed for address", piccReadWriteAuthBlock)
   145  		}
   146  		return err
   147  	}
   148  
   149  	// prepare data with text and trailing zero's
   150  	textData := append([]byte(text), make([]byte, len(piccUserBlockAddresses)*16)...)
   151  
   152  	for i, blockNum := range piccUserBlockAddresses {
   153  		blockData := textData[i*16 : (i+1)*16]
   154  		err := d.piccWrite(blockNum, blockData)
   155  		if err != nil {
   156  			if piccDebug {
   157  				fmt.Println("write failed at block", blockNum)
   158  			}
   159  			return err
   160  		}
   161  	}
   162  
   163  	if err := d.piccHalt(); err != nil {
   164  		return err
   165  	}
   166  
   167  	return d.stopCrypto1()
   168  }
   169  
   170  func (d *MFRC522Common) piccHalt() error {
   171  	if piccDebug {
   172  		fmt.Println("-halt-")
   173  	}
   174  	haltCommand := []byte{piccCommandHLTA, 0x00}
   175  	crcResult := []byte{0x00, 0x00}
   176  	if err := d.calculateCRC(haltCommand, crcResult); err != nil {
   177  		return err
   178  	}
   179  	haltCommand = append(haltCommand, crcResult...)
   180  
   181  	txLastBits := uint8(0x00) // we use all 8 bits
   182  	if err := d.communicateWithPICC(commandRegTransceive, haltCommand, []byte{}, txLastBits, false); err != nil {
   183  		// an error is the sign for successful halt
   184  		if piccDebug {
   185  			fmt.Println("this is not treated as error:", err)
   186  		}
   187  		return nil
   188  	}
   189  
   190  	return fmt.Errorf("something went wrong with halt")
   191  }
   192  
   193  func (d *MFRC522Common) piccWrite(block uint8, blockData []byte) error {
   194  	if piccDebug {
   195  		fmt.Println("-write-")
   196  		fmt.Println("blockData:", blockData, len(blockData))
   197  	}
   198  	if len(blockData) != 16 {
   199  		return fmt.Errorf("the block to write needs to be exactly 16 bytes long, but has %d bytes", len(blockData))
   200  	}
   201  	// MIFARE Classic protocol requires two steps to perform a write.
   202  	// Step 1: Tell the PICC we want to write to block blockAddr.
   203  	writeDataCommand := []byte{piccCommandMFRegWRITE, block}
   204  	crcResult := []byte{0x00, 0x00}
   205  	if err := d.calculateCRC(writeDataCommand, crcResult); err != nil {
   206  		return err
   207  	}
   208  	writeDataCommand = append(writeDataCommand, crcResult...)
   209  
   210  	txLastBits := uint8(0x00) // we use all 8 bits
   211  	backData := make([]byte, 1)
   212  	if err := d.communicateWithPICC(commandRegTransceive, writeDataCommand, backData, txLastBits, false); err != nil {
   213  		return err
   214  	}
   215  	if backData[0]&piccWriteAck != piccWriteAck {
   216  		return fmt.Errorf("preparation of write on MIFARE classic failed (%v)", backData)
   217  	}
   218  	if piccDebug {
   219  		fmt.Println("backData", backData)
   220  	}
   221  
   222  	// Step 2: Transfer the data
   223  	if err := d.calculateCRC(blockData, crcResult); err != nil {
   224  		return err
   225  	}
   226  
   227  	var writeData []byte
   228  	writeData = append(writeData, blockData...)
   229  	writeData = append(writeData, crcResult...)
   230  	if err := d.communicateWithPICC(commandRegTransceive, writeData, []byte{}, txLastBits, false); err != nil {
   231  		return err
   232  	}
   233  
   234  	return nil
   235  }
   236  
   237  func (d *MFRC522Common) piccRead(block uint8) ([]byte, error) {
   238  	if piccDebug {
   239  		fmt.Println("-read-")
   240  	}
   241  	readDataCommand := []byte{piccCommandMFRegREAD, block}
   242  	crcResult := []byte{0x00, 0x00}
   243  	if err := d.calculateCRC(readDataCommand, crcResult); err != nil {
   244  		return nil, err
   245  	}
   246  	readDataCommand = append(readDataCommand, crcResult...)
   247  
   248  	txLastBits := uint8(0x00)    // we use all 8 bits
   249  	backData := make([]byte, 18) // 16 data byte and 2 byte CRC
   250  	if err := d.communicateWithPICC(commandRegTransceive, readDataCommand, backData, txLastBits, true); err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	return backData[:16], nil
   255  }
   256  
   257  func (d *MFRC522Common) piccAuthenticate(address uint8, key []byte, uid []byte) error {
   258  	if piccDebug {
   259  		fmt.Println("-authenticate-")
   260  	}
   261  
   262  	buf := []byte{piccCommandMFRegAUTHRegKEYRegA, address}
   263  	buf = append(buf, key...)
   264  	buf = append(buf, uid...)
   265  
   266  	if err := d.communicateWithPICC(commandRegMFAuthent, buf, []byte{}, 0, false); err != nil {
   267  		return err
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  // activate a card with the dedicated workflow: Anticollision and optional Request for "Answer To Select" (RATS) and
   274  // "Protocol Parameter Selection" (PPS).
   275  // see "Card Activation" in https://www.nxp.com/docs/en/application-note/AN10834.pdf.
   276  // note: the card needs to be in ready state, e.g. by a request or wake up is done before
   277  func (d *MFRC522Common) piccActivate() ([]byte, error) {
   278  	if err := d.clearRegisterBitMask(regColl, collRegValuesAfterCollBit); err != nil {
   279  		return nil, err
   280  	}
   281  	if err := d.writeByteData(regBitFraming, bitFramingRegReset); err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	// start cascade level 1 (0x93) for return:
   286  	// * one size UID (4 byte): UID0..3 and one byte BCC or
   287  	// * cascade tag (0x88) and UID0..2 and BCC
   288  	// in the latter case the UID is incomplete and the next cascade level needs to be started.
   289  	// cascade level 2 (0x95) return:
   290  	// * double size UID (7 byte): UID3..6 and one byte BCC or
   291  	// * cascade tag (0x88) and UID3..5 and BCC
   292  	// cascade level 3 (0x97) return:
   293  	// * triple size UID (10 byte): UID6..9
   294  	// after each anticollision check (request of next UID) the SAK needs to be done (same level command)
   295  	// BCC: Block Check Character
   296  	// SAK: Select Acknowledge
   297  
   298  	var uid []byte
   299  	var sak uint8
   300  
   301  	for cascadeLevel := 1; cascadeLevel < 3; cascadeLevel++ {
   302  		var piccCommand uint8
   303  		switch cascadeLevel {
   304  		case 1:
   305  			piccCommand = piccCommandCascadeLevel1
   306  		case 2:
   307  			piccCommand = piccCommandCascadeLevel2
   308  		case 3:
   309  			piccCommand = piccCommandCascadeLevel3
   310  		default:
   311  			return nil, fmt.Errorf("unknown cascade level %d", cascadeLevel)
   312  		}
   313  
   314  		if piccDebug {
   315  			fmt.Println("-anti collision-")
   316  		}
   317  
   318  		txLastBits := uint8(0x00) // we use all 8 bits
   319  		numValidBits := uint8(4 * 8)
   320  		sendForAnticol := []byte{piccCommand, numValidBits}
   321  		backData := []byte{0x00, 0x00, 0x00, 0x00, 0x00} // 4 bytes CT/UID and BCC
   322  		if err := d.communicateWithPICC(commandRegTransceive, sendForAnticol, backData, txLastBits, false); err != nil {
   323  			return nil, err
   324  		}
   325  
   326  		// TODO: no real anticollision check yet
   327  
   328  		// check BCC
   329  		bcc := byte(0)
   330  		for _, v := range backData[:4] {
   331  			bcc = bcc ^ v
   332  		}
   333  		if bcc != backData[4] {
   334  			return nil, fmt.Errorf(fmt.Sprintf("BCC mismatch, expected %02x actual %02x", bcc, backData[4]))
   335  		}
   336  
   337  		if backData[0] == piccCascadeTag {
   338  			uid = append(uid, backData[1:3]...)
   339  			if piccDebug {
   340  				fmt.Printf("next cascade is needed after SAK, uid: %v", uid)
   341  			}
   342  		} else {
   343  			uid = append(uid, backData[:4]...)
   344  			if piccDebug {
   345  				fmt.Printf("backData: %v, uid: %v\n", backData, uid)
   346  			}
   347  		}
   348  
   349  		if piccDebug {
   350  			fmt.Println("-select acknowledge-")
   351  		}
   352  		sendCommand := []byte{piccCommand}
   353  		sendCommand = append(sendCommand, 0x70)        // 7 bytes
   354  		sendCommand = append(sendCommand, backData...) // uid including BCC
   355  		crcResult := []byte{0x00, 0x00}
   356  		if err := d.calculateCRC(sendCommand, crcResult); err != nil {
   357  			return uid, err
   358  		}
   359  		sendCommand = append(sendCommand, crcResult...)
   360  		sakData := []byte{0x00, 0x00, 0x00}
   361  		if err := d.communicateWithPICC(commandRegTransceive, sendCommand, sakData, txLastBits, false); err != nil {
   362  			return nil, err
   363  		}
   364  		bcc = byte(0)
   365  		for _, v := range sakData[:2] {
   366  			bcc = bcc ^ v
   367  		}
   368  		if piccDebug {
   369  			fmt.Printf("sak data: %v\n", sakData)
   370  		}
   371  		if sakData[0] != piccUIDNotComplete {
   372  			sak = sakData[0]
   373  			break
   374  		}
   375  		if piccDebug {
   376  			fmt.Printf("next cascade called, SAK: %v\n", sakData[0])
   377  		}
   378  	}
   379  
   380  	if piccDebug || d.firstCardAccess {
   381  		d.firstCardAccess = false
   382  		fmt.Printf("card '%s' selected\n", piccCardFromSak[sak])
   383  	}
   384  	return uid, nil
   385  }
   386  
   387  func (d *MFRC522Common) piccRequest(reqMode uint8, answer []byte) error {
   388  	if len(answer) < 2 {
   389  		return fmt.Errorf("at least 2 bytes room needed for the answer")
   390  	}
   391  
   392  	if err := d.clearRegisterBitMask(regColl, collRegValuesAfterCollBit); err != nil {
   393  		return err
   394  	}
   395  
   396  	// for request A and wake up the short frame format is used - transmit only 7 bits of the last (and only) byte.
   397  	txLastBits := uint8(0x07 & bitFramingRegTxLastBits)
   398  	if err := d.communicateWithPICC(commandRegTransceive, []byte{reqMode}, answer, txLastBits, false); err != nil {
   399  		return err
   400  	}
   401  
   402  	return nil
   403  }