Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for ble transport #120

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
9 changes: 7 additions & 2 deletions packages/v2/src/esp-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -121,7 +125,8 @@ export default class EspApp extends LitElement {

render() {
return html`
<h1>
<esp-ble></esp-ble>
<h1>
<a href="https://esphome.io/web-api" class="logo">
<esp-logo style="width: 52px; height: 40px; display: block;"></esp-logo>
</a>
Expand Down
192 changes: 192 additions & 0 deletions packages/v2/src/esp-ble.ts
Original file line number Diff line number Diff line change
@@ -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`
<button class="btn" @click="${this.connectionToggle}">${this.connected ? "Disconnect BLE" : "Connect BLE"}</button>
`;
}

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));
// }
// });
// }
30 changes: 11 additions & 19 deletions packages/v2/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -53,6 +52,15 @@ export default defineConfig({
enforce: "post",
apply: "build",
},
VitePWA({
registerType: 'autoUpdate',
manifest: {
icons: [{
"src": "logo.svg",
"sizes": "any"
}],
}
}),
],
build: {
brotliSize: false,
Expand All @@ -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,
},
},
});