JavaScript调用Web Serial API接口实现WEB浏览器前端向串口发送指令驱动NFC读写器读写IC卡

`
	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:
	                break;
	            case 2:  //读取M1卡序列号的回应
	            case 3:  //读取M1卡扇区数据的回应
	            case 4:  //写M1卡扇区数据的回应
	            case 5:  //修改M1卡扇区数密钥的回应
					switch (getdata[0]){
						case 1:  //返回有效数据长度为1
							switch (getdata[1]){
								case 8:
									dispstr = "未寻到卡!" ; 
									break;
								case 9:
									dispstr = "两张以上卡片同时在感应区,发生冲突!" ; 										
									break;
								case 10:
									dispstr = "无法选择激活卡片!" ; 
									break;
								case 11:
									dispstr = "密码装载失败,卡片序列号已知!" ; 
								    break;
								default:
									dispstr = "操作卡失败,返回代码:" + getdata[1].ToString() ; 		
									break;
							}
							var label_disp = document.getElementById('label_disp');
							label_disp.innerText = dispstr;
							break;		 
							
						case 5:  //返回有效数据长度为5								
							for(i=2;i<6;i++){
								cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();
							}
							if(SendCode==2){
							    dispstr ="读卡序列号";
							}else if(SendCode==3){
							    dispstr ="读扇区数据";
							}else if(SendCode==4){
							    dispstr ="写扇区数据";
							}else if(SendCode==5){
							    dispstr ="修改扇区密码";
							}	
							switch (getdata[1]){
								case 0:										
									dispstr=dispstr+"成功!卡号:"+cardnohex
									break;
								case 1:
								    dispstr = dispstr+",密码认证成功,但读写扇区内容失败!卡号:" + cardnohex;
									break;	
								case 2:
								    dispstr = dispstr+",第0块操作成功,但第1、2块操作失败,仅扇区内容前16个字节的数据有效!!卡号:" + cardnohex;
								    break;	
							    case 3:
							        dispstr = dispstr+",第0、1块操作成功,但第2块操作失败,仅扇区内容前32个字节的数据有效!!卡号:" + cardnohex;
							        break;	    
								case 12:
								    dispstr = dispstr+",密码认证失败,卡号:" + cardnohex;
									break;
								default:
								    dispstr = dispstr+",操作卡失败,返回代码:" + getdata[1].ToString() ; 		
									break;		
							}
							var label_disp = document.getElementById('label_disp');
							label_disp.innerText = dispstr;
							break;
							
						case 53:  //返回有效数据长度为53
							for(i=2;i<6;i++){
								cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();
							}
							dispstr = "读M1卡扇区数据成功,卡号:" + cardnohex;
							for(i=6;i<DataPoint-1;i++){
								datahex=datahex+getdata[i].toString(16).padStart(2, '0').toUpperCase()+" ";
							}
							var label_disp = document.getElementById('label_disp');
							label_disp.innerText = dispstr;
							RWM1Data.value=datahex;
							break;
							
						case 54:  //旧版本设备返回有效数据长度为54
							for(i=2;i<6;i++){
								cardnohex=cardnohex+getdata[i].toString(16).padStart(2, '0').toUpperCase();
							}
							dispstr = "读M1卡扇区数据成功,卡号:" + cardnohex;
							for(i=7;i<DataPoint-1;i++){
								datahex=datahex+getdata[i].toString(16).padStart(2, '0').toUpperCase()+" ";
							}
							var label_disp = document.getElementById('label_disp');
							label_disp.innerText = dispstr;
							RWM1Data.value=datahex;
							break;	
					}
	                break;
	        }
			DataPoint=0;
	    }
    }		
	
	async function listenReceived(){
		if (reading){
			console.log("On reading.");
            return;
		}
		reading=true;
					
		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();
		}			
	}
	
	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 Request(){
		if ((port == null) || (!port.writable)) {
            alert("请选择并打开与发卡器相连的串口!");
    		return;
    	}			
		const outputData = new Uint8Array(3);
		outputData[0]=0x01;		//指令长度
		outputData[1]=0xf0;     //功能码
		outputData[2]=0xf0;		//校验码	
		
		var sendhex="";
		for(i=0;i<3;i++){
			sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
		}			
		SendData.value=sendhex;
		ReceiveData.value="";
		SendCode=2;
		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 ReadM1Card(){
		if ((port == null) || (!port.writable)) {
            alert("请选择并打开与发卡器相连的串口!");
    		return;
    	}			
		
		if (selinoutkey.selectedIndex==1){
			myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN + EXTERNKEY;    //外部密钥认证
		}else {myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN;}			   //已装载到发卡器内部密钥认证
		
        myareano = selareano.selectedIndex;                       		   //指定为本次读取第8区,十进制
        authmode = selauthmode.selectedIndex;                              //指定密码模式,十进制,大于0表示用A密码认证,推荐用A密码认证
        mypiccserial = "00000000";         						           //指定本次操作的卡序列号,十六进制,未知时可指定为8个0
        mypicckey = authkey0.value.trim();                                 //指定卡片密码,十六进制,FFFFFFFFFFFF为卡片厂家出厂密码
		if (!isHex(mypicckey) || mypicckey.length!=12) {
			textarea.value = "认证密钥输入错误,请输入正确的12位16进制认证密钥!";
			authkey0.focus();
            authkey0.select();
            return;
		}            
					
		const outputData = new Uint8Array(16);
		outputData[0]=0x0e;			//指令长度
		outputData[1]=0x78;			//功能码
		outputData[2]=myctrlword;	//控制位		
		outputData[3]=0x00;			//四字节本次操作卡卡号,全部取0表示可操作任意卡
		outputData[4]=0x00;
		outputData[5]=0x00;
		outputData[6]=0x00;
		outputData[7]=myareano;		//扇区号
		outputData[8]=authmode;		//密钥认证方式	
		for(i=0;i<6;i++){           //6字节密钥
			outputData[9+i]=parseInt(mypicckey.substr(i*2,2),16);
		}
		var crc=0;
		for (i=1;i<15;i++){
			crc=crc^outputData[i];
		}
		outputData[15]=crc;	
		
		var sendhex="";
		for(i=0;i<16;i++){
			sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
		}			
		SendData.value=sendhex;
		ReceiveData.value="";
		RWM1Data.value="";
		SendCode=3;
		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 WriteM1Card(){
		if ((port == null) || (!port.writable)) {
            alert("请选择并打开与发卡器相连的串口!");
    		return;
    	}			
		
		if (selinoutkey.selectedIndex==1){
			myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN + EXTERNKEY;    //外部密钥认证
		}else {myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN;}			   //已装载到发卡器内部密钥认证
		
        myareano = selareano.selectedIndex;                       		   //指定为本次读取第8区,十进制
        authmode = selauthmode.selectedIndex;                              //指定密码模式,十进制,大于0表示用A密码认证,推荐用A密码认证
        mypiccserial = "00000000";         						           //指定本次操作的卡序列号,十六进制,未知时可指定为8个0
        mypicckey = authkey0.value.trim();                                 //指定卡片密码,十六进制,FFFFFFFFFFFF为卡片厂家出厂密码
		if (!isHex(mypicckey) || mypicckey.length!=12) {
			textarea.value = "认证密钥输入错误,请输入正确的12位16进制认证密钥!";
			authkey0.focus();
            authkey0.select();
            return;
		}     
		mypiccdata=RWM1Data.value.trim();
		mypiccdata=mypiccdata.replace(/\s/g, "");
		if (isHex(mypiccdata)){
			if(mypiccdata.length<96){
				if (confirm("写卡数据不足一扇区48个字节,是否要后面补0写入?")) {      
					while (mypiccdata.length<96){
						mypiccdata=mypiccdata+"0";
					}
				}else{
					return;
				}	
			}
		}else{
		   	alert("请输入96位16进制写卡数据!");
			return;
		}
										
		const outputData = new Uint8Array(64);
		outputData[0]=0x3e;			//指令长度
		outputData[1]=0x69;			//功能码
		outputData[2]=myctrlword;	//控制位		
		outputData[3]=0x00;			//四字节本次操作卡卡号,全部取0表示可操作任意卡
		outputData[4]=0x00;
		outputData[5]=0x00;
		outputData[6]=0x00;
		outputData[7]=myareano;		//扇区号
		outputData[8]=authmode;		//密钥认证方式	
		for(i=0;i<6;i++){           //6字节密钥
			outputData[9+i]=parseInt(mypicckey.substr(i*2,2),16);
		}
		for(i=0;i<48;i++){           //48字节写卡数据
			outputData[15+i]=parseInt(mypiccdata.substr(i*2,2),16);
		}
		var crc=0;
		for (i=1;i<63;i++){
			crc=crc^outputData[i];
		}
		outputData[63]=crc;	
		
		var sendhex="";
		for(i=0;i<64;i++){
			sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
		}			
		SendData.value=sendhex;
		ReceiveData.value="";
		
		SendCode=4;
		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 changecardkeyex(){
		if ((port == null) || (!port.writable)) {
            alert("请选择并打开与发卡器相连的串口!");
    		return;
    	}			
					
		
		if (selinoutkey.selectedIndex==1){
			myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN + EXTERNKEY;    //外部密钥认证
		}else {myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN;}			   //已装载到发卡器内部密钥认证
		
        myareano = selareano.selectedIndex;                       		   //指定为本次读取第8区,十进制
        authmode = selauthmode.selectedIndex;                              //指定密码模式,十进制,大于0表示用A密码认证,推荐用A密码认证
        mypiccserial = "00000000";         						           //指定本次操作的卡序列号,十六进制,未知时可指定为8个0
        mypicckey = authkey0.value.trim();                                 //指定卡片密码,十六进制,FFFFFFFFFFFF为卡片厂家出厂密码
		if (!isHex(mypicckey) || mypicckey.length!=12) {
			textarea.value = "认证密钥输入错误,请输入正确的12位16进制认证密钥!";
			authkey0.focus();
            authkey0.select();
            return;
		}     
		mypicckeyA = newkeya.value.trim();                             //新A密钥
		if (!isHex(mypicckeyA) || mypicckeyA.length!=12) {
			textarea.value = "新A密钥输入错误,请输入正确的12位16进制新A密钥!";
			newkeya.focus();
            newkeya.select();
            return;
		}			
        mypiccctr = cardctr.value.trim();                             //新控制位,出厂为FF078069,
		if (!isHex(mypiccctr) || mypiccctr.length!=8) {
			textarea.value = "新控制位输入错误,请输入正确的8位16进制控制位!";
			cardctr.focus();
            cardctr.select();
            return;
		}
        mypicckeyB = newkeyb.value.trim();                             //新B密钥
		if (!isHex(mypicckeyB) || mypicckeyB.length!=12) {
			textarea.value = "新B密钥输入错误,请输入正确的12位16进制新B密钥!";
			newkeyb.focus();
            newkeyb.select();
            return;
		}
		mypicckey_new=mypicckeyA+mypiccctr+mypicckeyB;
		switch (selchangekey.selectedIndex){
			case 0:
				mypicckey_new=mypicckey_new+"00";
				break;
			case 1:
				mypicckey_new=mypicckey_new+"02";
				break;					
			default:
				mypicckey_new=mypicckey_new+"03";
				break;					
		}						
										
		const outputData = new Uint8Array(33);
		outputData[0]=0x1f;			//指令长度
		outputData[1]=0xf1;			//功能码
		outputData[2]=myctrlword;	//控制位		
		outputData[3]=0x00;			//四字节本次操作卡卡号,全部取0表示可操作任意卡
		outputData[4]=0x00;
		outputData[5]=0x00;
		outputData[6]=0x00;
		outputData[7]=myareano;		//扇区号
		outputData[8]=authmode;		//密钥认证方式	
		for(i=0;i<6;i++){           //6字节密钥
			outputData[9+i]=parseInt(mypicckey.substr(i*2,2),16);
		}
								
		for(i=0;i<17;i++){           //6字节新A钥+4字节控制位+6字节新B钥+1字节密钥修改类型
			outputData[15+i]=parseInt(mypicckey_new.substr(i*2,2),16);
		}
						
		var crc=0;
		for (i=1;i<32;i++){
			crc=crc^outputData[i];
		}
		outputData[32]=crc;	
		
		var sendhex="";
		for(i=0;i<33;i++){
			sendhex=sendhex+outputData[i].toString(16).padStart(2, '0').toUpperCase()+" ";
		}			
		SendData.value=sendhex;
		ReceiveData.value="";
		
		SendCode=5;
		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>    
扇区号:
  ,认证密钥:
  <label for="authkey0"></label>
<input style="color:blue;text-align:center;" name="authkey0" type="text" id="authkey0" value="FFFFFFFFFFFF" size="12" maxlength="12" onkeyup="this.value=this.value.replace(/[^0-9a-fA-F]/g,'')"/>
</p>    
</td>
发送的数据
接收的数据
` 
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号