diff --git a/packages/v2/package.json b/packages/v2/package.json index 9d7408d..86124ad 100644 --- a/packages/v2/package.json +++ b/packages/v2/package.json @@ -25,6 +25,7 @@ "vite": "^2.3.6", "vite-plugin-html": "^2.1.1", "vite-plugin-package-version": "^1.0.2", - "vite-plugin-singlefile": "^0.5.1" + "vite-plugin-singlefile": "^0.5.1", + "vite-plugin-pwa": "^0.12.8" } } diff --git a/packages/v2/src/esp-app.ts b/packages/v2/src/esp-app.ts index 3d77dfc..31b1fd3 100644 --- a/packages/v2/src/esp-app.ts +++ b/packages/v2/src/esp-app.ts @@ -6,10 +6,14 @@ import "./esp-entity-table"; import "./esp-log"; import "./esp-switch"; import "./esp-logo"; +import "./esp-ble"; import cssReset from "./css/reset"; import cssButton from "./css/button"; -window.source = new EventSource(getBasePath() + "/events"); + +// window.source = new EventSource(getBasePath() + "/events"); +window.source = new EventTarget(); + interface Config { ota: boolean; @@ -121,7 +125,8 @@ export default class EspApp extends LitElement { render() { return html` -

+ +

diff --git a/packages/v2/src/esp-ble.ts b/packages/v2/src/esp-ble.ts new file mode 100644 index 0000000..916352a --- /dev/null +++ b/packages/v2/src/esp-ble.ts @@ -0,0 +1,192 @@ +import { LitElement, html } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +@customElement("esp-ble") +export default class EspLogo extends LitElement { + @state() connected: boolean = false; + private bleDevice: BluetoothDevice | null = null; + private nusService: BluetoothRemoteGATTServer | null = null; + private rxCharacteristic: BluetoothRemoteGATTCharacteristic | null = null; + private txCharacteristic: BluetoothRemoteGATTCharacteristic | null = null; + private buffer: string = ''; + private mtuSize: number = 20; + + connectionToggle() { + if (this.connected) { + this.disconnect(); + } else { + this.connect(); + } + } + + render() { + return html` + + `; + } + + connect() { + const bleNusServiceUUID ='6e400001-b5a3-f393-e0a9-e50e24dcca9e'; + const bleNusCharRXUUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; + const bleNusCharTXUUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'; + if (!navigator.bluetooth) { + console.log('WebBluetooth API is not available.\r\n' + + 'Please make sure the Web Bluetooth flag is enabled.'); + window.term_.io.println('WebBluetooth API is not available on your browser.\r\n' + + 'Please make sure the Web Bluetooth flag is enabled.'); + return; + } + console.log('Requesting Bluetooth Device...'); + navigator.bluetooth.requestDevice({ + //filters: [{services: []}] + optionalServices: [bleNusServiceUUID], + acceptAllDevices: true + }) + .then(device => { + this.bleDevice = device; + console.log('Found ' + device.name); + console.log('Connecting to GATT Server...'); + this.bleDevice.addEventListener('gattserverdisconnected', this.onDisconnected.bind(this)); + return device.gatt.connect(); + }) + .then(server => { + console.log('Locate NUS service'); + return server.getPrimaryService(bleNusServiceUUID); + }).then(service => { + this.nusService = service; + console.log('Found NUS service: ' + service.uuid); + }) + .then(() => { + console.log('Locate RX characteristic'); + return this.nusService.getCharacteristic(bleNusCharRXUUID); + }) + .then(characteristic => { + this.rxCharacteristic = characteristic; + console.log('Found RX characteristic'); + }) + .then(() => { + console.log('Locate TX characteristic'); + return this.nusService.getCharacteristic(bleNusCharTXUUID); + }) + .then(characteristic => { + this.txCharacteristic = characteristic; + console.log('Found TX characteristic'); + }) + .then(() => { + console.log('Enable notifications'); + return this.txCharacteristic.startNotifications(); + }) + .then(() => { + console.log('Notifications started'); + this.txCharacteristic.addEventListener('characteristicvaluechanged', + this.handleNotifications.bind(this)); + this.connected = true; + }) + .catch(error => { + console.log('' + error); + if(this.bleDevice && this.bleDevice.gatt.connected) + { + this.bleDevice.gatt.disconnect(); + } + }); + } + + disconnect() { + if (!this.bleDevice) { + console.log('No Bluetooth Device connected...'); + return; + } + console.log('Disconnecting from Bluetooth Device...'); + if (this.bleDevice.gatt.connected) { + this.bleDevice.gatt.disconnect(); + console.log('Bluetooth Device connected: ' + this.bleDevice.gatt.connected); + this.connected = false; + } else { + console.log('> Bluetooth Device is already disconnected'); + } + } + + onDisconnected() { + this.connected = false; + } + + handleNotifications(event) { + console.log('notification'); + let value = event.target.value; + // Convert raw data bytes to character values and use these to + // construct a string. + let str = ""; + for (let i = 0; i < value.byteLength; i++) { + str += String.fromCharCode(value.getUint8(i)); + } + if(str.length > this.mtuSize) { + // there is no direct method to get MTU + // another opton would be to send it as part of configuration + this.mtuSize = str.length + } + this.buffer += str; + + let messages = this.buffer.split('\n'); + this.buffer = ''; + + messages.forEach(message => { + if(message.length == 0){ + return; + } + try { + const jsonData = JSON.parse(message); + let msg = message; + if (jsonData.event == 'ping' && jsonData.title == undefined) { + msg = ''; + } + else if (jsonData.event == 'log') { + msg = jsonData.msg; + } + const event = new MessageEvent(jsonData.event, { + data: msg, + lastEventId: Math.random(), + }); + window.source.dispatchEvent(event); + } catch (error) { + if (error instanceof SyntaxError) { + this.buffer = message; + } else { + console.log("error", error); + } + } + }); + + if (this.buffer) { + console.log('Incomplete JSON data, waiting for more data... ' + this.buffer); + } + + } + +} + +// 'use strict'; +// const MTU = 20; + +// function nusSendString(s) { +// if(bleDevice && bleDevice.gatt.connected) { +// console.log("send: " + s); +// let val_arr = new Uint8Array(s.length) +// for (let i = 0; i < s.length; i++) { +// let val = s[i].charCodeAt(0); +// val_arr[i] = val; +// } +// sendNextChunk(val_arr); +// } else { +// window.term_.io.println('Not connected to a device yet.'); +// } +// } + +// function sendNextChunk(a) { +// let chunk = a.slice(0, MTU); +// rxCharacteristic.writeValue(chunk) +// .then(function() { +// if (a.length > MTU) { +// sendNextChunk(a.slice(MTU)); +// } +// }); +// } diff --git a/packages/v2/vite.config.ts b/packages/v2/vite.config.ts index 2c75de0..f18a25d 100644 --- a/packages/v2/vite.config.ts +++ b/packages/v2/vite.config.ts @@ -8,8 +8,7 @@ import { viteSingleFile } from "vite-plugin-singlefile"; import { minifyHtml as ViteMinifyHtml } from "vite-plugin-html"; import stripBanner from "rollup-plugin-strip-banner"; import replace from "@rollup/plugin-replace"; - -const proxy_target = process.env.PROXY_TARGET || "http://nodemcu.local"; +import { VitePWA } from 'vite-plugin-pwa' export default defineConfig({ clearScreen: false, @@ -53,6 +52,15 @@ export default defineConfig({ enforce: "post", apply: "build", }, + VitePWA({ + registerType: 'autoUpdate', + manifest: { + icons: [{ + "src": "logo.svg", + "sizes": "any" + }], + } + }), ], build: { brotliSize: false, @@ -71,24 +79,8 @@ export default defineConfig({ }, }, server: { - open: "/", // auto open browser in dev mode - host: true, // dev on local and network + host: true, port: 5001, strictPort: true, - proxy: { - "/light": proxy_target, - "/select": proxy_target, - "/cover": proxy_target, - "/switch": proxy_target, - "/button": proxy_target, - "/fan": proxy_target, - "/lock": proxy_target, - "/number": proxy_target, - "/climate": proxy_target, - "/events": proxy_target, - "/text": proxy_target, - "/date": proxy_target, - "/time": proxy_target, - }, }, });