webserial-core 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/AbstractSerialDevice.d.ts +17 -0
- package/dist/demo-style.css +255 -0
- package/dist/demos/assets/demo-shared-BVQKG6NC.js +272 -0
- package/dist/demos/assets/demo-shared-DLFsukQx.css +1 -0
- package/dist/demos/assets/web-bluetooth-yFc46qqb.js +1 -0
- package/dist/demos/assets/web-serial-DLoCrJS2.js +1 -0
- package/dist/demos/assets/web-usb-De-CD5AK.js +1 -0
- package/dist/demos/assets/websocket-pLqbbDwD.js +1 -0
- package/dist/demos/web-bluetooth.html +408 -0
- package/dist/demos/web-serial.html +483 -0
- package/dist/demos/web-usb.html +526 -0
- package/dist/demos/websocket.html +498 -0
- package/dist/dist/webserial-core.umd.js +1 -0
- package/dist/favicon.svg +10 -0
- package/dist/images/cover.svg +145 -0
- package/dist/images/icons/adapters.svg +13 -0
- package/dist/images/icons/events.svg +4 -0
- package/dist/images/icons/parsers.svg +7 -0
- package/dist/images/icons/queue.svg +8 -0
- package/dist/images/icons/reconnect.svg +7 -0
- package/dist/images/icons/typed.svg +5 -0
- package/dist/images/logo.svg +10 -0
- package/dist/robots.txt +4 -0
- package/dist/types/index.d.ts +6 -30
- package/dist/webserial-core.cjs +1 -1
- package/dist/webserial-core.mjs +7 -1
- package/dist/webserial-core.umd.js +1 -1
- package/package.json +12 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{S as e,_ as t,a as n,b as r,c as i,d as a,f as o,g as ee,h as te,i as s,m as ne,n as c,o as re,p as l,r as u,t as d,v as ie,x as ae,y as f}from"./demo-shared-BVQKG6NC.js";var p=[],m=[],oe=class extends e{_hsCmd;_hsCmdMode;_hsExpect;_hsExpectMode;constructor(e,t,n,r,i){super(e),this._hsCmd=t,this._hsCmdMode=n,this._hsExpect=r,this._hsExpectMode=i}async handshake(){if(!this._hsCmd||(this._hsCmdMode===`hex`?await this.send(P(this._hsCmd)):await this.send(u(this._hsCmd)),!this._hsExpect))return!0;let e=this._hsExpect.trim();return new Promise(t=>{let n=r=>{if(this.off(`serial:data`,n),this._hsExpectMode===`hex`){let e=new TextEncoder().encode(String(r)),n=P(this._hsExpect);t(e.length===n.length&&e.every((e,t)=>e===n[t]))}else t(String(r).trim()===e)};this.on(`serial:data`,n)})}},h=e=>document.getElementById(e),g=h(`messages`),_=h(`btn-connect`),v=h(`btn-disconnect`),y=h(`btn-send`),b=h(`input-send`),x=h(`mode-toggle`),se=h(`status-dot`),S=h(`status-text`),ce=h(`console-dot`),C=h(`console-text`),w=h(`sidebar`),T=h(`code-panel`),E=h(`code-view`),D=h(`code-tab`),le=h(`menu-btn`),ue=h(`code-toggle-btn`),O=h(`theme-btn`),de=h(`clear-btn`),k=h(`copy-btn`),fe=h(`dl-btn`),pe=h(`cfg-export-btn`),A=h(`cfg-import-input`);function j(){let e=e=>(h(e)?.value??``).trim(),t=(t,n)=>parseInt(e(t))||n,n=e=>h(e)?.value??``,r=e=>h(e)?.checked??!1,i=e(`cfg-vendor`),a=e(`cfg-product`),o=[];if(i||a){let e={};i&&(e.usbVendorId=parseInt(i,16)),a&&(e.usbProductId=parseInt(a,16)),o.push(e)}return{baudRate:t(`cfg-baud`,9600),dataBits:t(`cfg-databits`,8),stopBits:t(`cfg-stopbits`,1),parity:n(`cfg-parity`)||`none`,flowControl:n(`cfg-flow`)||`none`,bufferSize:t(`cfg-bufsize`,255),commandTimeout:t(`cfg-timeout`,3e3),autoReconnect:r(`cfg-autoreconnect`),autoReconnectInterval:t(`cfg-reconnect-ms`,1500),handshakeTimeout:t(`cfg-handshake`,2e3),delimiter:e(`cfg-delim`),prepend:e(`cfg-prepend`),append:e(`cfg-append`),hsCmd:e(`cfg-hs-cmd`),hsCmdMode:n(`cfg-hs-cmd-mode`)||`text`,hsExpect:e(`cfg-hs-expect`),hsExpectMode:n(`cfg-hs-expect-mode`)||`text`,filters:o}}function me(e){let t=(e,t)=>{let n=document.getElementById(e);n&&(n instanceof HTMLInputElement&&n.type===`checkbox`?n.checked=!!t:(n instanceof HTMLInputElement||n instanceof HTMLSelectElement)&&(n.value=String(t??``)))};t(`cfg-baud`,e.baudRate??9600),t(`cfg-databits`,e.dataBits??8),t(`cfg-stopbits`,e.stopBits??1),t(`cfg-parity`,e.parity??`none`),t(`cfg-flow`,e.flowControl??`none`),t(`cfg-bufsize`,e.bufferSize??255),t(`cfg-timeout`,e.commandTimeout??3e3),t(`cfg-handshake`,e.handshakeTimeout??2e3),t(`cfg-reconnect-ms`,e.autoReconnectInterval??1500),t(`cfg-autoreconnect`,e.autoReconnect??!1),t(`cfg-vendor`,e.filters?.[0]?.usbVendorId==null?``:e.filters[0].usbVendorId.toString(16)),t(`cfg-product`,e.filters?.[0]?.usbProductId==null?``:e.filters[0].usbProductId.toString(16)),t(`cfg-delim`,e.delimiter??`\\n`),t(`cfg-prepend`,e.prepend??``),t(`cfg-append`,e.append??``),t(`cfg-hs-cmd`,e.hsCmd??``),t(`cfg-hs-cmd-mode`,e.hsCmdMode??`text`),t(`cfg-hs-expect`,e.hsExpect??``),t(`cfg-hs-expect-mode`,e.hsExpectMode??`text`)}function M(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,``).replace(/^[^a-zA-Z_$]/,`C`);return t.charAt(0).toUpperCase()+t.slice(1)||`MyDevice`}function N(e){return Array.from(e).map(e=>e.toString(16).toUpperCase().padStart(2,`0`)).join(` `)}function P(e){let t=e.replace(/\s+/g,``);if(t.length%2!=0)throw Error(`Odd number of hex characters.`);let n=new Uint8Array(t.length/2);for(let e=0;e<t.length;e+=2)n[e/2]=parseInt(t.substring(e,e+2),16);return n}function F(e,t){[se,ce].forEach(t=>{t&&(t.className=`status-dot`,e!==`disconnected`&&t.classList.add(e))}),S&&(S.textContent=t),C&&(C.textContent=t)}var I=null;function L(){I&&clearTimeout(I),I=setTimeout(()=>{let e=j(),t=M((h(`dl-name`)?.value??`MySerialDevice`).trim()),n=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,r=n?`ts`:`js`;ie(E,i(e,t,n,p,m)),D&&(D.textContent=`${t.substring(0,10).toLowerCase()}.${r}`)},180)}var R=l();O&&(O.textContent=R===`dark`?`☀️`:`🌙`),le?.addEventListener(`click`,()=>w.classList.toggle(`collapsed`)),ue?.addEventListener(`click`,()=>T.classList.toggle(`collapsed`));var z=h(`resize-handle`);if(z){let e=0,t=0,n=n=>{let r=Math.max(180,Math.min(700,t+(e-n.clientX)));document.documentElement.style.setProperty(`--code-w`,`${r}px`)},r=()=>{z.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};z.addEventListener(`pointerdown`,i=>{e=i.clientX,t=T.getBoundingClientRect().width,z.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}var B=h(`sidebar-resize-handle`);if(B){let e=0,t=0,n=n=>{let r=Math.max(200,Math.min(600,t+(n.clientX-e)));document.documentElement.style.setProperty(`--sidebar-w`,`${r}px`)},r=()=>{B.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};B.addEventListener(`pointerdown`,i=>{e=i.clientX,t=w.getBoundingClientRect().width,B.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}window.innerWidth<=960&&T.classList.add(`collapsed`),window.innerWidth<=640&&w.classList.add(`collapsed`),O?.addEventListener(`click`,()=>{let e=f();O&&(O.textContent=e===`dark`?`☀️`:`🌙`)}),de?.addEventListener(`click`,()=>c(g)),k?.addEventListener(`click`,async()=>{let e=E?.textContent??``;try{await navigator.clipboard.writeText(e),k&&(k.textContent=`Copied!`,k.classList.add(`copied`),setTimeout(()=>{k.textContent=`Copy`,k.classList.remove(`copied`)},1500))}catch{}}),fe?.addEventListener(`click`,()=>{let e=j(),r=(h(`dl-name`)?.value??`my-device`).trim(),a=M(r),o=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,s=document.querySelector(`input[name='dl-type']:checked`)?.value??`project`,c=o?`ts`:`js`,l=i(e,a,o,p,m);s===`project`?re([{name:`device.${c}`,content:l},{name:`package.json`,content:te(r,o,`serial`)},{name:`index.html`,content:ne(a,c,`Web Serial`)},{name:`README.md`,content:ee(a,c,`Web Serial`,`Requires a Chromium browser with Web Serial API support.`)},...o?[{name:`tsconfig.json`,content:t()}]:[]],`${r}-project-${c}`):n(`${r}.${c}`,l)}),pe?.addEventListener(`click`,()=>{let e=(h(`dl-name`)?.value??`my-device`).trim(),t=M(e);s({$version:1,provider:`serial`,className:e,language:document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`,dlType:document.querySelector(`input[name='dl-type']:checked`)?.value??`file`,cfg:j(),commands:p,listeners:m},t+`-config`)}),A?.addEventListener(`change`,()=>{let e=A.files?.[0];if(!e)return;let t=new FileReader;t.onload=e=>{try{let t=JSON.parse(e.target?.result);if(t.$version!==1||t.provider!==`serial`)return;let n=h(`dl-name`);n&&(n.value=t.className),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>{e.checked=e.value===t.language}),document.querySelectorAll(`input[name='dl-type']`).forEach(e=>{e.checked=e.value===t.dlType}),me(t.cfg),p=t.commands.map(e=>({...e,id:crypto.randomUUID()})),m=t.listeners.map(e=>({...e,id:crypto.randomUUID()})),Q(),$(),L()}catch{}A.value=``},t.readAsText(e)}),[`cfg-baud`,`cfg-databits`,`cfg-stopbits`,`cfg-parity`,`cfg-flow`,`cfg-bufsize`,`cfg-timeout`,`cfg-handshake`,`cfg-reconnect-ms`,`cfg-autoreconnect`,`cfg-vendor`,`cfg-product`,`cfg-delim`,`cfg-prepend`,`cfg-append`,`cfg-hs-cmd`,`cfg-hs-cmd-mode`,`cfg-hs-expect`,`cfg-hs-expect-mode`,`dl-name`].forEach(e=>{let t=document.getElementById(e);t?.addEventListener(`change`,L),t?.addEventListener(`input`,L)}),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>e.addEventListener(`change`,L)),L();var V=`text`;x?.addEventListener(`click`,()=>{V=V===`text`?`hex`:`text`,x.textContent=V===`text`?`TXT`:`HEX`,b.placeholder=V===`text`?`Type a command, e.g. LED_ON`:`Hex bytes, e.g. FF 01 A3`});var H=null;function U(e){y.disabled=!e,b.disabled=!e,v.disabled=!e,_.disabled=e}_?.addEventListener(`click`,async()=>{if(H){try{await H.disconnect()}catch{}H=null}let e=j(),t=e.delimiter?u(e.delimiter):``;H=new oe({baudRate:e.baudRate,dataBits:e.dataBits,stopBits:e.stopBits,parity:e.parity,flowControl:e.flowControl,bufferSize:e.bufferSize,commandTimeout:e.commandTimeout,parser:t?ae(t):r(),autoReconnect:e.autoReconnect,autoReconnectInterval:e.autoReconnectInterval,handshakeTimeout:e.handshakeTimeout,filters:e.filters??[]},e.hsCmd,e.hsCmdMode,e.hsExpect,e.hsExpectMode),H.on(`serial:connecting`,()=>{F(`connecting`,`Connecting…`),_.disabled=!0,d(g,`Connecting to serial device…`,{kind:`system`})}),H.on(`serial:connected`,()=>{F(`connected`,`Connected`),U(!0),d(g,`Connected via Web Serial!`,{kind:`system`})}),H.on(`serial:disconnected`,()=>{F(`disconnected`,`Disconnected`),U(!1),d(g,`Disconnected.`,{kind:`system`}),H=null}),H.on(`serial:data`,e=>{d(g,String(e),{kind:`received`,label:`Device`})}),H.on(`serial:error`,e=>{F(`error`,`Error`),d(g,`Error: ${e.message}`,{kind:`error`}),_.disabled=!1}),H.on(`serial:need-permission`,()=>{F(`error`,`Permission denied`),d(g,`Permission denied — select a port and allow access.`,{kind:`error`}),_.disabled=!1}),H.on(`serial:timeout`,e=>{d(g,`Timeout: ${N(e)}`,{kind:`error`})}),H.on(`serial:reconnecting`,()=>{F(`connecting`,`Reconnecting…`),d(g,`Auto-reconnecting…`,{kind:`system`})});try{await H.connect()}catch{}}),v?.addEventListener(`click`,async()=>{await H?.disconnect()});async function W(){let e=b.value.trim();if(!e||!H)return;let t=j(),n=t.append?u(t.append):t.delimiter?u(t.delimiter):``;try{if(V===`hex`){let t=P(e);d(g,`HEX: ${N(t)}`,{kind:`sent`,label:`You`}),await H.send(t)}else{let r=t.prepend+e+n;d(g,e,{kind:`sent`,label:`You`}),await H.send(r)}b.value=``,b.focus()}catch(e){d(g,`Send error: ${e instanceof Error?e.message:String(e)}`,{kind:`error`})}}y?.addEventListener(`click`,W),b?.addEventListener(`keydown`,e=>{e.key===`Enter`&&W()});var G=h(`cmd-name`),K=h(`cmd-value`),he=h(`cmd-mode`),ge=h(`cmd-add`),q=h(`cmd-list`),J=h(`lst-name`),Y=h(`lst-pattern`),X=h(`lst-match`),_e=h(`lst-add`),Z=h(`lst-list`);function Q(){if(q){q.innerHTML=``;for(let e of p){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.mode.toUpperCase();let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.value;let a=document.createElement(`button`);a.className=`chip-send`,a.title=`Send now`,a.textContent=`▶`,a.addEventListener(`click`,()=>{if(H)if(e.mode===`hex`){let t=P(e.value);H.send(t).catch(()=>{}),d(g,`HEX: ${N(t)}`,{kind:`sent`,label:`You`})}else{let t=j(),n=t.append?u(t.append):u(t.delimiter);H.send(t.prepend+e.value+n).catch(()=>{}),d(g,e.name,{kind:`sent`,label:`You`})}});let o=document.createElement(`button`);o.className=`chip-del`,o.title=`Remove`,o.textContent=`×`,o.addEventListener(`click`,()=>{p=p.filter(t=>t.id!==e.id),Q(),L()}),t.append(n,r,i,a,o),q.appendChild(t)}}}function $(){if(Z){Z.innerHTML=``;for(let e of m){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.match;let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.pattern;let a=document.createElement(`button`);a.className=`chip-del`,a.title=`Remove`,a.textContent=`×`,a.addEventListener(`click`,()=>{m=m.filter(t=>t.id!==e.id),$(),L()}),t.append(n,r,i,a),Z.appendChild(t)}}}ge?.addEventListener(`click`,()=>{let e=G?.value.trim(),t=K?.value.trim();if(!e||!t)return;let n=he?.value??`text`;p.push({id:crypto.randomUUID(),name:e,value:t,mode:n}),G&&(G.value=``),K&&(K.value=``),Q(),L()}),_e?.addEventListener(`click`,()=>{let e=J?.value.trim(),t=Y?.value.trim();if(!e||!t)return;let n=X?.value??`exact`;m.push({id:crypto.randomUUID(),name:e,pattern:t,match:n}),J&&(J.value=``),Y&&(Y.value=``),$(),L()}),d(g,`Web Serial demo ready — configure settings and click Connect.`,{kind:`system`}),a(),o();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{S as e,_ as t,a as n,b as r,d as i,f as a,g as o,h as ee,i as s,l as c,m as te,n as l,o as ne,p as u,r as d,t as f,v as re,x as ie,y as ae}from"./demo-shared-BVQKG6NC.js";var oe=32,se=34,p=0,ce=30,le=3,ue=7,de=1,fe=0,pe=771,me=768,he=255,ge=8,_e=`none`,m=1,ve=[16,8,7,6,5],ye=[1,2],be=[`none`,`even`,`odd`],xe=[`none`,`odd`,`even`],Se=[1,1.5,2],h={usbControlInterfaceClass:2,usbTransferInterfaceClass:10,protocol:void 0};function g(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces)if(e.alternates[0]?.interfaceClass===t)return e;return null}function _(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces){let n=e.alternates[0];if(!n||n.interfaceClass!==t)continue;let r=n.endpoints.some(e=>e.direction===`in`),i=n.endpoints.some(e=>e.direction===`out`);if(r&&i)return e}return null}function v(e,t){let n=e.alternates[0];if(n){for(let e of n.endpoints)if(e.direction===t)return e}throw TypeError(`Interface ${e.interfaceNumber} does not have an ${t} endpoint.`)}function Ce(e,t){return t===2?`cdc_acm`:e.vendorId===4292?`cp210x`:`none`}var we=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}pull(e){(async()=>{let t=this.endpoint_.packetSize;try{let n=await this.device_.transferIn(this.endpoint_.endpointNumber,t);if(n.status!==`ok`){e.error(`USB error: ${n.status}`),this.onError_();return}if(n.data?.buffer&&n.data.byteLength>0){let t=new Uint8Array(n.data.buffer,n.data.byteOffset,n.data.byteLength);t.length>0&&e.enqueue(t)}}catch(t){e.error(String(t)),this.onError_()}})()}},Te=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}async write(e,t){try{let n=await this.device_.transferOut(this.endpoint_.endpointNumber,e.buffer);n.status!==`ok`&&(t.error(n.status),this.onError_())}catch(e){t.error(String(e)),this.onError_()}}},y=class{device_;protocol_;controlInterface_;transferInterface_;inEndpoint_;outEndpoint_;serialOptions_;readable_=null;writable_=null;cdcOutputSignals_={dataTerminalReady:!1,requestToSend:!1,break:!1};constructor(e,t){this.device_=e;let n={...h,...t};this.protocol_=n.protocol??Ce(e,n.usbControlInterfaceClass);let r=n.usbControlInterfaceClass,i=n.usbTransferInterfaceClass;if(r===i){let t=_(e,i);if(!t)throw TypeError(`Unable to find interface with class ${i} that has both IN and OUT endpoints.`);this.controlInterface_=t,this.transferInterface_=t}else{let t=g(e,r);if(!t)throw TypeError(`Unable to find control interface with class ${r}.`);let n=_(e,i)??g(e,i);if(!n)throw TypeError(`Unable to find transfer interface with class ${i}.`);this.controlInterface_=t,this.transferInterface_=n}this.inEndpoint_=v(this.transferInterface_,`in`),this.outEndpoint_=v(this.transferInterface_,`out`)}get readable(){return!this.readable_&&this.device_.opened&&(this.readable_=new ReadableStream(new we(this.device_,this.inEndpoint_,()=>{this.readable_=null}),{highWaterMark:this.serialOptions_?.bufferSize??he})),this.readable_}get writable(){return!this.writable_&&this.device_.opened&&(this.writable_=new WritableStream(new Te(this.device_,this.outEndpoint_,()=>{this.writable_=null}),new ByteLengthQueuingStrategy({highWaterMark:this.serialOptions_?.bufferSize??he}))),this.writable_}async open(e){this.serialOptions_=e,this.validateOptions();try{switch(await this.device_.open(),this.device_.configuration===null&&await this.device_.selectConfiguration(1),await this.device_.claimInterface(this.controlInterface_.interfaceNumber),this.controlInterface_!==this.transferInterface_&&await this.device_.claimInterface(this.transferInterface_.interfaceNumber),this.protocol_){case`cdc_acm`:await this.cdcInit();break;case`cp210x`:await this.cp210xInit();break;case`none`:break}}catch(e){throw this.device_.opened&&await this.device_.close(),Error(`Error setting up device: `+(e instanceof Error?e.message:String(e)),{cause:e})}}async close(){let e=[];if(this.readable_&&e.push(this.readable_.cancel()),this.writable_&&e.push(this.writable_.abort()),await Promise.all(e),this.readable_=null,this.writable_=null,this.device_.opened){switch(this.protocol_){case`cdc_acm`:await this.cdcSetSignals({dataTerminalReady:!1,requestToSend:!1});break;case`cp210x`:await this.cp210xDeinit();break}await this.device_.close()}}async forget(){return this.device_.forget()}getInfo(){return{usbVendorId:this.device_.vendorId,usbProductId:this.device_.productId}}async cdcInit(){await this.cdcSetLineCoding(),await this.cdcSetSignals({dataTerminalReady:!0})}async cdcSetSignals(e){if(this.cdcOutputSignals_={...this.cdcOutputSignals_,...e},e.dataTerminalReady!==void 0||e.requestToSend!==void 0){let e=(this.cdcOutputSignals_.dataTerminalReady?1:0)|(this.cdcOutputSignals_.requestToSend?2:0);await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:se,value:e,index:this.controlInterface_.interfaceNumber})}}async cdcSetLineCoding(){let e=new ArrayBuffer(7),t=new DataView(e);if(t.setUint32(0,this.serialOptions_.baudRate,!0),t.setUint8(4,Se.indexOf(this.serialOptions_.stopBits??m)),t.setUint8(5,xe.indexOf(this.serialOptions_.parity??_e)),t.setUint8(6,this.serialOptions_.dataBits??ge),(await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:oe,value:0,index:this.controlInterface_.interfaceNumber},e)).status!==`ok`)throw new DOMException(`Failed to set line coding.`,`NetworkError`)}async cp210xInit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:p,value:de,index:e});let t=new ArrayBuffer(4);new DataView(t).setUint32(0,this.serialOptions_.baudRate,!0),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:ce,value:0,index:e},t);let n=this.serialOptions_.dataBits??ge,r={none:0,odd:16,even:32}[this.serialOptions_.parity??_e]??0,i=({1:0,2:2}[this.serialOptions_.stopBits??m]??0)<<8|r|n;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:le,value:i,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:ue,value:pe,index:e})}async cp210xDeinit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:ue,value:me,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:p,value:fe,index:e})}validateOptions(){if(this.serialOptions_.baudRate%1!=0)throw RangeError(`Invalid baud rate: ${this.serialOptions_.baudRate}`);if(this.serialOptions_.dataBits!==void 0&&!ve.includes(this.serialOptions_.dataBits))throw RangeError(`Invalid dataBits: ${this.serialOptions_.dataBits}`);if(this.serialOptions_.stopBits!==void 0&&!ye.includes(this.serialOptions_.stopBits))throw RangeError(`Invalid stopBits: ${this.serialOptions_.stopBits}`);if(this.serialOptions_.parity!==void 0&&!be.includes(this.serialOptions_.parity))throw RangeError(`Invalid parity: ${this.serialOptions_.parity}`)}},Ee=class{options_;constructor(e){this.options_={...h,...e}}async requestPort(e,t){let n={...this.options_,...t},r=[];if(e?.filters&&e.filters.length>0)for(let t of e.filters){let e={};t.usbVendorId!==void 0&&(e.vendorId=t.usbVendorId),t.usbProductId!==void 0&&(e.productId=t.usbProductId),n.usbControlInterfaceClass!==void 0&&n.usbControlInterfaceClass!==255?e.classCode=n.usbControlInterfaceClass:e.vendorId===void 0&&e.productId===void 0&&(e.classCode=n.usbControlInterfaceClass??2),r.push(e)}else r.push({classCode:n.usbControlInterfaceClass??2});return new y(await navigator.usb.requestDevice({filters:r}),n)}async getPorts(e){let t={...this.options_,...e},n=await navigator.usb.getDevices(),r=[];for(let e of n)try{let n=new y(e,t);r.push(n)}catch{}return r}},b=[],x=[],De=class extends e{_hsCmd;_hsCmdMode;_hsExpect;_hsExpectMode;constructor(e,t,n,r,i){super(e),this._hsCmd=t,this._hsCmdMode=n,this._hsExpect=r,this._hsExpectMode=i}async handshake(){if(!this._hsCmd||(this._hsCmdMode===`hex`?await this.send(R(this._hsCmd)):await this.send(d(this._hsCmd)),!this._hsExpect))return!0;let e=this._hsExpect.trim();return new Promise(t=>{let n=r=>{if(this.off(`serial:data`,n),this._hsExpectMode===`hex`){let e=new TextEncoder().encode(String(r)),n=R(this._hsExpect);t(e.length===n.length&&e.every((e,t)=>e===n[t]))}else t(String(r).trim()===e)};this.on(`serial:data`,n)})}},S=e=>document.getElementById(e),C=S(`messages`),w=S(`btn-connect`),Oe=S(`btn-disconnect`),T=S(`btn-send`),E=S(`input-send`),D=S(`mode-toggle`),ke=S(`status-dot`),O=S(`status-text`),Ae=S(`console-dot`),k=S(`console-text`),A=S(`sidebar`),j=S(`code-panel`),je=S(`code-view`),Me=S(`code-tab`),Ne=S(`menu-btn`),Pe=S(`code-toggle-btn`),M=S(`theme-btn`),Fe=S(`clear-btn`),N=S(`copy-btn`),Ie=S(`dl-btn`),Le=S(`cfg-export-btn`),P=S(`cfg-import-input`);function F(){let e=e=>(S(e)?.value??``).trim(),t=(t,n)=>parseInt(e(t))||n,n=e=>S(e)?.value??``,r=e=>S(e)?.checked??!1,i=e(`cfg-vendor`),a=e(`cfg-product`),o=[];if(i||a){let e={};i&&(e.usbVendorId=parseInt(i,16)),a&&(e.usbProductId=parseInt(a,16)),o.push(e)}return{usbControlInterfaceClass:t(`cfg-ctrl-class`,255),usbTransferInterfaceClass:t(`cfg-xfer-class`,255),baudRate:t(`cfg-baud`,9600),dataBits:t(`cfg-databits`,8),stopBits:t(`cfg-stopbits`,1),parity:n(`cfg-parity`)||`none`,flowControl:n(`cfg-flow`)||`none`,bufferSize:t(`cfg-bufsize`,255),commandTimeout:t(`cfg-timeout`,3e3),autoReconnect:r(`cfg-autoreconnect`),autoReconnectInterval:t(`cfg-reconnect-ms`,1500),handshakeTimeout:t(`cfg-handshake`,2e3),delimiter:e(`cfg-delim`),prepend:e(`cfg-prepend`),append:e(`cfg-append`),hsCmd:e(`cfg-hs-cmd`),hsCmdMode:n(`cfg-hs-cmd-mode`)||`text`,hsExpect:e(`cfg-hs-expect`),hsExpectMode:n(`cfg-hs-expect-mode`)||`text`,filters:o}}function Re(e){let t=(e,t)=>{let n=document.getElementById(e);n&&(n instanceof HTMLInputElement&&n.type===`checkbox`?n.checked=!!t:(n instanceof HTMLInputElement||n instanceof HTMLSelectElement)&&(n.value=String(t??``)))};t(`cfg-ctrl-class`,e.usbControlInterfaceClass??255),t(`cfg-xfer-class`,e.usbTransferInterfaceClass??255),t(`cfg-baud`,e.baudRate??9600),t(`cfg-databits`,e.dataBits??8),t(`cfg-stopbits`,e.stopBits??1),t(`cfg-parity`,e.parity??`none`),t(`cfg-flow`,e.flowControl??`none`),t(`cfg-bufsize`,e.bufferSize??255),t(`cfg-timeout`,e.commandTimeout??3e3),t(`cfg-handshake`,e.handshakeTimeout??2e3),t(`cfg-reconnect-ms`,e.autoReconnectInterval??1500),t(`cfg-autoreconnect`,e.autoReconnect??!1),t(`cfg-vendor`,e.filters?.[0]?.usbVendorId==null?``:e.filters[0].usbVendorId.toString(16)),t(`cfg-product`,e.filters?.[0]?.usbProductId==null?``:e.filters[0].usbProductId.toString(16)),t(`cfg-delim`,e.delimiter??`\\n`),t(`cfg-prepend`,e.prepend??``),t(`cfg-append`,e.append??``),t(`cfg-hs-cmd`,e.hsCmd??``),t(`cfg-hs-cmd-mode`,e.hsCmdMode??`text`),t(`cfg-hs-expect`,e.hsExpect??``),t(`cfg-hs-expect-mode`,e.hsExpectMode??`text`)}function I(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,``).replace(/^[^a-zA-Z_$]/,`C`);return t.charAt(0).toUpperCase()+t.slice(1)||`MyDevice`}function L(e){return Array.from(e).map(e=>e.toString(16).toUpperCase().padStart(2,`0`)).join(` `)}function R(e){let t=e.replace(/\s+/g,``);if(t.length%2!=0)throw Error(`Odd number of hex characters.`);let n=new Uint8Array(t.length/2);for(let e=0;e<t.length;e+=2)n[e/2]=parseInt(t.substring(e,e+2),16);return n}function z(e,t){[ke,Ae].forEach(t=>{t&&(t.className=`status-dot`,e!==`disconnected`&&t.classList.add(e))}),O&&(O.textContent=t),k&&(k.textContent=t)}var B=null;function V(){B&&clearTimeout(B),B=setTimeout(()=>{let e=F(),t=I((S(`dl-name`)?.value??`MyUsbDevice`).trim()),n=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,r=n?`ts`:`js`;re(je,c(e,t,n,b,x)),Me&&(Me.textContent=`${t.substring(0,10).toLowerCase()}.${r}`)},180)}var ze=u();M&&(M.textContent=ze===`dark`?`☀️`:`🌙`),Ne?.addEventListener(`click`,()=>A.classList.toggle(`collapsed`)),Pe?.addEventListener(`click`,()=>j.classList.toggle(`collapsed`));var H=S(`resize-handle`);if(H){let e=0,t=0,n=n=>{let r=Math.max(180,Math.min(700,t+(e-n.clientX)));document.documentElement.style.setProperty(`--code-w`,`${r}px`)},r=()=>{H.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};H.addEventListener(`pointerdown`,i=>{e=i.clientX,t=j.getBoundingClientRect().width,H.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}var U=S(`sidebar-resize-handle`);if(U){let e=0,t=0,n=n=>{let r=Math.max(200,Math.min(600,t+(n.clientX-e)));document.documentElement.style.setProperty(`--sidebar-w`,`${r}px`)},r=()=>{U.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};U.addEventListener(`pointerdown`,i=>{e=i.clientX,t=A.getBoundingClientRect().width,U.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}window.innerWidth<=960&&j.classList.add(`collapsed`),window.innerWidth<=640&&A.classList.add(`collapsed`),M?.addEventListener(`click`,()=>{let e=ae();M&&(M.textContent=e===`dark`?`☀️`:`🌙`)}),Fe?.addEventListener(`click`,()=>l(C)),N?.addEventListener(`click`,async()=>{let e=je?.textContent??``;try{await navigator.clipboard.writeText(e),N&&(N.textContent=`Copied!`,N.classList.add(`copied`),setTimeout(()=>{N.textContent=`Copy`,N.classList.remove(`copied`)},1500))}catch{}}),Ie?.addEventListener(`click`,()=>{let e=F(),r=(S(`dl-name`)?.value??`my-usb-device`).trim(),i=I(r),a=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,s=document.querySelector(`input[name='dl-type']:checked`)?.value??`project`,l=a?`ts`:`js`,u=c(e,i,a,b,x);s===`project`?ne([{name:`device.${l}`,content:u},{name:`package.json`,content:ee(r,a,`usb`)},{name:`index.html`,content:te(i,l,`WebUSB`)},{name:`README.md`,content:o(i,l,`WebUSB`,`Requires a Chromium browser. Common USB-to-serial chips: CP2102 (0x10c4/0xea60), CH340 (0x1a86/0x7523).`)},...a?[{name:`tsconfig.json`,content:t()}]:[]],`${r}-project-${l}`):n(`${r}.${l}`,u)}),Le?.addEventListener(`click`,()=>{let e=(S(`dl-name`)?.value??`my-usb-device`).trim(),t=I(e);s({$version:1,provider:`usb`,className:e,language:document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`,dlType:document.querySelector(`input[name='dl-type']:checked`)?.value??`file`,cfg:F(),commands:b,listeners:x},t+`-config`)}),P?.addEventListener(`change`,()=>{let e=P.files?.[0];if(!e)return;let t=new FileReader;t.onload=e=>{try{let t=JSON.parse(e.target?.result);if(t.$version!==1||t.provider!==`usb`)return;let n=S(`dl-name`);n&&(n.value=t.className),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>{e.checked=e.value===t.language}),document.querySelectorAll(`input[name='dl-type']`).forEach(e=>{e.checked=e.value===t.dlType}),Re(t.cfg),b=t.commands.map(e=>({...e,id:crypto.randomUUID()})),x=t.listeners.map(e=>({...e,id:crypto.randomUUID()})),Q(),$(),V()}catch{}P.value=``},t.readAsText(e)}),[`cfg-ctrl-class`,`cfg-xfer-class`,`cfg-baud`,`cfg-databits`,`cfg-stopbits`,`cfg-parity`,`cfg-flow`,`cfg-bufsize`,`cfg-timeout`,`cfg-handshake`,`cfg-reconnect-ms`,`cfg-autoreconnect`,`cfg-vendor`,`cfg-product`,`cfg-delim`,`cfg-prepend`,`cfg-append`,`cfg-hs-cmd`,`cfg-hs-cmd-mode`,`cfg-hs-expect`,`cfg-hs-expect-mode`,`dl-name`].forEach(e=>{let t=document.getElementById(e);t?.addEventListener(`change`,V),t?.addEventListener(`input`,V)}),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>e.addEventListener(`change`,V)),V();var W=`text`;D?.addEventListener(`click`,()=>{W=W===`text`?`hex`:`text`,D.textContent=W===`text`?`TXT`:`HEX`,E.placeholder=W===`text`?`Type a command, e.g. LED_ON`:`Hex bytes, e.g. FF 01 A3`});var G=null;function Be(e){T.disabled=!e,E.disabled=!e,Oe.disabled=!e,w.disabled=e}w?.addEventListener(`click`,async()=>{if(G){try{await G.disconnect()}catch{}G=null}let t=F();e.setProvider(new Ee({usbControlInterfaceClass:t.usbControlInterfaceClass,usbTransferInterfaceClass:t.usbTransferInterfaceClass}));let n=t.delimiter?d(t.delimiter):``;G=new De({baudRate:t.baudRate,dataBits:t.dataBits,stopBits:t.stopBits,parity:t.parity,flowControl:t.flowControl,bufferSize:t.bufferSize,commandTimeout:t.commandTimeout,parser:n?ie(n):r(),autoReconnect:t.autoReconnect,autoReconnectInterval:t.autoReconnectInterval,handshakeTimeout:t.handshakeTimeout,filters:t.filters??[]},t.hsCmd,t.hsCmdMode,t.hsExpect,t.hsExpectMode),G.on(`serial:connecting`,()=>{z(`connecting`,`Connecting…`),w.disabled=!0,f(C,`Connecting via WebUSB…`,{kind:`system`})}),G.on(`serial:connected`,()=>{z(`connected`,`Connected`),Be(!0),f(C,`Connected via WebUSB!`,{kind:`system`})}),G.on(`serial:disconnected`,()=>{z(`disconnected`,`Disconnected`),Be(!1),f(C,`Disconnected.`,{kind:`system`}),G=null}),G.on(`serial:data`,e=>{f(C,String(e),{kind:`received`,label:`Device`})}),G.on(`serial:error`,e=>{z(`error`,`Error`),f(C,`Error: ${e.message}`,{kind:`error`}),w.disabled=!1}),G.on(`serial:need-permission`,()=>{z(`error`,`Permission denied`),f(C,`Permission denied — allow access to the USB device.`,{kind:`error`}),w.disabled=!1}),G.on(`serial:timeout`,e=>{f(C,`Timeout: ${L(e)}`,{kind:`error`})}),G.on(`serial:reconnecting`,()=>{z(`connecting`,`Reconnecting…`),f(C,`Auto-reconnecting via WebUSB…`,{kind:`system`})});try{await G.connect()}catch{}}),Oe?.addEventListener(`click`,async()=>{await G?.disconnect()});async function Ve(){let e=E.value.trim();if(!e||!G)return;let t=F(),n=t.append?d(t.append):t.delimiter?d(t.delimiter):``;try{if(W===`hex`){let t=R(e);f(C,`HEX: ${L(t)}`,{kind:`sent`,label:`You`}),await G.send(t)}else{let r=t.prepend+e+n;f(C,e,{kind:`sent`,label:`You`}),await G.send(r)}E.value=``,E.focus()}catch(e){f(C,`Send error: ${e instanceof Error?e.message:String(e)}`,{kind:`error`})}}T?.addEventListener(`click`,Ve),E?.addEventListener(`keydown`,e=>{e.key===`Enter`&&Ve()});var K=S(`cmd-name`),q=S(`cmd-value`),He=S(`cmd-mode`),Ue=S(`cmd-add`),J=S(`cmd-list`),Y=S(`lst-name`),X=S(`lst-pattern`),We=S(`lst-match`),Ge=S(`lst-add`),Z=S(`lst-list`);function Q(){if(J){J.innerHTML=``;for(let e of b){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.mode.toUpperCase();let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.value;let a=document.createElement(`button`);a.className=`chip-send`,a.title=`Send now`,a.textContent=`▶`,a.addEventListener(`click`,()=>{if(G)if(e.mode===`hex`){let t=R(e.value);G.send(t).catch(()=>{}),f(C,`HEX: ${L(t)}`,{kind:`sent`,label:`You`})}else{let t=F(),n=t.append?d(t.append):t.delimiter?d(t.delimiter):``;G.send(t.prepend+e.value+n).catch(()=>{}),f(C,e.name,{kind:`sent`,label:`You`})}});let o=document.createElement(`button`);o.className=`chip-del`,o.title=`Remove`,o.textContent=`×`,o.addEventListener(`click`,()=>{b=b.filter(t=>t.id!==e.id),Q(),V()}),t.append(n,r,i,a,o),J.appendChild(t)}}}function $(){if(Z){Z.innerHTML=``;for(let e of x){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.match;let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.pattern;let a=document.createElement(`button`);a.className=`chip-del`,a.title=`Remove`,a.textContent=`×`,a.addEventListener(`click`,()=>{x=x.filter(t=>t.id!==e.id),$(),V()}),t.append(n,r,i,a),Z.appendChild(t)}}}Ue?.addEventListener(`click`,()=>{let e=K?.value.trim(),t=q?.value.trim();!e||!t||(b.push({id:crypto.randomUUID(),name:e,value:t,mode:He?.value??`text`}),K&&(K.value=``),q&&(q.value=``),Q(),V())}),Ge?.addEventListener(`click`,()=>{let e=Y?.value.trim(),t=X?.value.trim();!e||!t||(x.push({id:crypto.randomUUID(),name:e,pattern:t,match:We?.value??`exact`}),Y&&(Y.value=``),X&&(X.value=``),$(),V())}),f(C,`WebUSB demo ready — CP2102/ESP32 (0x10c4/0xea60), CH340/Arduino (0x1a86/0x7523). Configure and click Connect.`,{kind:`system`}),i(),a();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{S as e,_ as t,a as n,b as r,d as i,f as a,g as o,h as s,i as c,m as ee,n as l,o as te,p as u,r as d,t as f,u as p,v as ne,x as re,y as ie}from"./demo-shared-BVQKG6NC.js";function m(e){return new Promise((t,n)=>{e.addEventListener(`open`,()=>t(),{once:!0}),e.addEventListener(`error`,e=>n(e),{once:!0})})}function h(e,t){return new Promise(n=>{let r=i=>{let a=JSON.parse(i.data);a.type===t&&(e.removeEventListener(`message`,r),n(a.payload))};e.addEventListener(`message`,r)})}function g(e,t){let n=null,r=null;return{get readable(){return n},get writable(){return r},getInfo(){return{usbVendorId:t.vendorId,usbProductId:t.productId}},async open(i){e.send(JSON.stringify({type:`open`,path:t.path,baudRate:i.baudRate,dataBits:i.dataBits,stopBits:i.stopBits,parity:i.parity,parser:{type:`delimiter`,value:`\\n`}})),await h(e,`opened`);let a=[],o=null,s=!1;function c(e){let t=JSON.parse(e.data);if(t.type===`data`&&t.bytes){let e=new Uint8Array(t.bytes);o?o.enqueue(e):a.push(e)}t.type===`closed`&&(s=!0,o&&o.close())}e.addEventListener(`message`,c),n=new ReadableStream({start(e){o=e;for(let t of a)e.enqueue(t);a.length=0,s&&e.close()},cancel(){e.removeEventListener(`message`,c),o=null}}),r=new WritableStream({write(t){e.send(JSON.stringify({type:`write`,bytes:Array.from(t)}))}})},async close(){e.send(JSON.stringify({type:`close`})),n=null,r=null,e.close()}}}function ae(e){return{async requestPort(t){let n=new WebSocket(e);await m(n),n.send(JSON.stringify({type:`list-ports`,filters:t?.filters??[]}));let r=(await h(n,`port-list`))[0];if(!r)throw Error(`No ports available on the bridge server. Make sure the Node.js server is running and a device is connected.`);return g(n,r)},async getPorts(){let t=new WebSocket(e);return await m(t),t.send(JSON.stringify({type:`list-ports`,filters:[]})),(await h(t,`port-list`)).map(e=>g(t,e))}}}var _=[],v=[],oe=class extends e{_hsCmd;_hsCmdMode;_hsExpect;_hsExpectMode;constructor(e,t,n,r,i){super(e),this._hsCmd=t,this._hsCmdMode=n,this._hsExpect=r,this._hsExpectMode=i}async handshake(){if(!this._hsCmd||(this._hsCmdMode===`hex`?await this.send(I(this._hsCmd)):await this.send(d(this._hsCmd)),!this._hsExpect))return!0;let e=this._hsExpect.trim();return new Promise(t=>{let n=r=>{if(this.off(`serial:data`,n),this._hsExpectMode===`hex`){let e=new TextEncoder().encode(String(r)),n=I(this._hsExpect);t(e.length===n.length&&e.every((e,t)=>e===n[t]))}else t(String(r).trim()===e)};this.on(`serial:data`,n)})}},y=e=>document.getElementById(e),b=y(`messages`),x=y(`btn-connect`),S=y(`btn-disconnect`),C=y(`btn-send`),w=y(`input-send`),T=y(`mode-toggle`),se=y(`status-dot`),E=y(`status-text`),ce=y(`console-dot`),D=y(`console-text`),O=y(`sidebar`),k=y(`code-panel`),le=y(`code-view`),ue=y(`code-tab`),de=y(`menu-btn`),fe=y(`code-toggle-btn`),A=y(`theme-btn`),pe=y(`clear-btn`),j=y(`copy-btn`),me=y(`dl-btn`),he=y(`cfg-export-btn`),M=y(`cfg-import-input`);function N(){let e=e=>(y(e)?.value??``).trim(),t=(t,n)=>parseInt(e(t))||n,n=e=>y(e)?.value??``;return{wsUrl:e(`cfg-wsurl`)||`ws://localhost:8080`,baudRate:t(`cfg-baud`,9600),dataBits:t(`cfg-databits`,8),stopBits:t(`cfg-stopbits`,1),parity:n(`cfg-parity`)||`none`,flowControl:n(`cfg-flow`)||`none`,bufferSize:t(`cfg-bufsize`,255),commandTimeout:t(`cfg-timeout`,3e3),autoReconnect:(e=>y(e)?.checked??!1)(`cfg-autoreconnect`),autoReconnectInterval:t(`cfg-reconnect-ms`,1500),handshakeTimeout:t(`cfg-handshake`,2e3),delimiter:e(`cfg-delim`),prepend:e(`cfg-prepend`),append:e(`cfg-append`),hsCmd:e(`cfg-hs-cmd`),hsCmdMode:n(`cfg-hs-cmd-mode`)||`text`,hsExpect:e(`cfg-hs-expect`),hsExpectMode:n(`cfg-hs-expect-mode`)||`text`}}function ge(e){let t=(e,t)=>{let n=document.getElementById(e);n&&(n instanceof HTMLInputElement&&n.type===`checkbox`?n.checked=!!t:(n instanceof HTMLInputElement||n instanceof HTMLSelectElement)&&(n.value=String(t??``)))};t(`cfg-wsurl`,e.wsUrl??`ws://localhost:8080`),t(`cfg-baud`,e.baudRate??9600),t(`cfg-databits`,e.dataBits??8),t(`cfg-stopbits`,e.stopBits??1),t(`cfg-parity`,e.parity??`none`),t(`cfg-flow`,e.flowControl??`none`),t(`cfg-bufsize`,e.bufferSize??255),t(`cfg-timeout`,e.commandTimeout??3e3),t(`cfg-handshake`,e.handshakeTimeout??2e3),t(`cfg-reconnect-ms`,e.autoReconnectInterval??1500),t(`cfg-autoreconnect`,e.autoReconnect??!1),t(`cfg-delim`,e.delimiter??`\\n`),t(`cfg-prepend`,e.prepend??``),t(`cfg-append`,e.append??``),t(`cfg-hs-cmd`,e.hsCmd??``),t(`cfg-hs-cmd-mode`,e.hsCmdMode??`text`),t(`cfg-hs-expect`,e.hsExpect??``),t(`cfg-hs-expect-mode`,e.hsExpectMode??`text`)}function P(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,``).replace(/^[^a-zA-Z_$]/,`C`);return t.charAt(0).toUpperCase()+t.slice(1)||`MyDevice`}function F(e){return Array.from(e).map(e=>e.toString(16).toUpperCase().padStart(2,`0`)).join(` `)}function I(e){let t=e.replace(/\s+/g,``);if(t.length%2!=0)throw Error(`Odd number of hex characters.`);let n=new Uint8Array(t.length/2);for(let e=0;e<t.length;e+=2)n[e/2]=parseInt(t.substring(e,e+2),16);return n}function L(e,t){[se,ce].forEach(t=>{t&&(t.className=`status-dot`,e!==`disconnected`&&t.classList.add(e))}),E&&(E.textContent=t),D&&(D.textContent=t)}var R=null;function z(){R&&clearTimeout(R),R=setTimeout(()=>{let e=N(),t=P((y(`dl-name`)?.value??`MyWsDevice`).trim()),n=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,r=n?`ts`:`js`;ne(le,p(e,t,n,_,v)),ue&&(ue.textContent=`${t.substring(0,10).toLowerCase()}.${r}`)},180)}var _e=u();A&&(A.textContent=_e===`dark`?`☀️`:`🌙`),de?.addEventListener(`click`,()=>O.classList.toggle(`collapsed`)),fe?.addEventListener(`click`,()=>k.classList.toggle(`collapsed`));var B=y(`resize-handle`);if(B){let e=0,t=0,n=n=>{let r=Math.max(180,Math.min(700,t+(e-n.clientX)));document.documentElement.style.setProperty(`--code-w`,`${r}px`)},r=()=>{B.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};B.addEventListener(`pointerdown`,i=>{e=i.clientX,t=k.getBoundingClientRect().width,B.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}var V=y(`sidebar-resize-handle`);if(V){let e=0,t=0,n=n=>{let r=Math.max(200,Math.min(600,t+(n.clientX-e)));document.documentElement.style.setProperty(`--sidebar-w`,`${r}px`)},r=()=>{V.classList.remove(`dragging`),document.removeEventListener(`pointermove`,n)};V.addEventListener(`pointerdown`,i=>{e=i.clientX,t=O.getBoundingClientRect().width,V.classList.add(`dragging`),document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{once:!0}),i.preventDefault()})}window.innerWidth<=960&&k.classList.add(`collapsed`),window.innerWidth<=640&&O.classList.add(`collapsed`),A?.addEventListener(`click`,()=>{let e=ie();A&&(A.textContent=e===`dark`?`☀️`:`🌙`)}),pe?.addEventListener(`click`,()=>l(b)),j?.addEventListener(`click`,async()=>{let e=le?.textContent??``;try{await navigator.clipboard.writeText(e),j&&(j.textContent=`Copied!`,j.classList.add(`copied`),setTimeout(()=>{j.textContent=`Copy`,j.classList.remove(`copied`)},1500))}catch{}}),me?.addEventListener(`click`,()=>{let e=N(),r=(y(`dl-name`)?.value??`my-ws-device`).trim(),i=P(r),a=(document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`)===`ts`,c=document.querySelector(`input[name='dl-type']:checked`)?.value??`project`,l=a?`ts`:`js`,u=p(e,i,a,_,v);c===`project`?te([{name:`device.${l}`,content:u},{name:`package.json`,content:s(r,a,`ws`)},{name:`index.html`,content:ee(i,l,`WebSocket`)},{name:`README.md`,content:o(i,l,`WebSocket`,"Requires a WebSocket-to-serial bridge. Start the bridge: `cd demos/websocket && node server.js`")},...a?[{name:`tsconfig.json`,content:t()}]:[]],`${r}-project-${l}`):n(`${r}.${l}`,u)}),he?.addEventListener(`click`,()=>{let e=(y(`dl-name`)?.value??`my-ws-device`).trim(),t=P(e);c({$version:1,provider:`ws`,className:e,language:document.querySelector(`input[name='dl-lang']:checked`)?.value??`ts`,dlType:document.querySelector(`input[name='dl-type']:checked`)?.value??`file`,cfg:N(),commands:_,listeners:v},t+`-config`)}),M?.addEventListener(`change`,()=>{let e=M.files?.[0];if(!e)return;let t=new FileReader;t.onload=e=>{try{let t=JSON.parse(e.target?.result);if(t.$version!==1||t.provider!==`ws`)return;let n=y(`dl-name`);n&&(n.value=t.className),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>{e.checked=e.value===t.language}),document.querySelectorAll(`input[name='dl-type']`).forEach(e=>{e.checked=e.value===t.dlType}),ge(t.cfg),_=t.commands.map(e=>({...e,id:crypto.randomUUID()})),v=t.listeners.map(e=>({...e,id:crypto.randomUUID()})),Q(),$(),z()}catch{}M.value=``},t.readAsText(e)}),[`cfg-wsurl`,`cfg-baud`,`cfg-databits`,`cfg-stopbits`,`cfg-parity`,`cfg-flow`,`cfg-bufsize`,`cfg-timeout`,`cfg-handshake`,`cfg-reconnect-ms`,`cfg-autoreconnect`,`cfg-delim`,`cfg-prepend`,`cfg-append`,`cfg-hs-cmd`,`cfg-hs-cmd-mode`,`cfg-hs-expect`,`cfg-hs-expect-mode`,`dl-name`].forEach(e=>{let t=document.getElementById(e);t?.addEventListener(`change`,z),t?.addEventListener(`input`,z)}),document.querySelectorAll(`input[name='dl-lang']`).forEach(e=>e.addEventListener(`change`,z)),z();var H=`text`;T?.addEventListener(`click`,()=>{H=H===`text`?`hex`:`text`,T.textContent=H===`text`?`TXT`:`HEX`,w.placeholder=H===`text`?`Type a command, e.g. LED_ON`:`Hex bytes, e.g. FF 01 A3`});var U=null;function W(e){C.disabled=!e,w.disabled=!e,S.disabled=!e,x.disabled=e}x?.addEventListener(`click`,async()=>{if(U){try{await U.disconnect()}catch{}U=null}let t=N();e.setProvider(ae(t.wsUrl));let n=t.delimiter?d(t.delimiter):``;U=new oe({baudRate:t.baudRate,dataBits:t.dataBits,stopBits:t.stopBits,parity:t.parity,flowControl:t.flowControl,bufferSize:t.bufferSize,commandTimeout:t.commandTimeout,parser:n?re(n):r(),autoReconnect:t.autoReconnect,autoReconnectInterval:t.autoReconnectInterval,handshakeTimeout:t.handshakeTimeout},t.hsCmd,t.hsCmdMode,t.hsExpect,t.hsExpectMode),U.on(`serial:connecting`,()=>{L(`connecting`,`Connecting…`),x.disabled=!0,f(b,`Connecting to ${t.wsUrl}…`,{kind:`system`})}),U.on(`serial:connected`,()=>{L(`connected`,`Connected`),W(!0),f(b,`Connected via WebSocket (${t.wsUrl})!`,{kind:`system`})}),U.on(`serial:disconnected`,()=>{L(`disconnected`,`Disconnected`),W(!1),f(b,`Disconnected.`,{kind:`system`}),U=null}),U.on(`serial:data`,e=>{f(b,String(e),{kind:`received`,label:`Device`})}),U.on(`serial:error`,e=>{L(`error`,`Error`),f(b,`Error: ${e.message}`,{kind:`error`}),x.disabled=!1}),U.on(`serial:need-permission`,()=>{L(`error`,`Connection refused`),f(b,`Could not connect — is the WebSocket bridge running?`,{kind:`error`}),x.disabled=!1}),U.on(`serial:timeout`,e=>{f(b,`Timeout: ${F(e)}`,{kind:`error`})}),U.on(`serial:reconnecting`,()=>{L(`connecting`,`Reconnecting…`),f(b,`Auto-reconnecting via WebSocket…`,{kind:`system`})});try{await U.connect()}catch{}}),S?.addEventListener(`click`,async()=>{await U?.disconnect()});async function G(){let e=w.value.trim();if(!e||!U)return;let t=N(),n=t.append?d(t.append):t.delimiter?d(t.delimiter):``;try{if(H===`hex`){let t=I(e);f(b,`HEX: ${F(t)}`,{kind:`sent`,label:`You`}),await U.send(t)}else{let r=t.prepend+e+n;f(b,e,{kind:`sent`,label:`You`}),await U.send(r)}w.value=``,w.focus()}catch(e){f(b,`Send error: ${e instanceof Error?e.message:String(e)}`,{kind:`error`})}}C?.addEventListener(`click`,G),w?.addEventListener(`keydown`,e=>{e.key===`Enter`&&G()});var K=y(`cmd-name`),q=y(`cmd-value`),ve=y(`cmd-mode`),ye=y(`cmd-add`),J=y(`cmd-list`),Y=y(`lst-name`),X=y(`lst-pattern`),be=y(`lst-match`),xe=y(`lst-add`),Z=y(`lst-list`);function Q(){if(J){J.innerHTML=``;for(let e of _){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.mode.toUpperCase();let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.value;let a=document.createElement(`button`);a.className=`chip-send`,a.title=`Send now`,a.textContent=`▶`,a.addEventListener(`click`,()=>{if(U)if(e.mode===`hex`){let t=I(e.value);U.send(t).catch(()=>{}),f(b,`HEX: ${F(t)}`,{kind:`sent`,label:`You`})}else{let t=N(),n=t.append?d(t.append):d(t.delimiter);U.send(t.prepend+e.value+n).catch(()=>{}),f(b,e.name,{kind:`sent`,label:`You`})}});let o=document.createElement(`button`);o.className=`chip-del`,o.title=`Remove`,o.textContent=`×`,o.addEventListener(`click`,()=>{_=_.filter(t=>t.id!==e.id),Q(),z()}),t.append(n,r,i,a,o),J.appendChild(t)}}}function $(){if(Z){Z.innerHTML=``;for(let e of v){let t=document.createElement(`div`);t.className=`chip`;let n=document.createElement(`span`);n.className=`chip-badge`,n.textContent=e.match;let r=document.createElement(`span`);r.className=`chip-name`,r.textContent=e.name;let i=document.createElement(`span`);i.className=`chip-val`,i.textContent=e.pattern;let a=document.createElement(`button`);a.className=`chip-del`,a.title=`Remove`,a.textContent=`×`,a.addEventListener(`click`,()=>{v=v.filter(t=>t.id!==e.id),$(),z()}),t.append(n,r,i,a),Z.appendChild(t)}}}ye?.addEventListener(`click`,()=>{let e=K?.value.trim(),t=q?.value.trim();if(!e||!t)return;let n=ve?.value??`text`;_.push({id:crypto.randomUUID(),name:e,value:t,mode:n}),K&&(K.value=``),q&&(q.value=``),Q(),z()}),xe?.addEventListener(`click`,()=>{let e=Y?.value.trim(),t=X?.value.trim();if(!e||!t)return;let n=be?.value??`exact`;v.push({id:crypto.randomUUID(),name:e,pattern:t,match:n}),Y&&(Y.value=``),X&&(X.value=``),$(),z()}),f(b,`WebSocket bridge demo — start the Node.js bridge first: cd demos/websocket && node server.js`,{kind:`system`}),i(),a();
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
|
+
<title>Web Bluetooth — webserial-core demo</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--accent: #a855f7;
|
|
11
|
+
--accent-dark: #9333ea;
|
|
12
|
+
--accent-glow: rgba(168, 85, 247, 0.12);
|
|
13
|
+
--accent-badge: #faf5ff;
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
<script
|
|
17
|
+
type="module"
|
|
18
|
+
crossorigin
|
|
19
|
+
src="/demos/assets/web-bluetooth-yFc46qqb.js"
|
|
20
|
+
></script>
|
|
21
|
+
<link
|
|
22
|
+
rel="modulepreload"
|
|
23
|
+
crossorigin
|
|
24
|
+
href="/demos/assets/demo-shared-BVQKG6NC.js"
|
|
25
|
+
/>
|
|
26
|
+
<link
|
|
27
|
+
rel="stylesheet"
|
|
28
|
+
crossorigin
|
|
29
|
+
href="/demos/assets/demo-shared-DLFsukQx.css"
|
|
30
|
+
/>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<!-- ── Topbar ──────────────────────────────────────────── -->
|
|
34
|
+
<header class="topbar">
|
|
35
|
+
<button class="icon-btn" id="menu-btn" title="Toggle sidebar">
|
|
36
|
+
<i data-lucide="menu"></i>
|
|
37
|
+
</button>
|
|
38
|
+
<a class="topbar-brand" href="/demos/web-serial.html">
|
|
39
|
+
<span class="brand-icon"><i data-lucide="bluetooth"></i></span>
|
|
40
|
+
<span class="brand-name">webserial-core</span>
|
|
41
|
+
<span class="brand-ver">v2</span>
|
|
42
|
+
</a>
|
|
43
|
+
<span class="provider-pill">Bluetooth</span>
|
|
44
|
+
<span class="flex-1"></span>
|
|
45
|
+
|
|
46
|
+
<nav class="topbar-nav">
|
|
47
|
+
<a class="nav-item" href="/demos/web-serial.html"
|
|
48
|
+
><i data-lucide="zap"></i><span class="nav-lbl"> Web Serial</span></a
|
|
49
|
+
>
|
|
50
|
+
<a class="nav-item active" href="/demos/web-bluetooth.html"
|
|
51
|
+
><i data-lucide="bluetooth"></i
|
|
52
|
+
><span class="nav-lbl"> Bluetooth</span></a
|
|
53
|
+
>
|
|
54
|
+
<a class="nav-item" href="/demos/web-usb.html"
|
|
55
|
+
><i data-lucide="usb"></i><span class="nav-lbl"> WebUSB</span></a
|
|
56
|
+
>
|
|
57
|
+
<a class="nav-item" href="/demos/websocket.html"
|
|
58
|
+
><i data-lucide="globe"></i><span class="nav-lbl"> WebSocket</span></a
|
|
59
|
+
>
|
|
60
|
+
</nav>
|
|
61
|
+
|
|
62
|
+
<div class="topbar-actions">
|
|
63
|
+
<div class="status-pill">
|
|
64
|
+
<div class="status-dot" id="status-dot"></div>
|
|
65
|
+
<span id="status-text">Disconnected</span>
|
|
66
|
+
</div>
|
|
67
|
+
<button class="icon-btn" id="theme-btn" title="Toggle theme">🌙</button>
|
|
68
|
+
<button
|
|
69
|
+
class="icon-btn code-toggle"
|
|
70
|
+
id="code-toggle-btn"
|
|
71
|
+
title="Toggle code panel"
|
|
72
|
+
>
|
|
73
|
+
<i data-lucide="code-2"></i>
|
|
74
|
+
</button>
|
|
75
|
+
<button class="btn btn-connect" id="btn-connect">Pair Device</button>
|
|
76
|
+
<button class="btn btn-disconnect" id="btn-disconnect" disabled>
|
|
77
|
+
Disconnect
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</header>
|
|
81
|
+
|
|
82
|
+
<!-- ── App layout ───────────────────────────────────────── -->
|
|
83
|
+
<div class="app-layout">
|
|
84
|
+
<!-- ── Sidebar ─────────────────────────────────────────── -->
|
|
85
|
+
<aside class="sidebar" id="sidebar">
|
|
86
|
+
<div class="sidebar-scroll">
|
|
87
|
+
<!-- BLE info notice -->
|
|
88
|
+
<div class="sb-section">
|
|
89
|
+
<div class="notice">
|
|
90
|
+
Uses <strong>Nordic UART Service (NUS)</strong> over GATT.
|
|
91
|
+
Compatible: nRF52, ESP32 BLE UART, or any NUS device
|
|
92
|
+
(<code>6e400001-…</code>).<br />
|
|
93
|
+
Requires <strong>HTTPS</strong> or <code>localhost</code> +
|
|
94
|
+
Chromium with Web Bluetooth enabled.
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Connection settings (limited for BLE) -->
|
|
99
|
+
<div class="sb-section">
|
|
100
|
+
<div class="sb-title">Connection</div>
|
|
101
|
+
|
|
102
|
+
<div class="field-row">
|
|
103
|
+
<div class="field">
|
|
104
|
+
<label for="cfg-bufsize">Buffer Size</label>
|
|
105
|
+
<input
|
|
106
|
+
type="number"
|
|
107
|
+
id="cfg-bufsize"
|
|
108
|
+
value="255"
|
|
109
|
+
min="64"
|
|
110
|
+
max="65536"
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="field">
|
|
114
|
+
<label for="cfg-timeout">Cmd Timeout (ms)</label>
|
|
115
|
+
<input type="number" id="cfg-timeout" value="3000" min="0" />
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="field">
|
|
119
|
+
<label for="cfg-handshake">Handshake Timeout (ms)</label>
|
|
120
|
+
<input type="number" id="cfg-handshake" value="2000" min="0" />
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div class="notice" style="margin-top: 6px">
|
|
124
|
+
<strong>Baud rate</strong> is not used over BLE — link speed is
|
|
125
|
+
negotiated at the GATT level.
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- Message format -->
|
|
130
|
+
<div class="sb-section">
|
|
131
|
+
<div class="sb-title">Message Format</div>
|
|
132
|
+
<div class="field">
|
|
133
|
+
<label for="cfg-delim">Delimiter</label>
|
|
134
|
+
<input
|
|
135
|
+
type="text"
|
|
136
|
+
id="cfg-delim"
|
|
137
|
+
value="\n"
|
|
138
|
+
placeholder="\n \r\n | etc."
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="field-row">
|
|
142
|
+
<div class="field">
|
|
143
|
+
<label for="cfg-prepend">Prepend</label>
|
|
144
|
+
<input
|
|
145
|
+
type="text"
|
|
146
|
+
id="cfg-prepend"
|
|
147
|
+
value=""
|
|
148
|
+
placeholder="e.g. STX"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="field">
|
|
152
|
+
<label for="cfg-append">Append</label>
|
|
153
|
+
<input
|
|
154
|
+
type="text"
|
|
155
|
+
id="cfg-append"
|
|
156
|
+
value="\n"
|
|
157
|
+
placeholder="e.g. \n"
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Handshake -->
|
|
164
|
+
<div class="sb-section">
|
|
165
|
+
<div class="sb-title">Handshake on Connect</div>
|
|
166
|
+
<div class="notice" style="margin-bottom: 6px">
|
|
167
|
+
Sent automatically right after the GATT link opens. Leave
|
|
168
|
+
<em>Command</em> empty to skip entirely.
|
|
169
|
+
</div>
|
|
170
|
+
<div class="field-row">
|
|
171
|
+
<div class="field">
|
|
172
|
+
<label for="cfg-hs-cmd">Command to send</label>
|
|
173
|
+
<input
|
|
174
|
+
type="text"
|
|
175
|
+
id="cfg-hs-cmd"
|
|
176
|
+
value=""
|
|
177
|
+
placeholder="e.g. PING\n or FF 01 A3"
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="field" style="flex: 0 0 62px">
|
|
181
|
+
<label for="cfg-hs-cmd-mode">Mode</label>
|
|
182
|
+
<select id="cfg-hs-cmd-mode">
|
|
183
|
+
<option value="text" selected>TXT</option>
|
|
184
|
+
<option value="hex">HEX</option>
|
|
185
|
+
</select>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="field-row">
|
|
189
|
+
<div class="field">
|
|
190
|
+
<label for="cfg-hs-expect"
|
|
191
|
+
>Expected <small>(empty = no check)</small></label
|
|
192
|
+
>
|
|
193
|
+
<input
|
|
194
|
+
type="text"
|
|
195
|
+
id="cfg-hs-expect"
|
|
196
|
+
value=""
|
|
197
|
+
placeholder="e.g. PONG or OK"
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="field" style="flex: 0 0 62px">
|
|
201
|
+
<label for="cfg-hs-expect-mode">Mode</label>
|
|
202
|
+
<select id="cfg-hs-expect-mode">
|
|
203
|
+
<option value="text" selected>TXT</option>
|
|
204
|
+
<option value="hex">HEX</option>
|
|
205
|
+
</select>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<!-- Saved Commands -->
|
|
211
|
+
<div class="sb-section">
|
|
212
|
+
<div class="sb-title">Saved Commands</div>
|
|
213
|
+
<div class="notice" style="margin-bottom: 6px">
|
|
214
|
+
Pre-defined commands to send. Use <em>HEX</em> for raw bytes.
|
|
215
|
+
</div>
|
|
216
|
+
<div class="field-row">
|
|
217
|
+
<div class="field" style="flex: 0 0 72px">
|
|
218
|
+
<label for="cmd-name">Name</label>
|
|
219
|
+
<input type="text" id="cmd-name" placeholder="LED ON" />
|
|
220
|
+
</div>
|
|
221
|
+
<div class="field">
|
|
222
|
+
<label for="cmd-value">Command</label>
|
|
223
|
+
<input type="text" id="cmd-value" placeholder="LED_ON\n" />
|
|
224
|
+
</div>
|
|
225
|
+
<div class="field" style="flex: 0 0 62px">
|
|
226
|
+
<label for="cmd-mode">Mode</label>
|
|
227
|
+
<select id="cmd-mode">
|
|
228
|
+
<option value="text" selected>TXT</option>
|
|
229
|
+
<option value="hex">HEX</option>
|
|
230
|
+
</select>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
<button
|
|
234
|
+
class="btn btn-ghost"
|
|
235
|
+
id="cmd-add"
|
|
236
|
+
style="width: 100%; margin-top: 3px"
|
|
237
|
+
>
|
|
238
|
+
<i data-lucide="plus"></i> Add Command
|
|
239
|
+
</button>
|
|
240
|
+
<div class="chip-list" id="cmd-list"></div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<!-- Data Listeners -->
|
|
244
|
+
<div class="sb-section">
|
|
245
|
+
<div class="sb-title">Data Listeners</div>
|
|
246
|
+
<div class="notice" style="margin-bottom: 6px">
|
|
247
|
+
Patterns for <code>on('serial:data')</code> — skeleton code in
|
|
248
|
+
download.
|
|
249
|
+
</div>
|
|
250
|
+
<div class="field-row">
|
|
251
|
+
<div class="field" style="flex: 0 0 64px">
|
|
252
|
+
<label for="lst-name">Name</label>
|
|
253
|
+
<input type="text" id="lst-name" placeholder="Temp" />
|
|
254
|
+
</div>
|
|
255
|
+
<div class="field">
|
|
256
|
+
<label for="lst-pattern">Pattern</label>
|
|
257
|
+
<input type="text" id="lst-pattern" placeholder="TEMP:" />
|
|
258
|
+
</div>
|
|
259
|
+
<div class="field" style="flex: 0 0 62px">
|
|
260
|
+
<label for="lst-match">Match</label>
|
|
261
|
+
<select id="lst-match">
|
|
262
|
+
<option value="exact">exact</option>
|
|
263
|
+
<option value="contains" selected>has</option>
|
|
264
|
+
<option value="startsWith">starts</option>
|
|
265
|
+
<option value="hex">hex</option>
|
|
266
|
+
</select>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
<button
|
|
270
|
+
class="btn btn-ghost"
|
|
271
|
+
id="lst-add"
|
|
272
|
+
style="width: 100%; margin-top: 3px"
|
|
273
|
+
>
|
|
274
|
+
<i data-lucide="plus"></i> Add Listener
|
|
275
|
+
</button>
|
|
276
|
+
<div class="chip-list" id="lst-list"></div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<!-- Download -->
|
|
280
|
+
<div class="sb-section">
|
|
281
|
+
<div class="sb-title">Download Code</div>
|
|
282
|
+
<div class="field">
|
|
283
|
+
<label for="dl-name">Class / File name</label>
|
|
284
|
+
<input
|
|
285
|
+
type="text"
|
|
286
|
+
id="dl-name"
|
|
287
|
+
value="MyBleDevice"
|
|
288
|
+
placeholder="MyBleDevice"
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="dl-lang-row">
|
|
292
|
+
<label class="dl-opt"
|
|
293
|
+
><input type="radio" name="dl-lang" value="ts" checked />
|
|
294
|
+
TypeScript</label
|
|
295
|
+
>
|
|
296
|
+
<label class="dl-opt"
|
|
297
|
+
><input type="radio" name="dl-lang" value="js" />
|
|
298
|
+
JavaScript</label
|
|
299
|
+
>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="dl-type-row">
|
|
302
|
+
<label class="dl-opt"
|
|
303
|
+
><input type="radio" name="dl-type" value="file" /> Standalone
|
|
304
|
+
file</label
|
|
305
|
+
>
|
|
306
|
+
<label class="dl-opt"
|
|
307
|
+
><input type="radio" name="dl-type" value="project" checked />
|
|
308
|
+
Full project (ZIP)</label
|
|
309
|
+
>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="dl-btns">
|
|
312
|
+
<button class="btn btn-dl" id="dl-btn">
|
|
313
|
+
<i data-lucide="download"></i> Download
|
|
314
|
+
</button>
|
|
315
|
+
<button
|
|
316
|
+
class="btn btn-ghost"
|
|
317
|
+
id="cfg-export-btn"
|
|
318
|
+
title="Export configuration as JSON"
|
|
319
|
+
>
|
|
320
|
+
<i data-lucide="share"></i> Export config
|
|
321
|
+
</button>
|
|
322
|
+
<label
|
|
323
|
+
class="btn btn-ghost"
|
|
324
|
+
title="Import configuration from JSON"
|
|
325
|
+
>
|
|
326
|
+
<i data-lucide="upload"></i> Import config
|
|
327
|
+
<input
|
|
328
|
+
type="file"
|
|
329
|
+
id="cfg-import-input"
|
|
330
|
+
accept=".json"
|
|
331
|
+
style="display: none"
|
|
332
|
+
/>
|
|
333
|
+
</label>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<!-- /sidebar-scroll -->
|
|
338
|
+
</aside>
|
|
339
|
+
<div class="sidebar-resize-handle" id="sidebar-resize-handle"></div>
|
|
340
|
+
|
|
341
|
+
<!-- ── Chat console ─────────────────────────────────── -->
|
|
342
|
+
<main class="console-area">
|
|
343
|
+
<div class="console-hdr">
|
|
344
|
+
<div class="status-pill">
|
|
345
|
+
<div class="status-dot" id="console-dot"></div>
|
|
346
|
+
<span id="console-text">Pair a Bluetooth device to begin</span>
|
|
347
|
+
</div>
|
|
348
|
+
<button
|
|
349
|
+
class="btn btn-ghost"
|
|
350
|
+
id="clear-btn"
|
|
351
|
+
style="margin-left: auto; font-size: 0.7rem; padding: 4px 10px"
|
|
352
|
+
>
|
|
353
|
+
Clear
|
|
354
|
+
</button>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<div class="messages" id="messages">
|
|
358
|
+
<div class="empty-state">
|
|
359
|
+
<div class="empty-icon">📡</div>
|
|
360
|
+
<span>Pair a BLE device to begin</span>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div class="input-bar">
|
|
365
|
+
<button class="btn btn-mode" id="mode-toggle">TXT</button>
|
|
366
|
+
<input
|
|
367
|
+
class="msg-input"
|
|
368
|
+
id="input-send"
|
|
369
|
+
type="text"
|
|
370
|
+
placeholder="Type a command, e.g. LED_ON"
|
|
371
|
+
disabled
|
|
372
|
+
/>
|
|
373
|
+
<button class="btn btn-send" id="btn-send" disabled>Send</button>
|
|
374
|
+
</div>
|
|
375
|
+
</main>
|
|
376
|
+
|
|
377
|
+
<!-- ── Code panel ────────────────────────────────────── -->
|
|
378
|
+
<div class="resize-handle" id="resize-handle"></div>
|
|
379
|
+
<aside class="code-panel" id="code-panel">
|
|
380
|
+
<div class="cp-hdr">
|
|
381
|
+
<div class="cp-title">
|
|
382
|
+
<span>Code Preview</span>
|
|
383
|
+
<span class="cp-tab" id="code-tab">device.ts</span>
|
|
384
|
+
</div>
|
|
385
|
+
<div class="cp-actions">
|
|
386
|
+
<button class="cp-btn" id="copy-btn">Copy</button>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
<pre class="code-view" id="code-view"></pre>
|
|
390
|
+
</aside>
|
|
391
|
+
</div>
|
|
392
|
+
<!-- /app-layout -->
|
|
393
|
+
|
|
394
|
+
<footer class="app-footer">
|
|
395
|
+
© 2025
|
|
396
|
+
<a
|
|
397
|
+
href="https://github.com/danidoble"
|
|
398
|
+
style="color: inherit; text-decoration: none"
|
|
399
|
+
>@danidoble</a
|
|
400
|
+
>
|
|
401
|
+
· webserial-core v2
|
|
402
|
+
</footer>
|
|
403
|
+
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
|
404
|
+
<script>
|
|
405
|
+
lucide.createIcons();
|
|
406
|
+
</script>
|
|
407
|
+
</body>
|
|
408
|
+
</html>
|