web-bluetooth get stored data from device GATT - javascript

My goal is to get data stored in device.
Like device which is measuring temp or whatever and store it to its memory. i need to query all this data device has via Record Access Control Point (RACP).
First thought, to achieve it, was
get characteristic
start notifications
write code to descriptor
get all data via eventListener
result: throws error on starting notifications
examples used:
https://googlechrome.github.io/samples/web-bluetooth/notifications-async-await.html
https://bugs.chromium.org/p/chromium/issues/detail?id=664863
Next thought was to not starting notification since characteristic is
INDICATE, WRITE type.
So was thinking about add listener and write to descriptor code from device docs which states:
OP Code:
1 – Report stored records
even with deleted startNotifications line is throwing error
so my code example is:
const mainService = 'my correct service uuid';
const characteristicUUID1 = 'my correct char uuid';
const characteristicUUID2 = 'my correct char uuid';
const descriptorUUID = '00002902-0000-1000-8000-00805f9b34fb';
let deviceCache = null;
let serverCache = null;
let serviceCache = null;
let characteristicCacheA = null;
let characteristicCacheB = null;
let descriptorCache = null;
try {
deviceCache = await navigator.bluetooth.requestDevice({ filters: [{ name: 'my device' }] });
console.log('Connecting to GATT Server...');
serverCache = await deviceCache.gatt.connect();
console.log('Getting Services...');
serviceCache = await serverCache.getPrimaryService(mainService);
console.log('Getting Characteristics A...');
characteristicCacheA = await serviceCache.getCharacteristic(characteristicUUID1);
console.log('Start Notifications A...');
await characteristicCacheA.startNotifications();
console.log('Getting Characteristics B...');
characteristicCacheB = await serviceCache.getCharacteristic(characteristicUUID2);
console.log('Start Notifications B...');
await characteristicCacheB.startNotifications();
console.log('Add event listener...');
characteristicCacheA.addEventListener('characteristicvaluechanged', this.handleNotifications);
console.log('Getting Descriptor...');
descriptorCache = await characteristicCacheA.getDescriptor(descriptorUUID);
console.log('Write value to descr...');
await descriptorCache.writeValue(new Uint8Array([1]));
} catch (error) {
console.log(error.message, 'error');
}
Error with notifications is(with experimental chrome features it doesn't throw error):
error: GATT operation failed for unknown reason.
Error with descriptor is:
writeValue() called on blocklisted object marked exclude-writes.
Also my device is asking for pin but web is connecting without prompting anything. And so maybe it says that writing to descriptor is blocked.
How to handle pin input - no clue(once i got prompt to enter pin after enabling chrome experimental features not sure if its related).
Is my logic correct? - dont think so.
Any suggestions?
What i have investigated so far?
https://googlechrome.github.io/samples/web-bluetooth/
https://www.oreilly.com/library/view/getting-started-with/9781491900550/ch04.html
https://webbluetoothcg.github.io/web-bluetooth/
Edit: After reading this article - https://medium.com/#devdevcharlie/experimenting-with-web-bluetooth-1f1176047ddd
I think correct logic should be, write to command characteristic commands you need(like get all data). After that find correct characteristic responsible for this data from device docs, and start notifications and add eventListener and receive data.

The call to writeValue() is failing because access to the CCCD is on the blocklist. The call to startNotifications() will write to the descriptor as necessary to enable notifications.
We need to investigate this "unknown reason" for startNotifications() failing. What operating system are you using? Please follow the instructions for reporting Web Bluetooth bugs and file an issue in the Chromium project's issue tracker.

As for now, chrome is not able or got issues with communicating with protected characteristics on windows 10, on macOS it is all working perfectly. I have posted issue on chromium bug tracker if someone is in to watching it.
https://bugs.chromium.org/p/chromium/issues/detail?id=960258#c6

Related

Why do I have an error "TypeError: Cannot read properties of null (reading 'getTracks')", but the code is working?

Here is my code :
const videoElem = document.getElementById("videoScan");
var stream = document.getElementById("videoScan").srcObject;
var tracks = stream.getTracks();
tracks.forEach(function (track) {
track.stop();
});
videoElem.srcObject = null;
I am trying to stop all camera streams in order to make ZXing work. However, I have the following error (only on android tablet) :
Problem is, I can't solve it, but everything is working well. And when I do a try/catch, code stops working. I can't figure out why this error is displaying.
When you say "everything is working well", you mean that you get what you want as output?
If the code throwing the error is the same you are showing us, then it means that streams contains null.
Since streams is a const value (thus once only) assigned on the previous instructions, it means that document.getElementById("videoScan").srcObject is null too.
I'd suggest you to do some other debug by, i.e., printing out to JS console (if you have access to it from your Android debug environment, I'm sure there's a way) what's inside stream, before trying to access its getTracks method reference.
Please do also check the compatibility with srcObject with your Android (browser?) environment: MDN has a compatibility list you can inspect.
The simplest way to catch this null reference error, is to check if tracks actually exists.
const videoElem = document.getElementById("videoScan");
const stream = videoElem.srcObject;
if (stream && stream?.getTracks()) {
const tracks = stream.getTracks();
tracks.forEach(function (track) {
track.stop();
});
} else {
console.warn('Stream object is not available');
}
videoElem.srcObject = null;
So I found the solution. The error stopped the code from continuing, which, in my case, actually made it work. I made a very basic mistake : not paying enough attention to the debug results. However, if anyone has the error "Uncaught (in promise) DOMException: Could not start video source" with ZXing on Android, you simply have to cut all video streams to allow chrome starting an other camera. It will not work otherwise.

TypeError: Assignment to constant variable ,After Changing it to let/var

Im trying to set the binary path of a binary of chrome with selenium, in javascript language.
unfortunately, my knowledge in javascript is limited, and Im getting an error while trying to do so, in which I cannot solve, despite my efforts.
so without further ado, I will now share my problem, with the hope that someone with a better knowledge in javascript then me, will help me
some background:
Im triggering a function in the firebase could functions,
inside this function , I'm trying to create a selenium webdriver.
in order to do so:
I need to do those things:
chromedriver --> that work on a linux system(located inside the functions project folder)✅
chrome browser binary that is located on this machine ✅
3.then, I need to create a a chrome Options object.
a. adding an Argument so it will be headless.✅
b. setting it with a path to the chrome binary.❌
and at last, create a chrome driver with options, that I have created
currently, I'm at stage 3.b
the error that rise coming from my poor knowledge in javascript
this is the error :
TypeError: Assignment to constant variable.
here what's lead to this error
this is my code :
exports.initializedChromeDriver = functions.https.onRequest((request, response) => {
async function start_chrome_driver() {
try {
functions.logger.info('Hello logs!', {structuredData: true});
console.log("did enter the function")
const google_site = "https://www.gooogle.com";
const { WebDriver } = require('selenium-webdriver');
const {Builder, By} = require('selenium-webdriver');
console.log("will try to initialzed chrome");
let chrome = require('selenium-webdriver/chrome');
console.log("did initialzed chrome");
var chrome_options = new chrome.Options()
console.log("will try to set the chrome binary Path");
functions.logger.info('new chrome.Options()', {structuredData: true});
chrome_options = chrome_options.setChromeBinaryPath(path="/usr/bin/google-chrome");// <------- THIS IS THE LINE THAT RISE THE ERROR!
console.log("did setChromeBinaryPath");
chrome_options.addArguments("--headless");
let buillder = new Builder().forBrowser('chrome');
functions.logger.info(' did new Builder().forBrowser(chrome)', {structuredData: true});
const google_site = 'https://wwww.google.com'
await driver.get(google_site);
functions.logger.info('driver did open google site', {structuredData: true});
return "succ loading google"
}
catch (err) {
console.log('did catch')
console.error(err);
return "error loading google";
}
}
const p = start_chrome_driver().then((value,reject) => {
const dic = {};
dic['status'] = 200;
dic['data'] = {"message": value};
response.send(dic);
});
and here's the error that follows this code in the firebase functions logs:
I tried to change the chrome_options object into var/let, and looking for answers in the web , but after deploying again, and again, and again.. I feel like its time to get another perspective, any help will do.
You have an unnecessary assignment here (path=...)
chrome_options = chrome_options.setChromeBinaryPath(path="/usr/bin/google-chrome");
Just remove the assignment to path
chrome_options = chrome_options.setChromeBinaryPath("/usr/bin/google-chrome");

Is there a way to get Chrome to “forget” a device to test navigator.usb.requestDevice, navigator.serial.requestPort?

I'm hoping to migrate from using WebUSB to SerialAPI (which is explained nicely here).
Current Code:
try {
let device = await navigator.usb.requestDevice({
filters: [{
usbVendorId: RECEIVER_VENDOR_ID
}]
})
this.connect(device)
} catch (error) {
console.log(DEVICE_NAME + ': Permission Denied')
}
New Code:
try {
let device = await navigator.serial.requestPort({
filters: [{
usbVendorId: RECEIVER_VENDOR_ID
}]
})
this.connect(device)
} catch (error) {
console.log(DEVICE_NAME + ': Permission Denied')
}
The new code appears to work, but I think it's because the browser has already requested the device via the old code.
I've tried restarting Chrome as well as clearing all of the browsing history. Even closed the USB-claiming page and claimed the device with another app (during which it returns the DOMException: Unable to claim interface error), but Chrome doesn't seem to want to ask again. It just happily streams the data with the previous connection.
My hope was that using SerialAPI would be a way to avoid fighting over the USB with other processes, or at least losing to them.
Update
I had forgotten about:
Failed to execute 'requestPort' on 'Serial': "Must be handling a user gesture to show a permission request"
Does this mean that the user will need to use a button to connect to the device via SerialUSB? I think with WebUSB I was able to make the connect window automatically pop up.
For both APIs, as is noted in the update, a user gesture is required in order to call the requestDevice() or requestPort() method. It is not possible to automatically pop up this prompt. (If there is that's a bug so please let the Chrome team know so we can fix it.)
Permissions granted to a site through the WebUSB API and Web Serial API are currently tracked separately so permission to access a device through one will not automatically translate into the other.
There is not currently a way to programatically forget a device permission. That would require the navigator.permissions.revoke() method which has been abandoned. You can however manually revoke permission to access the device by clicking on the "lock" icon in the address bar while visiting the site or going to chrome://settings/content/usbDevices (for USB devices) and chrome://settings/content/serialPorts (for serial ports).
To get Chrome to "forget" the WebUSB device previously paired via navigator.usb.requestDevice API:
Open the page paired to the device you want to forget
Click on the icon in the address bar
Click x next to device. If nothing is listed, then there are no paired devices for this web page.
The new code was NOT working. It just appeared to be because Chrome was already paired with the port via the old code. There is no way the "new code" could have worked because, as noted in Kalido's comment, the SerialAPI (due to its power) requires a user gesture to connect.
The code I'm using to actually connect and get data is basically built up from a few pieces from the above links in the OP:
navigator.serial.addEventListener('connect', e => {
// Add |e.target| to the UI or automatically connect.
console.log("connected");
});
navigator.serial.addEventListener('disconnect', e => {
// Remove |e.target| from the UI. If the device was open the disconnection can
// also be observed as a stream error.
console.log("disconnected");
});
console.log(navigator.serial);
document.addEventListener('DOMContentLoaded', async () => {
const connectButton = document.querySelector('#connect') as HTMLInputElement;
if (connectButton) {
connectButton.addEventListener('click', async () => {
try {
// Request Keiser Receiver from the user.
const port = await navigator.serial.requestPort({
filters: [{ usbVendorId: 0x2341, usbProductId: not_required }]
});
try {
// Open and begin reading.
await port.open({ baudRate: 115200 });
} catch (e) {
console.log(e);
}
while (port.readable) {
const reader = port.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
// Allow the serial port to be closed later.
reader.releaseLock();
break;
}
if (value) {
console.log(value);
}
}
} catch (error) {
// TODO: Handle non-fatal read error.
console.log(error);
}
}
} catch (e) {
console.log("Permission to access a device was denied implicitly or explicitly by the user.");
console.log(e);
console.log(port);
}
}
}
The device-specific Vendor and Product IDs would obviously change from device to device. In the above example I have inserted an Arduino vendor ID.
It doesn't answer the question of how to get Chrome to "forget", but I'm not sure if that's relevant when using SerialAPI because of the required gesture.
Hopefully someone with more experience will be able to post a more informative answer.

Web Serial API - Uncaught (in promise) DOMException: Failed to open serial port / required member baudRate is undefined

The code below works on my Xubuntu machine, but now I'm on Kubuntu and it isn't working anymore - it won't open the port.
The Arduino IDE works fine (can write code to the board) and I'm able to select the device (Arduino Uno) in Chrome, but the code will stop when I try to open the port: Uncaught (in promise) DOMException: Failed to open serial port or required member baudRate is undefined will come up.
const filters = [
// Filter on devices with the Arduino Uno USB Vendor/Product IDs.
{ usbVendorId: 0x2341, usbProductId: 0x0043 },
{ usbVendorId: 0x2341, usbProductId: 0x0001 },
];
async function getPortAndStartReading() {
if (!portFound) {
const port = await navigator.serial.requestPort({ filters });
await port.open({ baudRate: 9600 }) //problem here
reader = port.readable.getReader();
outputStream = port.writable
readLoop();
if (port) {
connectionToPortSuccessfulMessage = 'Connection successful'
setPortFound(true)
}
}
}
I've tried changing the permissions on the serial port by following this, so now if I run groups user I get user : user adm dialout cdrom sudo dip plugdev lpadmin lxd sambashare, but it still won't work.
I've also checked chrome://device-log to see if I could find any errors but all I get is info about (physically) adding or removing a USB device.
I believe the member name has recently been changed from 'baudrate' to 'baudRate'. At least in my case changing from 'baudrate' (which used to work) to 'baudRate' fixed it for me. Could it perhaps be Kubuntu is using an older chrome version that expects 'baudrate'.

Is it possible to broadcast a SharedArrayBuffer to Web Workers via the Broadcast Channel API?

The Broadcast Channel API seems like an alternative to postMessage or the Channel Messaging API (aka MessageChannel.)
I've used both successfully in recent versions of Google Chrome to send Shared Array Buffers; however, I am having trouble sending a Shared Array Buffer using the Broadcast Channel API.
Mozilla's doc at https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel references the spec at https://html.spec.whatwg.org/multipage/web-messaging.html#broadcastchannel, which says:
For each destination in destinations...
Let data be StructuredDeserialize(serialized, targetRealm).
If this throws an exception, catch it, fire an event named messageerror at destination, using MessageEvent, with the origin attribute initialized to the serialization of sourceOrigin, and then abort these steps.
StructuredDeserialize is defined at https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize and seems to suggest it covers SharedArrayBuffers:
Otherwise, if serialized.[[Type]] is "SharedArrayBuffer", then:
If targetRealm's corresponding agent cluster is not serialized.[[AgentCluster]], then then throw a "DataCloneError" DOMException.
Otherwise, set value to a new SharedArrayBuffer object in targetRealm whose [[ArrayBufferData]] internal slot value is serialized.[[ArrayBufferData]] and whose [[ArrayBufferByteLength]] internal slot value is serialized.[[ArrayBufferByteLength]].
Reading this, it seems to me that this should work, but I'm getting a message event where the data is simply null.
If this were a security issue, I would expect to get a messageerror event instead of a message event.
Here's my minimal test case:
broadcast-test.html (must be served from an http server - won't work via file://)
<!DOCTYPE html>
<html>
<head><title></title></head>
<body>
<script src="broadcast-test.js"></script>
</body>
</html>
broadcast-test.js
const isThisTheWorker = this.document === undefined
const broadcastChannel = new BroadcastChannel('foo')
if (!isThisTheWorker) {
broadcastChannel.addEventListener('message', (event) => {
console.log('main received', event.data)
const sab = new SharedArrayBuffer(100)
broadcastChannel.postMessage({ hello: 'from main', sab })
})
var myWorker = new Worker('broadcast-test.js')
}
else {
broadcastChannel.addEventListener('message', (event) => {
console.log('worker received', event.data)
})
broadcastChannel.postMessage({ hello: 'from worker' })
}
Observed console output: (Chrome 84.0.4147.135 on Windows 10)
main received {hello: "from worker"}
worker received null
Is Google Chrome's implementation incorrect, or am I misunderstanding the spec?

Categories