Web浏览器读写NFC Ntag213、215、216 标签_Ntag

 本示例使用的发卡器:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Web Serial串口读写器Ntag卡示例 </title>
	<script>		
		window.onload = function() {
   			document.getElementById('butt_openserial').hidden=true;
			document.getElementById('butt_closeserial').hidden=true;
			document.getElementById('dispauthkey').hidden=true;
			document.getElementById('authkey0').hidden=true;
		}
		
		if ('serial' in navigator){
			
		}else{
			alert('您的浏览器不支持 Web Serial API,暂无法使用以下功能!');
		}		
		
		navigator.serial.onconnect =function event(){
			console.log("Serial port connected: ", event.target);
		}
		
		navigator.serial.ondisconnect =function event(){
			console.log("Serial port disconnected: ", event.target);
		}
		
		var BLOCK0_EN = 0x01;//读第一块的(16个字节)
        var BLOCK1_EN = 0x02;//读第二块的(16个字节)
        var BLOCK2_EN = 0x04;//读第三块的(16个字节)
        var NEEDSERIAL = 0x08;//仅读指定序列号的卡
        var EXTERNKEY = 0x10;//用明码认证密码,产品开发完成后,建议把密码放到设备的只写区,然后用该区的密码后台认证,这样谁都不知道密码是多少,需要这方面支持请联系
        var NEEDHALT = 0x20; //读/写完卡后立即休眠该卡,相当于这张卡不在感应区。要相重新操作该卡必要拿开卡再放上去
						
		var port = null;
        var reader = null;
        var reading = false;
        const getdata=new Uint8Array(1000);     //接收串口返回的数据
        var DataPoint=0;                        //接收数据指针
        var SendCode=0;                         //已发送的指令代码

        function isUIntNum(val) {
            var testval = /^\d+$/; // 非负整数
            return (testval.test(val));
        }

        function isHex(val) {
            var testval = /^(\d|[A-F]|[a-f])+$/; // 十六进制数判断
            return (testval.test(val));
        }
		
		
		async function SelectSerial(){
			try{
				port =await navigator.serial.requestPort();  // 弹出系统串口列表对话框,选择一个串口进行连接
				ports =await navigator.serial.getPorts();    // 获取已连接的授权过的设备列表
				document.getElementById('butt_openserial').hidden=false;				
			}
			catch (e)
			{
				console.log(e);
			}
		}
		
		function updateInputData(data) {
            let array = new Uint8Array(data); // event.data.buffer就是接收到的inputreport包数据了
            //let hexstr = "";					
			for (const data of array) {
                //hexstr += (Array(2).join(0) + data.toString(16).toUpperCase()).slice(-2) + " "; // 将字节数据转换成(XX )形式字符串
				getdata[DataPoint]=data;
                DataPoint=DataPoint+1;
            }
			
		    var crc=0;
		    for(i=1;i<DataPoint;i++){   //校验接收数据,同时也解决数据分包上传的问题
		        crc=crc^getdata[i];
		    }
		    if (crc==0 && DataPoint>1){                
		        let hexstr = "";
		        for (i=0;i<DataPoint;i++){
		            hexstr=hexstr+getdata[i].toString(16).padStart(2, '0').toUpperCase()+" ";
		        }
		        ReceiveData.value += hexstr;		        
				
				var dispstr="";
				var cardnohex="";
				var datahex="";
				switch (SendCode) {
				    case 1:   //驱动发卡器响声的回应
				        dispstr = "发卡器已执行响声指令!" ; 		
				        label_disp.innerText=dispstr;
				        break;

				    case 6:    //读Ntag卡的回应
				        switch (getdata[1]){
				            case 0x00:
				                for (i=2;i<=8;i++){cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();}
				                for (i=11;i<DataPoint-1;i++){datahex=datahex+getdata[i].toString(16).padStart(2, '0').toUpperCase()+" ";}
				                dispstr= "读Ntag卡成功,卡号:" + cardnohex ;				                
				                RWdata.value=datahex;
				                break;
				            case 0x08:
				                dispstr= "读卡失败,未寻到卡!"				                
				                break;
				            case 0x09:
				                dispstr= "读卡失败!两张以上卡片同时在感应区发生冲突!"				                
				                break;
				            case 0x0c:
				                for (i=2;i<=8;i++){cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();}
				                dispstr= "卡密码认证失败,卡号:" + cardnohex +",读取块数据失败!";				                
				                break;
				            case 0x0d:
				                for (i=2;i<=8;i++){cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();}
				                dispstr= "可能要带密码操作,卡号:" + cardnohex +",读取块数据失败!";				                
				                break;
				            default:
				                dispstr= "读卡失败,返回错误代码:"+getdata[1].toString();
				        }
				        label_disp.innerText=dispstr;
				        break;

				    case 7:    //写Ntag卡的回应
				    case 8:    //初始化Ntag卡的回应
				        switch (getdata[1]){
				            case 0x00:
				                for (i=2;i<=8;i++){cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();}				                
				                dispstr= "写Ntag卡成功,卡号:" + cardnohex ;				                
				                break;
				            case 0x08:
				                dispstr= "读卡失败,未寻到卡!"				                
				                break;
				            case 0x09:
				                dispstr= "写卡失败!两张以上卡片同时在感应区发生冲突!"				                
				                break;
				            case 0x0c:
				                for (i=2;i<=8;i++){cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();}
				                dispstr= "卡密码认证失败,卡号:" + cardnohex +",写块数据失败!";				                
				                break;
				            case 0x0e:
				                for (i=2;i<=8;i++){cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();}
				                dispstr= "可能要带密码操作,卡号:" + cardnohex +",写块数据失败!";				                
				                break;
				            default:
				                dispstr= "写卡失败,返回错误代码:"+getdata[1].toString();
				        }
				        label_disp.innerText=dispstr;
				        break;
				}		        
				DataPoint=0;        //数据接收指针置0
		    }
        }		
		
		async function listenReceived(){
			if (reading){
				console.log("On reading.");
                return;
			}
			reading=true;
			
			while (port.readable && reading) {
                reader = port.readable.getReader();
                try {
                        updateInputData(value);
                } catch (e) {
                    alert(e);
                } finally {
                    reader.releaseLock();
                }
            }
			
			await port.close(); // 关闭串口
            port = null;
            alert("串口已关闭!");
		}
				 
		async function OpenSerial(){
			if (port==null){
				alert('请先选择要操作的串口号!');
				return;
			}else{
				document.getElementById('butt_closeserial').hidden=false;	
				var baudSelected = parseInt(document.getElementById("select_btn").value);
				await port.open({
					baudRate: baudSelected,					
					});	
				listenReceived();	
				alert('串口打开成功!');							
			}			
		}
		
		async function CloseSerial(){
			if ((port == null) || (!port.writable)) {
                alert("请选择并打开与发卡器相连的串口!");
       			return;
            }
			
			if (reading) {
				reading = false;
				reader?.cancel();
			}	
			document.getElementById('butt_openserial').hidden=true;
			document.getElementById('butt_closeserial').hidden=true;		
		}
		
		function selecheckauthkey(){			
			if (checkauth.checked){				
                document.getElementById('dispauthkey').hidden=false;
				document.getElementById('authkey0').hidden=false;
			}else{
				document.getElementById('dispauthkey').hidden=true;
				document.getElementById('authkey0').hidden=true;
			}			
		}
		
		async function beep(){
			if ((port == null) || (!port.writable)) {
                alert("请选择并打开与发卡器相连的串口!");
        		return;
        	}
			var beepdelay=parseInt(document.getElementById("beepdelay").value);
			const outputData = new Uint8Array(5);
			outputData[0]=0x03;     
			outputData[1]=0x0f;   
			outputData[2]=beepdelay % 256;
			outputData[3]=beepdelay / 256;
			outputData[4]=outputData[1] ^ outputData[2] ^outputData[3];
			
			var sendhex="";
			for(i=0;i<5;i++){
				sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
			}			
			SendData.value=sendhex;
			ReceiveData.value="";
			SendCode=1;
			DataPoint=0;
            const writer = port.writable.getWriter();
	        await writer.write(outputData);           // 发送数据
    	    writer.releaseLock();
		}
		
		async function piccinit_ntag(){
			if ((port == null) || (!port.writable)) {
                alert("请选择并打开与发卡器相连的串口!");
        		return;
			}

			myctrlword = 0; 							//指定控制字,无需密码为0,当需要密码时为EXTERNKEY;			
			mypiccserial = "00000000000000";  			//指定序列号,00000000000000 表示任意 NTAG卡。			
			if (checkauth.checked) {			     	//指定密码,NTAG21x卡密码为4个字节,卡出厂时密码功能不启用,这样无需密码也能读写卡
			    myctrlword = EXTERNKEY;              	//指定控制字,无需密码为0,当需要密码时为EXTERNKEY;
			    mypicckey = authkey0.value.trim();
			    if (!isHex(mypicckey) || mypicckey.length!=8) {
			        alert( "卡片认证密钥输入错误,请输入8位16进制密钥!");
			        authkey0.focus();
			        authkey0.select();
			        return;
			    }
			}
			else {
			    mypicckey = "00000000";               	
			}
						
		    //数据准备
			if (selonoff.selectedIndex == 0) {			//开启密码保护功能,写保护功能生效,但读保护需要下面的数据设定                
			    newkeystr = newkey.value.trim();		//取新密码
			    if (!isHex(newkeystr) || newkeystr.length != 8) {
			        alert( "新密钥输入错误,请输入8位16进制新密钥!");
			        newkey.focus();
			        newkey.select();
			        return;
			    }

			    strls1 = protectpageno.value.trim();//起始保护页号
			    if (!isUIntNum(strls1)) {
			        alert("起始保护页号输入错误!");
			        protectpageno.focus();
			        protectpageno.select();
			        return;
			    }
			    strls1 = "0" + parseInt(strls1).toString(16);
			    beginpage = strls1.substring(strls1.length - 2);	
							                
			    //计数器
			    strls1 = keyerrortimes.value.trim();//允许密码错误次数
			    if (!isUIntNum(strls1)) {
			        alert("允许密码错误次数输入错误!");
			        protectpageno.focus();
			        protectpageno.select();
			        return;
			    }
			    i = parseInt(strls1);
			    i = i % 8;
			    if (checkreadon.checked) {
			        i = i + 128;
			    }
			    strls1 = "0" + i.toString(16);
			    authfail = strls1.substring(strls1.length - 2);
				
			    packstr = packcode.value.trim();		//取PACK码
			    if (!isHex(packstr) || packstr.length != 4) {
			        alert( "PACK密钥确认码输入错误,请输入4位16进制PACK密钥确认码!");
			        packcode.focus();
			        packcode.select();
			        return;
			    }				
				
			    mypiccdata = "000000";
			    mypiccdata = mypiccdata +beginpage;
			    mypiccdata = mypiccdata + authfail;
			    mypiccdata = mypiccdata + "000000";
			    mypiccdata = mypiccdata + newkeystr;         //4字节新密码
			    mypiccdata = mypiccdata + packstr;			 //2字节PACK确认码	
			    mypiccdata = mypiccdata + "0000";
				
			    myctrlword = myctrlword + 0x01; //更新控制字
			    myctrlword = myctrlword + 0x02; //更新控制字
			    myctrlword = myctrlword + 0x04; //更新控制字
			}
			else {
			    mypiccdata = "000000FF";		//MIRROR,RFUI,MIRROR_PAGE,AUTH0
			    myctrlword = myctrlword + 0x01; //更新控制字
			    mypiccdata = mypiccdata + "000000000000000000000000";
			    myctrlword = myctrlword + 0x02; //更新控制字
			}
			
			const outputData = new Uint8Array(31);
			outputData[0]=0x1d;            //指令数据长度       
			outputData[1]=0x16;            //功能码
			outputData[2]=myctrlword;      //控制位
			for (i=0;i<7;i++){             //7字节本次操作卡UID,7字节全部取0表示可操作任意ntag标签
			    outputData[3+i]=parseInt(mypiccserial.substr(i*2,2),16);
			}
			for (i=0;i<4;i++){             //4字节卡片认证密钥
			    outputData[10+i]=parseInt(mypicckey.substr(i*2,2),16);
			}
			for (i=0;i<16;i++){            //16字节初化数据 
			    outputData[14+i]=parseInt(mypiccdata.substr(i*2,2),16);
			}

			var crc=0;
			for (i=1;i<30;i++){crc=crc^outputData[i];}
			outputData[30]=crc;	          //指令信息累加和校验位
			
			var sendhex="";
			for(i=0;i<31;i++){
			    sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
			}			
			SendData.value=sendhex;
			ReceiveData.value="";
			
			SendCode=8;
			DataPoint=0;
			var label_disp = document.getElementById('label_disp');
			label_disp.innerText = "";
								
			const writer = port.writable.getWriter();
			await writer.write(outputData);           // 发送数据
			writer.releaseLock();			
		}
		
		async function readcard_ntag(){
			if ((port == null) || (!port.writable)) {
                alert("请选择并打开与发卡器相连的串口!");
        		return;
        	}			
			
			mypiccserial = "00000000000000";         	//指定序列号,未知卡序列号时可指定为14个0,因为NTAG21x卡是7个字节的卡序列号            
			if (checkauth.checked) {			     	//指定密码,NTAG21x卡密码为4个字节,卡出厂时密码功能不启用,这样无需密码也能读写卡
			    myctrlword = EXTERNKEY;              	//指定控制字,无需密码为0,当需要密码时为EXTERNKEY;
			    mypicckey = authkey0.value.trim();
			    if (!isHex(mypicckey) || mypicckey.length!=8) {
			        alert( "卡片认证密钥输入错误,请输入8位16进制密钥!");
			        authkey0.focus();
			        authkey0.select();
			        return;
			    }
			}
			else {
			    mypicckey = "00000000";
			    myctrlword = 0;                      	//指定控制字,无需密码为0,当需要密码时为EXTERNKEY;
			}

			myblockaddr = ntagstartno.value.trim();  	//读写起始页
			if (!isUIntNum(myblockaddr)) {
			    alert( "读写起始页输入错误!");
			    ntagstartno.focus();
			    ntagstartno.select();
			    return;
			}

			myblocksize = ntagpagenumber.value.trim();	//读写页数
			if (!isUIntNum(myblocksize)) {
			    alert( "读写页数输入错误!");
			    ntagpagenumber.focus();
			    ntagpagenumber.select();
			    return;
			}else if(myblocksize>12){
			    alert( "每次最多读取12块数据!");
			    ntagpagenumber.focus();
			    ntagpagenumber.select();
			    ntagpagenumber.value="12";
			    return;
			}

			const outputData = new Uint8Array(17);
			outputData[0]=0x0f;            //指令数据长度       
			outputData[1]=0x1b;            //功能码
			outputData[2]=myctrlword;      //控制位
			for (i=0;i<7;i++){             //7字节本次操作卡UID,7字节全部取0表示可操作任意ntag标签
			    outputData[3+i]=parseInt(mypiccserial.substr(i*2,2),16);
			}
			for (i=0;i<4;i++){             //4字节卡片认证密钥
			    outputData[10+i]=parseInt(mypicckey.substr(i*2,2),16);
			}
			outputData[14]=myblockaddr;    //读卡起始块
			outputData[15]=myblocksize;    //读卡总块数
			
			var crc=0;
			for (i=1;i<16;i++){
			    crc=crc^outputData[i];
			}
			outputData[16]=crc;	          //指令信息累加和校验位
			
			var sendhex="";
			for(i=0;i<17;i++){
			    sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
			}			
			SendData.value=sendhex;
			ReceiveData.value="";
			RWdata.value="";
			SendCode=6;
			DataPoint=0;
			var label_disp = document.getElementById('label_disp');
			label_disp.innerText = "";
								
			const writer = port.writable.getWriter();
			await writer.write(outputData);           // 发送数据
			writer.releaseLock();			
		}
		
		async function writecard_ntag(){
			if ((port == null) || (!port.writable)) {
                alert("请选择并打开与发卡器相连的串口!");
        		return;
        	}			
			
			mypiccserial = "00000000000000";         	//指定序列号,未知卡序列号时可指定为14个0,因为NTAG21x卡是7个字节的卡序列号            
			if (checkauth.checked) {			     	//指定密码,NTAG21x卡密码为4个字节,卡出厂时密码功能不启用,这样无需密码也能读写卡
			    myctrlword = EXTERNKEY;              	//指定控制字,无需密码为0,当需要密码时为EXTERNKEY;
			    mypicckey = authkey0.value.trim();
			    if (!isHex(mypicckey) || mypicckey.length!=8) {
			        alert( "卡片认证密钥输入错误,请输入8位16进制密钥!");
			        authkey0.focus();
			        authkey0.select();
			        return;
			    }
			}
			else {
			    mypicckey = "00000000";
			    myctrlword = 0;                      	//指定控制字,无需密码为0,当需要密码时为EXTERNKEY;
			}

			myblockaddr = ntagstartno.value.trim();  	//读写起始页
			if (!isUIntNum(myblockaddr)) {
			    alert( "读写起始页输入错误!");
			    ntagstartno.focus();
			    ntagstartno.select();
			    return;
			}

			myblocksize = ntagpagenumber.value.trim();	//读写页数
			if (!isUIntNum(myblocksize)) {
			    alert( "读写页数输入错误!");
			    ntagpagenumber.focus();
			    ntagpagenumber.select();
			    return;
			}else if(myblocksize>11){
			    alert( "每次最多写入11块数据!");
			    ntagpagenumber.focus();
			    ntagpagenumber.select();
			    ntagpagenumber.value="11";
			    return;
			}

			var datalen=myblocksize*4;
		    
			mypiccdata = RWdata.value.trim();            //写卡数据
			mypiccdata=mypiccdata.replace(/\s/g, "");
			if (!isHex(mypiccdata)) {
			    alert( "写卡数据输入错误,请输入"+(datalen*2).toString()+"位16进制写卡数据!");
			    RWdata.focus();
			    RWdata.select();
			    return;
			}else if(mypiccdata.length<datalen*2){
			    if (confirm("写卡数据不足,是否要后面补0写入?")) {      
			        while (mypiccdata.length<datalen*2){
			            mypiccdata=mypiccdata+"0";
			        }
			    }else{
			        return;
			    }	
			}

			const outputData = new Uint8Array(datalen+17);
			outputData[0]=15+datalen;      //指令数据长度       
			outputData[1]=0x1c;            //功能码
			outputData[2]=myctrlword;      //控制位
			for (i=0;i<7;i++){             //7字节本次操作卡UID,7字节全部取0表示可操作任意ntag标签
			    outputData[3+i]=parseInt(mypiccserial.substr(i*2,2),16);
			}
			for (i=0;i<4;i++){             //4字节卡片认证密钥
			    outputData[10+i]=parseInt(mypicckey.substr(i*2,2),16);
			}
			outputData[14]=myblockaddr;    //写卡起始块
			outputData[15]=myblocksize;    //写卡总块数
			for  (i=0;i<datalen;i++){      //写卡数据
			    outputData[16+i]=parseInt(mypiccdata.substr(i*2,2),16);
			}

			var crc=0;                     
			for (i=1;i<16+datalen;i++){
			    crc=crc^outputData[i];
			}
			outputData[16+datalen]=crc;	   //指令信息累加和校验位
			
			var sendhex="";
			for(i=0;i<17+datalen;i++){
			    sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
			}			
			SendData.value=sendhex;
			ReceiveData.value="";
			
			SendCode=7;
			DataPoint=0;
			var label_disp = document.getElementById('label_disp');
			label_disp.innerText = "";
								
			const writer = port.writable.getWriter();
			await writer.write(outputData);           // 发送数据
			writer.releaseLock();								
		}
				
		
	</script>
    
	<style>
		th {
		  font-family:楷体;
		  background-color:#F6FAFF;		
		  color:blue;
		}
		td {
		  font-family:楷体;
		  background-color:#F6FAFF;		
		}  
    </style>    
    
</head>

<body>
<table width="950" height="428"  align="center">
  <tr>
    <td width="120" height="50">    
      <input  name="btnSelect"  type="submit" id="btnSelect" style="width:100%" onclick="SelectSerial()" value="选择串口" />
    </td>
    <td width="800">波特率:<label for="select_btn"></label>
        <select name="select_btn" id="select_btn">
          <option>1200</option>
          <option>4800</option>
          <option>9600</option>
          <option>14400</option>
          <option selected="selected">19200</option>
          <option>38400</option>
          <option>43000</option>
          <option>57600</option>
          <option>115200</option>
          <option>128000</option>
          <option>230400</option>
		  <option>256000</option>
		  <option>460800</option>
		  <option>921600</option>
		  <option>1382400</option>
        </select>
         数据位:
        <select name="select_btn2" id="select_data">
          <option>8</option>
          <option>7</option>
          <option>6</option>
          <option>5</option>
        </select>
         停止位:
        <select name="select_btn3" id="select_stop">
          <option>1</option>
          <option>1.5</option>
          <option>2</option>
        </select>
         校验位:
      <select name="select_btn4" id="select_mark">
          <option>None  无</option>
          <option>Odd   奇</option>
		  <option>Even  偶</option>
		  <option>Mask  常1</option>
		  <option>Space 常0</option>
        </select>
         <input  name="butt_openserial"  type="submit" id="butt_openserial" style="width:80px" onclick="OpenSerial()" value="打开串口" />
        <input  name="butt_closeserial"  type="submit" id="butt_closeserial" style="width:80px" onclick="CloseSerial()" value="关闭串口" />
	</td>
  </tr>
  
  <tr>
    <td height="36" >
      <input name="butt_beep"  type="submit" id="butt_beep" style="width:100%" onclick="beep()" value="驱动发卡器响声" />
    </td>
    <td>响声延时:
    <input style="color:blue;text-align:center;" name="beepdelay" type="text" id="beepdelay" value="30" size="5" maxlength="4" onkeyup="this.value=this.value.replace(/\D/g,'')"/>
    毫秒</td>
  </tr>
  
  <tr>
    <td height="36"> </td>
    <td><label style="color:blue;" name="label_disp" id="label_disp"></label></td>
  </tr>
  
  <tr>
    <td height="36"> </td>
    <td>
      <input type="checkbox" name="checkauth" id="checkauth" onchange="selecheckauthkey()"/>      
      选择先认证卡片密钥再继续以下的操作
      <label name="dispauthkey" id="dispauthkey">,16进制卡片认证密钥:</label>
    <input style="color:blue;text-align:center;" name="authkey0" type="text" id="authkey0" value="12345678" size="8" maxlength="8" onkeyup="this.value=this.value.replace(/[^0-9a-fA-F]/g,'')"/>
       
    </td>
  </tr>
  
  <tr>
    <td height="69"><input style="width:120px" name="butt_piccinit_ntag" type="submit" id="butt_piccinit_ntag" onclick="piccinit_ntag()" value="初始化Ntag卡" /></td>
    <td><p>
      <label for="rwtext"></label>
      <select style="color:blue;" name="selonoff" id="selonoff">
        <option>开启卡片密钥保护功能</option>
        <option selected="selected">取消卡片密钥保护功能</option>
      </select>
    ,从:
    <input style="color:blue;text-align:center;" name="protectpageno" type="text" id="protectpageno" value="20" size="4" maxlength="4" onkeyup="this.value=this.value.replace(/\D/g,'')"/>
    页开始有密钥保护功能,
    <input type="checkbox" name="checkreadon" id="checkreadon" />
    选择开启读操作密钥保护。</p>
      <p>新密钥:
        <input style="color:blue;text-align:center;" name="newkey" type="text" id="newkey" value="12345678" size="8" maxlength="8" onkeyup="this.value=this.value.replace(/[^0-9a-fA-F]/g,'')"/>
      ,允许密钥认证失败次数:
      <input style="color:blue;text-align:center;" name="keyerrortimes" type="text" id="keyerrortimes" value="0" size="2" maxlength="2" onkeyup="this.value=this.value.replace(/\D/g,'')"/>
      ,PACK密钥确认码:
      <input style="color:blue;text-align:center;" name="packcode" type="text" id="packcode" value="1234" size="4" maxlength="4" onkeyup="this.value=this.value.replace(/[^0-9a-fA-F]/g,'')"/>
      </p>
    <p style="color:red;">警告:当密钥认证失败次数取值0表示不限制次数,认证密钥操作失败大于设置值时卡片将会报废!</p></td>
  </tr>  
  
  <tr>
    <td height="36"><p>
      <input style="width:120px" name="butt_readcard_ntag" type="submit" id="butt_readcard_ntag" onclick="readcard_ntag()" value="轻松读Ntag卡" />
    </p>
    <p> </p>
    <p>
      <input style="width:120px" name="butt_writecard_ntag" type="submit" id="butt_writecard_ntag" onclick="writecard_ntag()" value="轻松写Ntag卡" />
    </p></td>
    <td><p>读写起始页:
      <input style="color:blue;text-align:center;" name="ntagstartno" type="text" id="ntagstartno" value="4" size="4" maxlength="4" onkeyup="this.value=this.value.replace(/\D/g,'')"/>
    ,读写页数:
    <input style="color:blue;text-align:center;" name="ntagpagenumber" type="text" id="ntagpagenumber" value="10" size="2" maxlength="2" onkeyup="this.value=this.value.replace(/\D/g,'')"/>
    ,每次最多读12页、写11页。</p>
      <p>
        <textarea style="width:800px;color:red;font-family:楷体;" name="RWdata" id="RWdata" cols="100" rows="4" ></textarea>
    </p></td>
  </tr>    
  
  <tr>
    <td height="70" scope="row"><p align="center">发送的数据</p></td>
    <td><textarea style="width:800px;color:blue;" name="SendData" id="SendData" cols="100" rows="4" ></textarea></td>
  </tr>
  
  <tr>
    <td height="70" scope="row"><p align="center">接收的数据</p></td>
    <td><textarea style="width:800px" name="ReceiveData" id="ReceiveData" cols="100" rows="4" ></textarea></td>
  </tr>
</table>

</body>
</html>