Skip to content
Open
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
378 changes: 369 additions & 9 deletions dist/index.browser.esm.js
100644 → 100755

Large diffs are not rendered by default.

384 changes: 373 additions & 11 deletions dist/index.browser.js
100644 → 100755

Large diffs are not rendered by default.

381 changes: 369 additions & 12 deletions dist/index.esm.js
100644 → 100755

Large diffs are not rendered by default.

383 changes: 370 additions & 13 deletions dist/index.js
100644 → 100755

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions examples/example-prediction-node/predictionExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const p = new Predictor(
*/

// setInterval(() => {
// p.addDatapoint('AccelerometerX', getAccelerometerX())
// p.addDatapoint('AccelerometerY', getAccelerometerY())
// p.addDatapoint('AccelerometerZ', getAccelerometerZ())
// p.addDataPoint(Date.now(),'AccelerometerX', getAccelerometerX())
// p.addDataPoint(Date.now(),'AccelerometerY', getAccelerometerY())
// p.addDataPoint(Date.now(),'AccelerometerZ', getAccelerometerZ())

// p.predict()
// .then(x => x)
Expand All @@ -41,7 +41,7 @@ async function runner() {
for (const { time: ti, ...valObjs } of test) {
const time = parseInt(ti);
for (const [key, valStr] of Object.entries(valObjs)) {
p.addDatapoint(key, parseInt(valStr), time)
p.addDataPoint(time, key, parseInt(valStr))
}

try {
Expand Down Expand Up @@ -71,4 +71,4 @@ function csvToArray(str, delimiter = ",") {

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
19 changes: 10 additions & 9 deletions examples/example-prediction-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,27 @@ <h1>devicemotion</h1>

<script>
const warn = (...x) => document.getElementById("warn").innerHTML = x
const startSensors = async (addDatapoint) => {
const startSensors = async (addDataPoint) => {
function handleMotionEvent(event) {
const x = event.accelerationIncludingGravity.x;
const y = event.accelerationIncludingGravity.y;
const z = event.accelerationIncludingGravity.z;
const alpha = event.rotationRate.alpha;
const time = Math.floor(event.timeStamp + performance.timeOrigin);

addDatapoint('x', x)
addDatapoint('y', y)
addDatapoint('z', z)
addDatapoint('alpha', alpha)
addDataPoint(time,'x', x)
addDataPoint(time,'y', y)
addDataPoint(time,'z', z)
addDataPoint(time,'alpha', alpha)
}

window.addEventListener("devicemotion", handleMotionEvent, true);
}
</script>

<script>
const addDatapoint = (...x) => {
p.addDatapoint(...x)
const addDataPoint = (...x) => {
p.addDataPoint(...x)
document.getElementById("numberadded").innerHTML = parseInt(document.getElementById("numberadded").innerHTML) + 1
}

Expand All @@ -63,7 +64,7 @@ <h1>devicemotion</h1>
}

document.getElementById("startsense").onclick = () => {
startSensors(addDatapoint)
startSensors(addDataPoint)
}

document.getElementById("predict").onclick = () => {
Expand All @@ -80,4 +81,4 @@ <h1>devicemotion</h1>
</script>

</body>
</html>
</html>
10 changes: 5 additions & 5 deletions examples/example-shake/sampleModeExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const p = new Predictor(
*/

// setInterval(() => {
// p.addDatapoint('AccelerometerX', getAccelerometerX())
// p.addDatapoint('AccelerometerY', getAccelerometerY())
// p.addDatapoint('AccelerometerZ', getAccelerometerZ())
// p.addDataPoint(Date.now(), 'AccelerometerX', getAccelerometerX())
// p.addDataPoint(Date.now(), 'AccelerometerY', getAccelerometerY())
// p.addDataPoint(Date.now(), 'AccelerometerZ', getAccelerometerZ())

// p.predict()
// .then(x => x)
Expand All @@ -44,7 +44,7 @@ async function runner() {
for (const { time: ti, ...valObjs } of test) {
const time = parseInt(ti);
for (const [key, valStr] of Object.entries(valObjs)) {
p.addDatapoint(key, parseInt(valStr), time)
p.addDataPoint(time, key, parseInt(valStr))
}

try {
Expand Down Expand Up @@ -74,4 +74,4 @@ function csvToArray(str, delimiter = ",") {

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
10 changes: 5 additions & 5 deletions examples/example-shake/timeModeExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const p = new Predictor(
*/

// setInterval(() => {
// p.addDatapoint('AccelerometerX', getAccelerometerX())
// p.addDatapoint('AccelerometerY', getAccelerometerY())
// p.addDatapoint('AccelerometerZ', getAccelerometerZ())
// p.addDataPoint(Date.now(), 'AccelerometerX', getAccelerometerX())
// p.addDataPoint(Date.now(), 'AccelerometerY', getAccelerometerY())
// p.addDataPoint(Date.now(), 'AccelerometerZ', getAccelerometerZ())

// p.predict()
// .then(x => x)
Expand All @@ -44,7 +44,7 @@ async function runner() {
for (const { time: ti, ...valObjs } of test) {
const time = parseInt(ti);
for (const [key, valStr] of Object.entries(valObjs)) {
p.addDatapoint(key, parseInt(valStr), time)
p.addDataPoint(time, key, parseInt(valStr))
}

try {
Expand Down Expand Up @@ -74,4 +74,4 @@ function csvToArray(str, delimiter = ",") {

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
19 changes: 10 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@rollup/plugin-node-resolve": "^13.3.0",
"husky": "^7.0.4",
"jest": "^28.0.3",
"rollup": "^2.75.7",
"rollup": "^2.79.2",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-polyfill-node": "^0.10.0"
}
Expand Down
18 changes: 9 additions & 9 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import commonjs from '@rollup/plugin-commonjs';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import nodePolyfills from 'rollup-plugin-polyfill-node';
import json from '@rollup/plugin-json';
// import copy from 'rollup-plugin-copy'
import copy from 'rollup-plugin-copy'

import { readFileSync } from 'fs';

const pkg = JSON.parse(readFileSync('./package.json'))

// const copyOpts = {
// targets: [
// { src: 'src/vendor/edge-fel/edge-fel.wasm', dest: 'dist' }
// ]
// }
const copyOpts = {
targets: [
{ src: 'src/vendor/edge-fel/edge-fel.wasm', dest: 'dist' }
]
}

export default [
// browser-friendly builds
Expand All @@ -31,7 +31,7 @@ export default [
format: 'es'
}],
plugins: [
// copy(copyOpts),
copy(copyOpts),
nodeResolve({ preferBuiltins: true, browser: true }),
commonjs({ transformMixedEsModules: true, include: ["src/**", "node_modules/**"], strictRequires: true }), // so Rollup can convert to ES module
]
Expand All @@ -50,10 +50,10 @@ export default [
{ file: pkg.module, format: 'es' } // from package.json
],
plugins: [
// copy(copyOpts),
copy(copyOpts),
nodeResolve(),
json(),
commonjs({ transformMixedEsModules: true, include: ["src/**", "node_modules/**"], strictRequires: true }), // so Rollup can convert to ES module
]
},
];
];
24 changes: 13 additions & 11 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Predictor } from './predictor/index.js';

const axios = require("axios")

const UPLOAD_INTERVAL = 5 * 1000;
Expand Down Expand Up @@ -80,23 +82,22 @@ async function datasetCollector(
var timeSeries = timeSeries;

/**
* Uploads a vlaue for a specific timestamp to a datasets timeSeries with name sensorName
* @param {string} name - The name of the timeSeries to upload the value to
* Uploads a value for a specific timestamp to a dataset's timeSeries with name sensorName
* @param {string} sensorName - The name of the timeSeries to upload the value to
* @param {number} value - The datapoint to upload
* @param {number} time - The timestamp assigned to the datapoint
* @returns A Promise indicating success or failure of upload
*/
function addDataPoint(time, name, value) {
function addDataPoint(time, sensorName, value) {

if (!timeSeries.includes(name)) {
if (!timeSeries.includes(sensorName)) {
throw Error("invalid time-series name")
}

if (error) {
throw new Error(error);
}
if (typeof value !== "number") {
throw new Error("Datapoint is not a number");
throw new Error("DataPoint value is not a number");
}
if (!useDeviceTime && typeof time !== "number") {
throw new Error("Provide a valid timestamp");
Expand All @@ -108,14 +109,14 @@ async function datasetCollector(

value = Math.round(value * 100) / 100;

if (dataStore.data.every((elm) => elm.name !== name)) {
if (dataStore.data.every((elm) => elm.name !== sensorName)) {
dataStore.data.push({
name: name,
name: sensorName,
data: [[time, value]],
});
} else {
const idx = dataStore.data.findIndex(
(elm) => elm.name === name
(elm) => elm.name === sensorName
);
dataStore.data[idx].data.push([time, value]);

Expand Down Expand Up @@ -170,8 +171,9 @@ async function datasetCollector(

const edgeML = {
datasetCollector: datasetCollector,
sendDataset: sendDataset
sendDataset: sendDataset,
Predictor: Predictor

};

export default edgeML;
export default edgeML;
30 changes: 23 additions & 7 deletions src/predictor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ export const Predictor = class Predictor {
* @param {string[]} labels
* @param {{ center: { [featureName: string]: number }, scale: { [featureName: string]: number } }} scaler
* @param {{ windowingMode: "time" | "sample" }} options
* @param {boolean} useDeviceTime - True if you want to use timestamps generated by the server
*/
constructor(predictor, sensors, windowSize, labels, scaler, { windowingMode = "sample" } = {}) {
constructor(predictor, sensors, windowSize, labels, scaler, { windowingMode = "sample", useDeviceTime = false } = {}) {
/** @type {(input: number[]) => number[]} */
this.predictor = predictor;
/** @type {string[]} */
Expand All @@ -47,6 +48,9 @@ export const Predictor = class Predictor {

/** @type {boolean} */
this.windowModeMs = windowSize < 0 || windowingMode === "time"
/** @type {boolean} */
this.useDeviceTime = useDeviceTime

this.lastPruneTime = 0;
this.lastAddTime = 0;

Expand All @@ -58,15 +62,24 @@ export const Predictor = class Predictor {
}

/**
* addDatapoint
* addDataPoint
* @param {number} time - The timestamp assigned to the datapoint
* @param {string} sensorName
* @param {number} value
* @param {number | null} time use a predefined timestamp, or null if the timestamp should be generated
*/
addDatapoint = (sensorName, value, time = null) => {
if (typeof value !== 'number') throw new TypeError('Datapoint is not a number');
addDataPoint = (time, sensorName, value) => {
if (typeof value !== 'number') throw new TypeError('DataPoint value is not a number');
if (!this.sensors.includes(sensorName)) throw new TypeError('Sensor is not valid');
if (time === null) time = Date.now()
// TODO: see #16 use if (time === null) time = Date.now()
if (!this.useDeviceTime && typeof time !== "number") {
throw new TypeError("Provide a valid timestamp");
}
if (this.useDeviceTime) {
time = Date.now();
}

//TODO: see #17 no clue why see Collector
Copy link

Copilot AI Jul 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This TODO comment is vague and lacks context. Please clarify the required action or remove it if it's no longer relevant.

Suggested change
//TODO: see #17 no clue why see Collector
// TODO: Investigate the relevance of "Collector" in this context and document why rounding the value is necessary.

Copilot uses AI. Check for mistakes.
value = Math.round(value * 100) / 100;

this.lastAddTime = time
this.store[sensorName].push([time, value]);
Expand Down Expand Up @@ -105,6 +118,9 @@ export const Predictor = class Predictor {
let window;
if (this.windowModeMs) {
window = Predictor._sliceByTime(samples, this.lastAddTime - this.windowSize)
if (window.length < 1 ) {
throw new PredictorError("Not enough samples")
}
} else {
window = samples.slice(-this.windowSize)
if (window.length < this.windowSize) {
Expand Down Expand Up @@ -225,4 +241,4 @@ Predictor.featuresTSfresh = [
"min"
]
Predictor.felParams = cache(() => objToMap({"mean_n_abs_max_n": 8, "change_quantile_lower": -0.1, "change_quantile_upper": 0.1, "change_quantile_aggr": 0, "range_count_lower": -1, "range_count_upper": 1, "count_above_x": 0, "count_below_x": 0, "quantile_q": 0.5, "autocorrelation_lag": 1}))
Predictor.felFeaturesTSfresh = cache(() => arrToVector(Predictor.featuresTSfresh, 'string'))
Predictor.felFeaturesTSfresh = cache(() => arrToVector(Predictor.featuresTSfresh, 'string'))