connection timout on write function regardless of timout option supplied.
kairos0ne opened this issue · 4 comments
Describe the bug
I am running Moddable on a ESP32-S3 TFT
/*
* Copyright (c) 2016-2019 Moddable Tech, Inc.
*
* This file is part of the Moddable SDK.
*
* This work is licensed under the
* Creative Commons Attribution 4.0 International License.
* To view a copy of this license, visit
* <http://creativecommons.org/licenses/by/4.0>.
* or send a letter to Creative Commons, PO Box 1866,
* Mountain View, CA 94042, USA.
*
*/
/*
Nordic UART Service Server
The Nordic UART Service is a simple GATT-based service with TX and RX characteristics.
Strings written to the UART Service server are echoed back to the client as characteristic notifications.
https://devzone.nordicsemi.com/f/nordic-q-a/10567/what-is-nus-nordic-uart-service
*/
import BLEServer from "bleserver"; // import the bleserver module
import {uuid} from "btutils"; // import the uuid module";
// Import the tricolor led module
import Digital from "pins/digital";
import Timer from "timer";
import NeoPixel from 'neopixel';
import Pins from 'pins/digital'; // import the pins module
const SERVICE_UUID = uuid`6E400001-B5A3-F393-E0A9-E50E24DCCA9E`; // create a uuid for the service
// create a uuid for the characteristic
const UART_SERVER = "UART Server"; // create a name for the service
// pin for the neopixel
const neopixelPin = 10;
let neopixel = new NeoPixel({length: 12, pin: neopixelPin, order: "GRB"});
const red = neopixel.makeRGB(255, 0, 0);
const yellow = neopixel.makeRGB(255, 255, 0);
const orange = neopixel.makeRGB(255, 165, 0);
const purple = neopixel.makeRGB(128, 0, 128);
const pink = neopixel.makeRGB(255, 192, 203);
const cyan = neopixel.makeRGB(0, 255, 255);
const magenta = neopixel.makeRGB(255, 0, 255);
const green = neopixel.makeRGB(0, 255, 0);
const blue = neopixel.makeRGB(0, 0, 255);
const white = neopixel.makeRGB(255, 255, 255);
const black = neopixel.makeRGB(0, 0, 0);
function setAll(color) {
for (let i = 0; i <= neopixel.length; i++) {
neopixel.setPixel(i, color);
}
neopixel.update({ length: neopixel.length, start: 0, end: neopixel.length, repeat: 0, delay: 0, offset: 0, count: neopixel.length });
}
function setPixel(pixel, color) {
neopixel.setPixel(pixel, color);
neopixel.update();
}
function clearAll() {
setAll(black);
}
function setAllColor(color) {
setAll(color);
}
class UARTServer extends BLEServer {
onReady() {
this.deviceName = "VEGAONE";
this.onDisconnected();
}
onConnected() {
this.stopAdvertising();
}
onDisconnected() {
delete this.tx;
this.startAdvertising({
advertisingData: {flags: 6, completeName: this.deviceName, completeUUID128List: [SERVICE_UUID]}
});
}
onCharacteristicNotifyEnabled(characteristic) {
this.tx = characteristic;
}
onCharacteristicNotifyDisabled(characteristic) {
delete this.tx;
}
onPasskeyConfirm(connection, passkey) {
trace("Confirm connection: " + connection + " Passkey: " + passkey + "\n");
}
onAuthenticationComplete(connection, result) {
trace("Authentication complete connection: " + connection + " result: " + result + "\n");
}
onCharacteristicRead(characteristic) {
trace("Read characteristic: " + characteristic + "\n");
}
onCharacteristicWritten(characteristic, value) {
const timeoutMs = 5000; // Set the timeout value in milliseconds (e.g., 5000 for 5 seconds)
let timeoutId;
const handleTimeout = () => {
// Handle the timeout event here
// You can perform any necessary actions when the write operation times out
// For example, you could log an error message or retry the operation
trace("Timeout\n");
// Example actions for a timeout:
// 1. Log an error message
trace("Error: Write operation timed out\n");
// 2. Retry the operation
// 2. Retry the operation
// Retry the write operation here
if (this.tx) {
this.notifyValue(this.tx, value);
}
// 3. Clear the timeout and handle the write operation
// Clear the timeout and handle the write operation
clearTimeoutAndHandleWrite();
};
const clearTimeoutAndHandleWrite = () => {
Timer.clear(timeoutId); // Clear the timeout if the write operation completes within the timeout duration
// Perform your existing logic for handling the write operation here
// This code block includes the existing if statements and actions based on the value
trace.left(value, UART_SERVER);
if (this.tx) {
this.notifyValue(this.tx, value);
trace("value: " + value);
}
// Example actions for a value of 2:
if (value == 1) {
clearAll();
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 2) {
trace("two: " + value);
// this.notifyValue(this.tx, value);
setAllColor(red);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 3) {
trace("three: " + value);
// this.notifyValue(this.tx, value);
setAllColor(green);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 4) {
trace("four: " + value);
// this.notifyValue(this.tx, value);
setAllColor(blue);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 5) {
trace("five: " + value);
// this.notifyValue(this.tx, value);
setAllColor(yellow);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 6) {
trace("six: " + value);
// this.notifyValue(this.tx, value);
setAllColor(orange);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 7) {
trace("seven: " + value);
// this.notifyValue(this.tx, value);
setAllColor(purple);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 8) {
trace("eight: " + value);
// this.notifyValue(this.tx, value);
setAllColor(pink);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 9) {
trace("nine: " + value);
// this.notifyValue(this.tx, value);
setAllColor(cyan);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
if (value == 10) {
trace("ten: " + value);
// this.notifyValue(this.tx, value);
setAllColor(magenta);
// Set a timeout for the write operation
timeoutId = Timer.set(() => handleTimeout(), timeoutMs);
}
};
// Set the timeout and handle the write operation
timeoutId = Timer.set(handleTimeout, timeoutMs);
clearTimeoutAndHandleWrite();
// Note: If you are using multiple characteristics, you can use a different timeout value for each characteristic
// For example, you could set a longer timeout for a characteristic that requires more time to process the data
}
}
let server = new UARTServer;
and Im running capacitor in vue with the following code in my component:
<template>
<div class="connection">
<v-btn v-if="!device" block class="mb-5" @click="scanDevices()">
<v-spacer></v-spacer>
<v-icon>mdi-bluetooth</v-icon> Connect to device
<v-spacer></v-spacer>
</v-btn>
<h3 class="mb-5">Connected devices</h3>
<v-card class="mx-auto elevation-5 ma-2" v-if="device">
<v-list-item three-line class="mt-5">
<v-list-item-media>
<div class="d-flex">
<div class="flex-fill">
<v-list-item-title @click="dialog = !dialog" class="headline mb-1">{{device.localName}}</v-list-item-title>
<v-list-item-subtitle @click="dialog = !dialog">{{device.device.deviceId}}</v-list-item-subtitle>
</div>
<div>
<v-icon size="50" class="mr-5 mb-5" large>mdi-connection</v-icon>
</div>
</div>
</v-list-item-media>
</v-list-item>
<v-divider></v-divider>
<v-card-actions>
<v-btn @click="disconnect(device)" text> <v-icon class="mr-5">mdi-lan-connect</v-icon> Disconnect</v-btn>
</v-card-actions>
</v-card>
<div v-else class="text-center">
No data available
</div>
<h3 v-if="devices != null || devices != undefined" class="mb-5">Available devices</h3>
<div v-if="devices">
<v-card class="mx-auto elevation-5 ma-2" v-for="device in devices" :key="device.id">
<v-list-item three-line class="mt-5">
<v-list-item-media>
<div class="d-flex">
<div class="flex-fill">
<v-list-item-title class="headline mb-1">{{device.localName}}</v-list-item-title>
<v-list-item-subtitle>{{device.device.deviceId}}</v-list-item-subtitle>
</div>
<div>
<v-icon size="50" class="mr-5 mb-5" large>mdi-connection</v-icon>
</div>
</div>
</v-list-item-media>
</v-list-item>
<v-divider></v-divider>
<v-card-actions>
<v-btn @click="join(device)" text> <v-icon class="mr-5">mdi-lan-connect</v-icon> Connect</v-btn>
</v-card-actions>
</v-card>
</div>
<!-- Create a dialog for when the user clicks the connected microcontroller -->
<v-dialog v-model="dialog" max-width="500px" class="my-10">
<v-card>
<v-card-title>
<span class="headline">Device information</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="bleDeviceId" label="Device ID" readonly></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="bleDeviceName" label="Device name" readonly></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="bleDeviceServices" label="Services" readonly></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-form>
<v-text-field
v-model="writeData"
label="UART Service"
type="number"
hint="Enter a number"
persistent-hint
clearable
/>
<v-btn @click.once="write(writeData)" block>
Send data
</v-btn>
</v-form>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="dialog = false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
// Implement capacitor plugin ble
// scan for devices
import { BleClient } from '@capacitor-community/bluetooth-le';
// import { Capacitor } from '@capacitor/core';
// import { Plugins } from '@capacitor/core';
// const { BluetoothLe } = Plugins;
const tx_characteristic = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E';
const rx_characteristic = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E';
export default {
name: 'ConnectionComponent',
components: {
// HelloWorld
},
data: () => ({
writeData: null,
dialog: false,
device: null,
devices: [],
bleClient: null,
bleServer: null,
bleDevice: null,
bleDeviceId: null,
bleDeviceName: null,
bleDeviceServices: null,
bleDeviceCharacteristic: null,
}),
computed: {
canvasWidth() {
return (this.$vuetify.display.width - 40)
}
},
methods: {
async scanDevices() {
console.log('scan devices');
// Set UART service UUID
const UART_SERVICE = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E';
this.devices = [];
await BleClient.requestLEScan(
{
services: [UART_SERVICE],
name: "VEGAONE" // Specify the desired device name
},
(result) => {
console.log('received new scan result', result);
// this.devices.push(result);
this.devices = [...this.devices, result];
},
(error) => {
console.log('error while scanning', error);
},
);
},
async join(device) {
console.log('join device', device);
this.bleDeviceId = device.device.deviceId;
this.bleDeviceName = device.localName;
console.log('ble device to connect', device);
this.device = device;
// Connect to the device
await BleClient.connect(device.device.deviceId);
console.log('connected to device', device);
// Get the services
const services = await BleClient.getServices(this.bleDeviceId);
console.log('get services', services);
this.bleDeviceServices = services;
// Get the characteristics
// const characteristics = await BleClient.getCharacteristics(this.bleDeviceId);
// Show the dialog
console.log('show dialog');
this.dialog = true;
// Remove the device from the list of available devices
this.devices = this.devices.filter((d) => d.device.deviceId !== device.device.deviceId);
},
async disconnect(device) {
console.log('disconnect');
await BleClient.disconnect( device.device.deviceId );
// clear data
this.bleDeviceId = null;
this.bleDeviceName = null;
this.bleDeviceServices = null;
this.bleDevice = null;
this.device = null;
},
async write(input) {
console.log('send');
// Function to convert string to Uint8Array using TextEncoder
function stringToUint8Array(string) {
const encoder = new TextEncoder();
return encoder.encode(string);
}
// Input is a string
const data = stringToUint8Array(input);
// set options
const options = {
timeout: 5000,
};
try {
await BleClient.write (this.bleDeviceId, this.bleDeviceServices[0].uuid, rx_characteristic, data, options);
this.writeData = null;
console.log('data sent');
} catch (error) {
console.error('Error sending data:', error);
}
},
async read() {
console.log('read');
await BleClient.read(this.bleDeviceId, this.bleDeviceCharacteristic);
},
async subscribe() {
console.log('subscribe');
await BleClient.subscribe(this.bleDeviceId, this.bleDeviceCharacteristic, (result) => {
console.log('received new subscribe result', result);
this.bleDeviceCharacteristicValue = result.value;
});
},
async unsubscribe() {
console.log('unsubscribe');
await BleClient.unsubscribe(this.bleDeviceId, this.bleDeviceCharacteristic);
},
async readDescriptor() {
console.log('read descriptor');
await BleClient.readDescriptor(this.bleDeviceId, this.bleDeviceCharacteristic, this.bleDeviceCharacteristicDescriptor);
},
async writeDescriptor() {
console.log('write descriptor');
const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
await BleClient.writeDescriptor(this.bleDeviceId, this.bleDeviceCharacteristic, this.bleDeviceCharacteristicDescriptor, data);
},
async readRssi() {
console.log('read rssi');
await BleClient.readRssi(this.bleDeviceId);
},
async requestConnectionPriority() {
console.log('request connection priority');
await BleClient.requestConnectionPriority(this.bleDeviceId, 'high');
},
async requestMtu() {
console.log('request mtu');
await BleClient.requestMtu(this.bleDeviceId, 512);
},
async refreshCache() {
console.log('refresh cache');
await BleClient.refreshCache(this.bleDeviceId);
},
},
created() {
console.log('created');
BleClient.initialize();
},
mounted() {
console.log('mounted');
// initialize ble client
if (this.bleDeviceId) {
BleClient.getConnectedDevices({services: [this.bleDeviceId]}, (result) => {
console.log('get connected devices', result);
this.device = result;
},
(error) => {
console.log('error while getting connected devices', error);
},
);
} else {
console.log('no ble device id');
}
},
// beforeUnmount() {
// console.log('before destroy');
// // disconnect from device
// BleClient.disconnect( this.bleDeviceId );
// // clear data
// this.bleDeviceId = null;
// this.bleDeviceName = null;
// this.bleDeviceServices = null;
// this.bleDevice = null;
// },
watch: {
bleDeviceId: function (val) {
console.log('ble device id changed', val);
}
}
};
</script>
Im getting disconnected on write while and Im not sure why it seems fine when i use the bluefruit app on the uart service.
The errors reported are:
[log] - show dialog
⚡️ To Native -> BluetoothLe write 16540818
⚡️ [log] - send
⚡️ BluetoothLe - Resolve onDisconnected|D0746ABE-530A-9085-FAE6-B2740050BCA1 Disconnected.
⚡️ BluetoothLe - The connection has timed out unexpectedly.
⚡️ BluetoothLe - Reject write|6E400001-B5A3-F393-E0A9-E50E24DCCA9E|6E400002-B5A3-F393-E0A9-E50E24DCCA9E Write timeout.
ERROR MESSAGE: {"message":"Write timeout.","errorMessage":"Write timeout."}
⚡️ [error] - {"message":"Write timeout.","errorMessage":"Write timeout."}
⚡️ [error] - Error sending data: {"errorMessage":"Write timeout."}
Expected behavior
A clear and concise description of what you expected to happen.
I don't expect the timeout and the timeout seems to happen immediately regardless of timeout option supplied
Screenshots
If applicable, add screenshots to help explain your problem.
Plugin version:
- @capacitor-community/bluetooth-le:latest
Desktop (please complete the following information):
- OS: IOS
Smartphone (please complete the following information):
iPhone 11
latest OS
Do you know if the same happens on Android?
If you call getServices
, do you see the service and characteristic and does it have write
property true
?
I know that the blufruit app (ios)writes to uart perfectly fine and no disconnection so there nothing wrong with moddable code used if thats useful, I have not yet tested android as i have none but will get hold of one and test, np
I clicked that button accidentally
This can be closed as it was a hardware issue - works fine on a another esp32-s3 I have.