UNPKG

lavva.webbluetooth

Version:

Library implementing WebBluetooth custom functionality if underlying platform does support it

2 lines 14.6 kB
(()=>{"use strict";var e,t,r,i={d:(e,t)=>{for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};i.d({},{_f:()=>d}),function(e){e.Lavva="lavva",e.Exalus="exalus",e.Wisniowski="wisniowski"}(e||(e={})),function(e){e.En="en",e.De="de",e.Pl="pl",e.It="it",e.Es="es",e.Fr="fr",e.Ro="ro",e.Hu="hu",e.Lt="lt",e.No="no",e.Fi="fi",e.Sv="sv",e.Nl="nl",e.Pt="pt",e.Cz="cz",e.Uk="uk",e.Unknown="en"}(t||(t={})),function(e){e[e.Unknown=0]="Unknown",e[e.Remote=1]="Remote",e[e.BlindController=2]="BlindController",e[e.LightController=3]="LightController",e[e.HeatingController=4]="HeatingController",e[e.FacadeBlindController=5]="FacadeBlindController",e[e.GateController=6]="GateController",e[e.DoorController=7]="DoorController",e[e.GatewayController=8]="GatewayController",e[e.Sensor=9]="Sensor",e[e.SwitchController=10]="SwitchController",e[e.Meters=11]="Meters",e[e.Cameras=12]="Cameras",e[e.Intercoms=13]="Intercoms",e[e.GarageDoorController=14]="GarageDoorController",e[e.FencesWicketGatesAndGatesController=15]="FencesWicketGatesAndGatesController"}(r||(r={}));class n{static Delay(e){return new Promise((t=>setTimeout(t,e)))}}var c,s,a,o=function(e,t,r,i){return new(r||(r=Promise))((function(n,c){function s(e){try{o(i.next(e))}catch(e){c(e)}}function a(e){try{o(i.throw(e))}catch(e){c(e)}}function o(e){var t;e.done?n(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(s,a)}o((i=i.apply(e,t||[])).next())}))};class l{constructor(){this.receiveBuffer="",this._maxCharacteristicValueLength=244,this.serviceUuid=65504,this.characteristicUuid=65505,this.receiveSeparator="\n",this.sendSeparator="\n",this.characteristic=null,this.notificationsStarted=!1,this.pendingResponses=[],this.OnGattServiceDisconnectedEvent=new d("OnGattServiceDisconnectedEvent"),this.OnCharacteristicValueChangedEvent=new d("OnCharacteristicValueChangedEvent"),this.OnConnectedEvent=new d("OnConnectedEvent"),this.OnDisconnectedEvent=new d("OnDisconnectedEvent"),this.filter=[{namePrefix:"Lavva-",services:[this.serviceUuid]},{namePrefix:"smartAW",services:[this.serviceUuid]}],this.WHITESPACE_NAMES={32:"SPACE",9:"TAB",10:"LF",13:"CR",11:"VT",12:"FF",160:"NBSP",8232:"LINE SEPARATOR",8233:"PARA SEPARATOR"},this.handleCharacteristicValueChanged=e=>{var t,r;try{if(!this.characteristic)return void console.warn("handleCharacteristicValueChanged: this.characteristic is null, ignoring");const i=e.target;if(i!==this.characteristic)return void console.warn(`handleCharacteristicValueChanged: event from foreign characteristic ${i.uuid}, expected ${this.characteristic.uuid}`);if(!this.isExpectedCharacteristic(i))return;const n=(new TextDecoder).decode(i.value);console.warn(this.visualizeTrailingWhitespace(`Received characteristic value change: ${n}`)),this.receiveBuffer+=n;const c=this.receiveBuffer.split(this.receiveSeparator);this.receiveBuffer=null!==(t=c.pop())&&void 0!==t?t:"";for(const e of c){const t=this.normalizeFrame(e);t&&(console.warn(`Received data: ${t}`),this.dispatchPendingResponse(t),null===(r=this.OnCharacteristicValueChangedEvent)||void 0===r||r.Invoke(t))}}catch(e){console.error("Error handling characteristic value changed:",e)}},l.instances.add(this)}Dispose(){l.instances.delete(this)}visualizeTrailingWhitespace(e){const t=e.match(/\s+$/u);return t?e.slice(0,-t[0].length)+[...t[0]].map((e=>{switch(e){case" ":return"·";case"\t":return"→";case"\r":return"␍";case"\n":return"␊";case" ":return"⍽";default:return"□"}})).join(""):e}logTrailingWhitespaceDetail(e){const t=e.match(/\s+$/u);t?[...t[0]].forEach(((e,t)=>{var r;const i=e.codePointAt(0),n=null!==(r=this.WHITESPACE_NAMES[i])&&void 0!==r?r:"UNKNOWN";console.log(`${t}: U+${i.toString(16).toUpperCase().padStart(4,"0")} (${i}) — ${n}`)})):console.log("🚫 Brak białych znaków na końcu łańcucha.")}updateMaxPayload(e){const t=Math.min(Math.max(e-3,20),244);this._maxCharacteristicValueLength=t}SetFilter(e){this.filter=e}GetConnectedDevice(){return l.selectedDevice}expectedUuidFrom16Bit(e){return`0000${e.toString(16).padStart(4,"0").toLowerCase()}-0000-1000-8000-00805f9b34fb`}isExpectedCharacteristic(e){const t=this.expectedUuidFrom16Bit(this.characteristicUuid),r=e.uuid.toLowerCase(),i=r===t;return console.warn(`UUID compare: actual=${r} expected=${t} rawExpected=${this.characteristicUuid} => ${i}`),i}RequestDeviceAndConnectAsync(){return o(this,void 0,void 0,(function*(){var e,t;if(null!==l.selectedDevice)return c.AnotherDeviceIsAlreadyConnected;let r=null;try{if(console.log(`Requesting Bluetooth Device, filter: ${JSON.stringify(this.filter)}`),r=yield navigator.bluetooth.requestDevice({filters:this.filter}),!r)return console.warn("No device selected"),c.NoDeviceHasBeenSelected}catch(e){return console.error("Error requesting device: ",e),c.NoDeviceHasBeenSelected}try{if(console.log(`Selected device: ${r.name} with ID: ${r.id}`),console.warn(`Connecting to GATT Server of device: ${r.name}`),l.selectedDevice=r,l.globalGattDisconnectedHandlerInstalled||(r.addEventListener("gattserverdisconnected",l.globalGattDisconnectedHandler),l.globalGattDisconnectedHandlerInstalled=!0),l.gattServer=yield null===(e=r.gatt)||void 0===e?void 0:e.connect(),!l.gattServer)return console.error("Failed to connect to GATT Server"),c.FailedToConnectToGattServer}catch(e){return console.error("Error connecting to GATT Server: ",e),c.FailedToConnectToGattServer}try{if(console.warn(`Getting primary service: ${this.serviceUuid}`),l.gattService=yield l.gattServer.getPrimaryService(this.serviceUuid),!l.gattService)return console.error("Failed to get primary service"),c.FailedToGetPrimaryService}catch(e){return console.error("Error getting primary service:",e),c.FailedToGetPrimaryService}const i=yield this.AttachToCharacteristicInternal(this.characteristicUuid);return"number"==typeof i?i:(console.warn("Connected to device successfully!"),void 0!==(null===(t=l.gattServer)||void 0===t?void 0:t.mtu)&&(this.updateMaxPayload(l.gattServer.mtu),console.warn(`MTU: ${l.gattServer.mtu}`)),this.OnConnectedEvent.Invoke(r),r)}))}ConnectToGattCharacteristicAsync(e){return o(this,void 0,void 0,(function*(){var t;if(this.characteristic)return console.warn("Already connected to a characteristic in this instance. Use a new instance."),c.AnotherDeviceIsAlreadyConnected;if(!l.selectedDevice)return console.warn("No device selected/connected"),c.NoDeviceHasBeenSelected;if(!l.gattServer)return console.error("No GATT server"),c.FailedToConnectToGattServer;if(!l.gattService)return console.error("No primary service"),c.FailedToGetPrimaryService;const r=yield this.AttachToCharacteristicInternal(e);return"number"==typeof r||(console.warn("Connected to characteristic successfully!"),void 0!==(null===(t=l.gattServer)||void 0===t?void 0:t.mtu)&&(this.updateMaxPayload(l.gattServer.mtu),console.warn(`MTU: ${l.gattServer.mtu}`))),r}))}AttachToCharacteristicInternal(e){return o(this,void 0,void 0,(function*(){this.characteristicUuid=e;try{if(console.warn(`Getting characteristic: ${this.characteristicUuid}`),this.characteristic=yield l.gattService.getCharacteristic(this.characteristicUuid),!this.characteristic)return console.error("Failed to get characteristic"),c.FailedToGetCharacteristic}catch(e){return console.error("Error getting characteristic:",e),c.FailedToGetCharacteristic}try{console.warn("Starting notifications");const e=yield this.characteristic.startNotifications();if(!e)return console.error("Failed to start notifications"),c.FailedToStartNotifications;e.addEventListener("characteristicvaluechanged",this.handleCharacteristicValueChanged),this.notificationsStarted=!0}catch(e){return console.error("Error starting notifications:",e),c.FailedToStartNotifications}return this.characteristic}))}DisconnectDeviceAsync(e){return o(this,void 0,void 0,(function*(){var t,r;try{if(!e)return s.NoDeviceConnected;yield Promise.all([...l.instances].map((e=>e.detachCharacteristic())));try{null===(t=l.selectedDevice)||void 0===t||t.removeEventListener("gattserverdisconnected",l.globalGattDisconnectedHandler)}catch(e){}l.globalGattDisconnectedHandlerInstalled=!1;try{null===(r=e.gatt)||void 0===r||r.disconnect(),yield n.Delay(200)}catch(e){}try{yield e.forget()}catch(e){}return this.rejectPendingResponses(new Error("Device disconnected")),l.selectedDevice=null,l.gattServer=null,l.gattService=null,this.OnDisconnectedEvent.Invoke(e),s.DisconnectedSuccesfully}catch(e){return console.error("Error disconnecting device:",e),s.FailedToDisconnect}}))}detachCharacteristic(){return o(this,void 0,void 0,(function*(){if(this.characteristic){try{this.characteristic.removeEventListener("characteristicvaluechanged",this.handleCharacteristicValueChanged)}catch(e){}try{yield this.characteristic.stopNotifications()}catch(e){}this.notificationsStarted=!1,this.receiveBuffer="",this.rejectPendingResponses(new Error("Characteristic detached")),this.characteristic=null}}))}SendDataAsync(e){return o(this,void 0,void 0,(function*(){const t=this.serializeOutgoingData(e);if(!t)return a.DataIsEmpty;try{const e=this.SplitByLength(t+this.sendSeparator,this._maxCharacteristicValueLength);if(!e)return a.FailedToWriteValue;if(!this.characteristic)return a.NoDeviceConnected;for(let t=0;t<e.length;t++)if(!(yield this.WriteValueAsync(e[t])))return a.FailedToWriteValue;return a.Sucess}catch(e){return console.error("Error sending data:",e),a.FailedToWriteValue}}))}SendAndWaitForResponseAsync(e){return o(this,arguments,void 0,(function*(e,t=5e3){if(!this.characteristic)throw new Error("Connection is not established");return new Promise(((r,i)=>o(this,void 0,void 0,(function*(){const c=this.createResponseMatcher(e),s=window.setTimeout((()=>{this.receiveBuffer="",this.pendingResponses=this.pendingResponses.filter((e=>e.onFrame!==l)),i(new Error("Response timeout"))}),t),l=e=>o(this,void 0,void 0,(function*(){clearTimeout(s),this.pendingResponses=this.pendingResponses.filter((e=>e.onFrame!==l));try{yield n.Delay(30),r(JSON.parse(e))}catch(e){this.receiveBuffer="",i(new Error("Failed to parse response"))}}));switch(this.pendingResponses.push({matcher:c,onFrame:l,reject:i}),yield this.SendDataAsync(e)){case a.Sucess:console.info("Data sent successfully");break;case a.DataIsEmpty:clearTimeout(s),this.pendingResponses=this.pendingResponses.filter((e=>e.onFrame!==l)),i(new Error("The data is empty"));break;case a.NoDeviceConnected:clearTimeout(s),this.pendingResponses=this.pendingResponses.filter((e=>e.onFrame!==l)),i(new Error("Failed to send request, device is disconnected"));break;case a.FailedToWriteValue:clearTimeout(s),this.pendingResponses=this.pendingResponses.filter((e=>e.onFrame!==l)),i(new Error("Failed to send request"))}}))))}))}WriteValueAsync(e){return o(this,void 0,void 0,(function*(){if(!this.characteristic)return!1;const t=(new TextEncoder).encode(e);try{return console.warn(`Writing characteristic [${this.characteristic.uuid}] value: ${e}`),yield this.characteristic.writeValue(t),yield n.Delay(10),!0}catch(r){try{return console.warn(`Retry writing characteristic [${this.characteristic.uuid}] value: ${e}`),yield this.characteristic.writeValue(t),yield n.Delay(10),!0}catch(t){return console.error(`Error writing value: ${e} error:`,t),!1}}}))}SplitByLength(e,t){return e.match(new RegExp(`(.|[\r\n]){1,${t}}`,"g"))}serializeOutgoingData(e){return"string"==typeof e?e:null==e?"":JSON.stringify(e)}normalizeFrame(e){return e.endsWith("\r")?e.slice(0,-1):e}dispatchPendingResponse(e){for(let t=0;t<this.pendingResponses.length;t++){const r=this.pendingResponses[t];if(r.matcher(e))return this.pendingResponses.splice(t,1),r.onFrame(e),!0}return!1}rejectPendingResponses(e){const t=this.pendingResponses.splice(0,this.pendingResponses.length);this.receiveBuffer="",t.forEach((t=>t.reject(e))),console.warn(`Rejecting pending responses: ${e.message}`)}createResponseMatcher(e){const t=this.tryParseJsonFrame("string"==typeof e?e:this.serializeOutgoingData(e)),r=this.getCorrelationId(t),i=this.getStringField(t,"Resource");return e=>{const n=this.tryParseJsonFrame(e);if(null==n)return null==t;const c=this.getCorrelationId(n);if(null!=r||null!=c)return null!=r&&r===c;const s=this.getStringField(n,"Resource");return null!=i||null!=s?null!=i&&i===s:null==t||null==i}}tryParseJsonFrame(e){try{const t=JSON.parse(e);if(null!=t&&"object"==typeof t&&!Array.isArray(t))return t}catch(e){return null}return null}getCorrelationId(e){var t,r,i;return null==e?null:null!==(i=null!==(r=null!==(t=this.getStringField(e,"TransactionId"))&&void 0!==t?t:this.getStringField(e,"transactionId"))&&void 0!==r?r:this.getStringField(e,"RequestId"))&&void 0!==i?i:this.getStringField(e,"requestId")}getStringField(e,t){const r=null==e?void 0:e[t];return"string"==typeof r&&r.length>0?r:null}}l.selectedDevice=null,l.gattServer=null,l.gattService=null,l.instances=new Set,l.globalGattDisconnectedHandlerInstalled=!1,l.globalGattDisconnectedHandler=e=>{var t,r;console.warn("Gatt Server disconnected (global handler)");for(const e of l.instances)try{e.rejectPendingResponses(new Error("GATT server disconnected")),null===(t=e.OnGattServiceDisconnectedEvent)||void 0===t||t.Invoke(null!==(r=l.gattServer)&&void 0!==r?r:void 0)}catch(e){console.warn("Error invoking OnGattServiceDisconnectedEvent:",e)}},function(e){e[e.NoDeviceHasBeenSelected=0]="NoDeviceHasBeenSelected",e[e.AnotherDeviceIsAlreadyConnected=1]="AnotherDeviceIsAlreadyConnected",e[e.FailedToConnectToGattServer=2]="FailedToConnectToGattServer",e[e.FailedToGetPrimaryService=3]="FailedToGetPrimaryService",e[e.FailedToDiscoverService=4]="FailedToDiscoverService",e[e.FailedToGetCharacteristic=5]="FailedToGetCharacteristic",e[e.FailedToStartNotifications=6]="FailedToStartNotifications"}(c||(c={})),function(e){e[e.NoDeviceConnected=0]="NoDeviceConnected",e[e.FailedToForgetDevice=1]="FailedToForgetDevice",e[e.FailedToDisconnect=2]="FailedToDisconnect",e[e.FailedToStopNotifications=3]="FailedToStopNotifications",e[e.DisconnectedSuccesfully=4]="DisconnectedSuccesfully"}(s||(s={})),function(e){e[e.Sucess=0]="Sucess",e[e.DataIsEmpty=1]="DataIsEmpty",e[e.NoDeviceConnected=2]="NoDeviceConnected",e[e.FailedToWriteValue=3]="FailedToWriteValue"}(a||(a={}));class d{constructor(e){this.handlers=[],this.name=e}Subscribe(e){this.handlers.push(e)}Unsubscribe(e){this.handlers=this.handlers.filter((t=>t!==e))}RemoveAllSubscriptions(){this.handlers=[]}Invoke(e){this.handlers.slice(0).forEach((t=>{try{null!=t&&t(e)}catch(e){console.error(`[NativeTypedEvent] Error invoking event ${this.name} handler: ${JSON.stringify(t)} error: ${e}`)}}))}}})(); //# sourceMappingURL=lavva-webbluetooth.js.map