Files
Jack Hsieh e7c398bf93 CO2 meter extension - a sample for using WebHID on extension service worker. (#921)
* Initial README.md file

* fix design doc link

* Update README.md adding scheib

* Update README.md

* test

* update README.md with alvinjiooo

* add a ascii fish in README.md

* Update README.md

* skeloton extension

* Add original co2meter.html

* add buttons to settings page

* Initial storage module stub.

* Add storage module reference from settings page.

* Fix settings JS to be module compatible.

* Add stubs for settings storage writing

* Call saveCO2Value() from onInputReport in co2meter.html

* background page to read CO2 periodically based on the stored interval

* verion 1.0 /module/co2_meter.js

* Refactor settings.html and hook up CO2 driver

* update v2 of /module/co2_meter.js which access all methods from CO2Meter class

* Fix co2_meter import in background.js

* background page to show CO2 meter disconnected icon

* v2.1 update /module/co2_meter.js add support for virtual meter and connection listener also rquestPermission

* v2.1 update /module/co2_meter.js

* Import idb-keyval indexedDB library into storage.js, with trivial usage.

* background page handle disconnect connect device

* update v2.2 /module/co2_meter.js make  non-static and add guarding flag for exccessive reading

* update v2.3 /module/co2_meter.js await device to close before open device in

* update v2.4 /module/co2_meter.js change to return reject promise instead of Error in

* Add third-party/idb-keyval, use to set/get interval.

* Simplify settings and storage.

* handle background page device disconnect/connect

* Rename *_script.js files to only *.js.

* Add temp reading

* Add chart to popup.html with chartjs etc.

* Update extension tool tip to show connected/disconnected status.

* use indexedDB to store CO2 reading

* Renaming items in background.js

Name methods more clearly for what they are doing.
Collapse some code when possible to be inline.

* add  in Storage

* add support for storing temperature and query temperature in range

* background page boardcasting reading updated

* Refactor chart out of popup into chart.html for iframing

* Continued: chart out of popup into chart.html for iframing

* Use message channel for message broadcasting and move common constant to constant.js

* Work in progress for data into chart.

* Use set for clients in background page

* Add promise to storage constructor.

* Change IDB version number due to schema change

* Populate chart data.

* check store name before creating when version changed

* increase popup window and iframe size

* Update chart upon new data.

* Add calibration period and refactor inputReport

* Use flexbox to layout pages.

* move internal and temperature unit into setting store

* move dbInitialized into transaction oncomplete event

* remove reading out interval and temp unit log

* Remove co2meter.html

* Add .map files for third party code to remove devtools warnings.

* Display chart in Fahrenheit

* remove idb-keyvalue

* prettier storage.js and add comments to public methods

* Chart style: time axis respects time of data, no data points

* Show dialog on the chart when device is disconnected

* Add Example Data Button

* Close dialog button

* Implement toggling between Celsius / Fahrenheit

* polish co2_meter.js and remove virtual device and change default interval to 30 sec

* Change small icon to CO2 text

* change reading interval default to 30 secs which helps the chart update faster for fresh load case

* Fix lint error

* Clean up console.log

* Add chrome.alarm example

* remove keep alive for reading alarm

* refactor to keep connection open

* add README.md

* strip down

* addressed comments

* add link to README.md

---------

Co-authored-by: Vincent Scheib <scheib@users.noreply.github.com>
Co-authored-by: Alvin Ji <111466895+alvinjiooo@users.noreply.github.com>
Co-authored-by: Alvin Ji <alvinji@chromium.org>
Co-authored-by: Alpaca Jam <alvinji@google.com>
2023-06-16 20:56:03 +02:00

164 lines
4.5 KiB
JavaScript

// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @filename co2_meter.js
*
* @description CO2Meter provides methods for accessing status and data of a
* CO2 meter. When creating a CO2Meter, it has to await `init()` to finish
* before quering device status.
*/
import { PERMISSION_GRANTED_MESSAGE } from './constant.js';
const key = new Uint8Array([0xc4, 0xc6, 0xc0, 0x92, 0x40, 0x23, 0xdc, 0x96]);
function KelvinToFahrenheit(k) {
return Math.trunc(((k - 273.15) * 9) / 5 + 32);
}
class CO2Meter {
constructor() {
this.device = null;
this.connectClientCB = null;
this.disconnectClientCB = null;
this.co2ReadingClientCB = null;
this.tempReadingClientCB = null;
this.connectHandler = this.connectHandler.bind(this);
this.disconnectHandler = this.disconnectHandler.bind(this);
this.onInputReport = this.onInputReport.bind(this);
}
/**
* @description This function initializes the CO2Meter object.
*/
async init(
connectCallback = null,
disconnectCallback = null,
co2ReadingCallback = null,
tempReadingCallback = null
) {
this.connectClientCB = connectCallback;
this.disconnectClientCB = disconnectCallback;
this.co2ReadingClientCB = co2ReadingCallback;
this.tempReadingClientCB = tempReadingCallback;
navigator.hid.addEventListener('connect', this.connectHandler);
navigator.hid.addEventListener('disconnect', this.disconnectHandler);
console.log('CO2Meter init() done');
}
async startReading() {
if (this.device) {
console.log('CO2 reading has already started!');
return;
}
const devices = await navigator.hid.getDevices();
if (devices.length == 0) {
throw 'No CO2 meter for reading!';
}
this.device = devices[0];
try {
await this.device.open();
await this.device.sendFeatureReport(0, key);
} catch (e) {
console.log('CO2 reading exception:', e);
await this.device.close();
this.device = null;
throw 'Fail to open CO2 meter for reading!';
}
this.device.addEventListener('inputreport', this.onInputReport);
}
async stopReading() {
if (this.device) {
this.device.removeEventListener('inputreport', this.onInputReport);
await this.device.close();
this.device = null;
}
}
onInputReport(report) {
let data = new Uint8Array(
report.data.buffer,
report.data.byteOffset,
report.data.byteLength
);
const op = data[0];
let val = (data[1] << 8) | data[2];
if (op == 0x50) {
console.log(`Current CO2 reading is ${val}`);
if (this.co2ReadingClientCB) {
this.co2ReadingClientCB(val);
}
} else if (op == 0x42) {
val = val / 16;
console.log(`Current Temp reading is ${val}`);
if (this.tempReadingClientCB) {
this.tempReadingClientCB(val);
}
}
}
/**
* @description Request user to grant permission for using CO2 meter.
* The extension currently only support this model:
* https://www.co2meter.com/products/co2mini-co2-indoor-air-quality-monitor
*/
async requestPermission() {
const devices = await navigator.hid.requestDevice({
filters: [{ vendorId: 1241, productId: 41042 }]
});
console.log('CO2 meter permission granted!', devices[0]);
chrome.runtime.sendMessage(PERMISSION_GRANTED_MESSAGE);
}
connectHandler() {
if (this.connectClientCB && typeof this.connectClientCB === 'function') {
this.connectClientCB();
}
}
disconnectHandler() {
if (this.device) {
this.device.close();
}
this.device = null;
if (
this.disconnectClientCB &&
typeof this.disconnectClientCB === 'function'
) {
this.disconnectClientCB();
}
}
/**
* @description Get Device connected status.
* @return {Boolean}
*/
async getDeviceStatus() {
const devices = await navigator.hid.getDevices();
return devices.length > 0;
}
tempReadingToFahrenheit(temp_reading) {
return KelvinToFahrenheit(temp_reading);
}
}
export default new CO2Meter();