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 }