Web Serial串口通信实现WEB浏览器读写M1卡

news2024/11/16 1:57:21

 本示例使用的设备:RS232串口RFID NFC IC卡读写器可二次开发编程发卡器USB转COM-淘宝网 (taobao.com)

<!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串口读写器示例 </title>
	<script>		
		window.onload = function() {
   			document.getElementById('butt_openserial').hidden=true;
			document.getElementById('butt_closeserial').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:
		                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>    
    
</head>

<body>
<table width="950" height="423"  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>
        &nbsp;数据位:
        <select name="select_btn2" id="select_data">
          <option>8</option>
          <option>7</option>
          <option>6</option>
          <option>5</option>
        </select>
        &nbsp;停止位:
        <select name="select_btn3" id="select_stop">
          <option>1</option>
          <option>1.5</option>
          <option>2</option>
        </select>
        &nbsp;校验位:
      <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>
        &nbsp;<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"><input name="butt_request"  type="submit" id="butt_request" style="width:100%" onclick="Request()" value="仅读取M1卡序列号" /></td>
    <td><label style="color:blue;" name="label_disp" id="label_disp"></label></td>
  </tr>
  
  <tr>
    <td height="36"><input name="butt_readm1"  type="submit" id="butt_readm1" style="width:100%" onclick="ReadM1Card()" value="读取M1卡扇区数据" /></td>
    <td><p>扇区号:
        <select style="color:blue;" name="selareano" id="selareano">
        <option selected="selected">第0扇区</option>
        <option>第1扇区</option>
        <option>第2扇区</option>
        <option>第3扇区</option>
        <option>第4扇区</option>
        <option>第5扇区</option>
        <option>第6扇区</option>
        <option>第7扇区</option>
        <option>第8扇区</option>
        <option>第9扇区</option>
        <option>第10扇区</option>
        <option>第11扇区</option>
        <option>第12扇区</option>
        <option>第13扇区</option>
        <option>第14扇区</option>
        <option>第15扇区</option>        
      </select>
      <label for="changeauthkey"></label>
      <select style="color:blue;" name="selinoutkey" id="selinoutkey">
        <option>内部密钥认证</option>
        <option selected="selected">外部密钥认证</option>
      </select>
      <label for="newkey"></label>
      <select style="color:blue;" name="selauthmode" id="selauthmode">
	    <option>B密钥认证</option>
        <option selected="selected">A密钥认证</option>        
      </select>
      
      ,认证密钥:
      <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>
  </tr>
  
  <tr>
    <td height="69"><input name="butt_writem1"  type="submit" id="butt_writem1" style="width:100%" onclick="WriteM1Card()" value="写数据到M1卡扇区" /></td>
    <td><textarea style="width:800px;color:red;" name="RWM1Data" id="RWM1Data" cols="100" rows="4" ></textarea></td>
  </tr>  
  
  <tr>
    <td height="36"><input style="width:100%" name="butt_changecardkeyex" type="submit" id="butt_changecardkeyex" onclick="changecardkeyex()" value="修改M1卡扇区密钥" /></td>
    <td><select style="color:blue;" name="selchangekey" id="selchangekey">
      <option>只修改A密钥</option>
      <option>只修改AB密钥</option>
      <option selected="selected">修改AB密钥及控制位</option>
    </select>
      ,新A密钥
      <input style="color:red;text-align:center;" name="newkeya" type="text" id="newkeya" value="FFFFFFFFFFFF" size="12" maxlength="12" onkeyup="this.value=this.value.replace(/[^0-9a-fA-F]/g,'')"/>
      ,控制位
      <input style="color:red;text-align:center;" name="cardctr" type="text" id="cardctr" value="FF078069" size="8" maxlength="8" onkeyup="this.value=this.value.replace(/[^0-9a-fA-F]/g,'')"/>
      ,B密钥
    <input style="color:red;text-align:center;" name="newkeyb" type="text" id="newkeyb" value="FFFFFFFFFFFF" size="12" maxlength="12" onkeyup="this.value=this.value.replace(/[^0-9a-fA-F]/g,'')"/></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>

源码下载:WebSerialapi浏览器串口通讯读写NFC标签资源-CSDN文库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1861024.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第10章 启动过程组 (启动过程组的重点工作)

第10章 启动过程组 10.3启动过程组的重点工作&#xff0c;在第三版教材第362~364页&#xff1b; 文字图片音频方式 第一个知识点&#xff1a;项目启动会议 1、作用 标志着对项目经理责权的定义结果的正式公布&#xff0c;通常由项目经理负责组织和召开。2、目的 使项目各…

AI网络爬虫:下载m3u8视频文件

要下载m3u8视频文件&#xff0c;首先得找到m3u8地址&#xff0c;按下F12键&#xff0c;看网络-fetch/xhr,然后找网址中包括m3u8的地址&#xff0c;再预览或者看下相应 https://1304688195.vod2.myqcloud.com/9d058fb7vodtranscq1304688195/1194c6da1253642699220090018/video_1…

【阅读论文】-- IDmvis:面向1型糖尿病治疗决策支持的时序事件序列可视化

IDMVis: Temporal Event Sequence Visualization for Type 1 Diabetes Treatment Decision Support 摘要1 引言2 1 型糖尿病的背景3 相关工作3.1 时间事件序列可视化3.2 电子健康记录可视化3.3 1 型糖尿病可视化3.4 任务分析与抽象 4 数据抽象5 层次化任务抽象5.1 临床医生工作…

【IM 服务】新用户为什么刚注册就能收到通知?为什么能接收注册前的通知?

功能说明&#xff1a; 默认新注册的用户可以接收到注册前 7 天内的广播消息。您可以从控制台免费基础功能页面关闭该服务。 开通方式&#xff1a; 访问开发者后台 免费基础功能 1页面&#xff0c;确认应用名称与环境&#xff08;开发 /生产 &#xff09;正确无误后&#xff0c…

统一视频接入平台LntonCVS视频共享交换平台智慧景区运用方案

随着夏季的到来&#xff0c;各地景区迎来了大量游客&#xff0c;而景区管理面临的挑战也愈加严峻&#xff0c;尤其是安全问题显得格外突出。 视频监控在预防各类安全事故方面发挥着重要作用&#xff0c;不论是自然景区还是人文景区&#xff0c;都潜藏着诸多安全隐患&#xff0…

eslint 与 prettier 的一些常见的配置项(很详细)

目录 1、eslint 常见配置项&#xff08;语法规范&#xff09; 2、 prettier 常见的配置项&#xff08;格式规范&#xff09; 代码规范相关内容看小编的该文章&#xff0c;获取对你有更好的帮助 vsCode代码格式化&#xff08;理解eslint、vetur、prettier&#xff0c;实现格式…

AIPainter:创意绘画的智能助手

AIPainter 介绍 AIPainter是一款简单易用的AI画图工具&#xff0c;支持文生图、图生图&#xff08;提示词改图、图片变体、分辨率增强等&#xff09;&#xff0c;底层大模型基于开源的腾讯混元文生图、SDXL等。 功能特点 提示词库 AIPainter默认提供了一些常用场景的提示词供…

(python)小学出题热门词汇可视化绘制

1.代码 import pandas as pd from wordcloud import WordCloud import matplotlib.pyplot as plt from collections import Counter import jieba # 如果你处理的是中文文本&#xff0c;需要jieba分词 import re # 停用词列表&#xff0c;这里只是示例&#xff0c…

2024年【R2移动式压力容器充装】考试总结及R2移动式压力容器充装试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【R2移动式压力容器充装】考试总结及R2移动式压力容器充装试题及解析&#xff0c;包含R2移动式压力容器充装考试总结答案和解析及R2移动式压力容器充装试题及解析练习。安全生产模拟考试一点通结合国家R2移动式…

PWN练习---Heap_1

heap_Easy_Uaf 题源&#xff1a;PolarD&N 考点&#xff1a;UAF漏洞(use after free) 源码 程序是一个菜单&#xff0c;可以实现add&#xff0c;dele&#xff0c;edit&#xff0c;puts 堆块内容等的功能。&#xff08;堆块编号从0开始&#xff09; 注意到一个存在backdoor的…

定制化服务:可燃气体报警器检定收费新模式

随着工业化和城市化的快速发展&#xff0c;可燃气体报警器作为重要的安全监测设备&#xff0c;其准确性和可靠性对于保障人们的生命财产安全至关重要。因此&#xff0c;可燃气体报警器的定期检定和维护显得尤为重要。 在这篇文章中&#xff0c;佰德将围绕可燃气体报警器检定收…

ssm 宠物领养系统-计算机毕业设计源码08465

目 录 摘要 1 绪论 1.1课题背景及意义 1.2研究现状 1.3ssm框架介绍 1.3论文结构与章节安排 2 宠物领养系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 …

Android Native 客户端属性配置系统使用说明

Android Native 客户端属性配置系统使用说明 背景和问题现代 android 开发基本都基于 gradle 属性设置来进行定制化编译,随着项目的迭代,工程结构越发复杂,配置属性越来越多,越来越多的配置使得上手难度越来越大。 解决方案设计一般而言,在 android 开发中,Gradle 属性系…

Calibre - 合并电子书(EpubMerge)

这里使用 Calibre 软件和 EpubMerge 插件 EpubMerge github &#xff1a; https://github.com/JimmXinu/EpubMerge 1、安装 Merge 插件 安装后需要重启 calibre 2、查看设置 4 3、选中文件、开始合并 合并完成后&#xff0c;会弹窗窗口&#xff0c;来编辑 合辑的元信息 完成…

学习记录697@数据通信基础之异步通信和同步通信

最近在看计算机网络物理层部分&#xff0c;涉及到异步通信和同步通信&#xff0c;这个和通信知识相关。 异步通信和同步通信都是为了解决时钟同步问题&#xff0c;这个和编程中的同步和异步是不一样的概念。 时钟同步 我的理解是&#xff0c;发送者发送一系列信号&#xff0…

Vue3 和 ECharts 创建交互式雷达图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 Vue.js 中使用 ECharts 创建雷达图 应用场景 雷达图是一种多维数据可视化图表&#xff0c;常用于比较不同指标之间的关系和变化趋势。在 Vue.js 项目中&#xff0c;我们可以使用 ECharts 库轻松创建雷达图。 …

CPR曲面重建代码

废话不说&#xff0c;直接上代码&#xff1a; #include "vtkAutoInit.h" #include "vtkPolyData.h" #include "vtkProbeFilter.h" #include "vtkParametricFunctionSource.h" #include "vtkParametricSpline.h" #include &…

jenkins api部署时,一直提示pending-Finished waiting

问题&#xff1a; 调用jenkins api部署时&#xff0c;一直提示pending-Finished waiting 解决方案&#xff1a; 这个问题困扰了很久&#xff0c;一直没有思路&#xff0c;后面看到调用jenkinsAPI本身会出现一段提示&#xff0c;pending in the quiet period&#xff0c;通过搜…

智慧仓储的秘密武器:数据可视化的应用

智慧仓储中数据可视化是如何应用的&#xff1f;在现代物流和供应链管理中&#xff0c;智慧仓储已成为企业提升效率、降低成本和优化运营的重要手段。而数据可视化作为智慧仓储的重要工具&#xff0c;通过将复杂的数据转化为直观、易理解的图表和图形&#xff0c;极大地提升了仓…

js实现blockly后台解释器,可以单步执行,可以调用c/c++函数

实现原理 解析blockly语法树,使用js管理状态,实际使用lua执行,c/c++函数调用使用lua调用c/c++函数的能力 可以单行执行 已实现if功能 TODO for循环功能 函数功能 单步执行效果图 直接执行效果图 源代码 //0 暂停 1 单步执行 2 断点 //创建枚举 var AstStatus = {PAUS…