Skip to content

Commit

Permalink
Merge pull request #5 from reelyactive/master
Browse files Browse the repository at this point in the history
Added Knob Button app for Puck.js (v2)
  • Loading branch information
gfwilliams committed Aug 12, 2024
2 parents 1893504 + a27eef0 commit e731d7b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 0 deletions.
12 changes: 12 additions & 0 deletions apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,17 @@
"storage": [
{"name":".bootcde","url":"app.js"}
]
},
{ "id": "knobbutton",
"name": "Knob Button",
"icon": "icon.png",
"version":"0.01",
"description": "Use the Puck.js (v2) as an anywhere knob: push the button to transmit the angle of rotation in BLE advertising packets.",
"tags": "bluetooth",
"readme": "README.md",
"needsFeatures":["BLE","ACCEL"],
"storage": [
{"name":".bootcde","url":"app.js"}
]
}
]
1 change: 1 addition & 0 deletions apps/knobbutton/ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.01: New App!
26 changes: 26 additions & 0 deletions apps/knobbutton/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Knob Button

Use the Puck.js (v2) as an on-demand knob: rotate to the desired angle and press the button. The angle of rotation will be advertised to any Bluetooth Low Energy receivers in range.

## Usage

Load the app onto the Puck.js (v2) and then:
- hold the Puck.js in a vertical position (ex: against a wall)
- rotate to the desired angle
- press the button
- observe the green LED flash
- observe BLE advertising packets with the angle of rotation data for ~5 seconds

## How it works

The Puck.js (v2) will wake on button press and read the accelerometer. The angle of rotation will be calculated based on the accelerometer's X-axis and Y-axis readings. The value is advertised as a JSON string in a manufacturer-specific data packet using the Espruino company code (0x590), for example:

{angleOfRotation:123}

[Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source IoT middleware will automatically interpret these packets using its [advlib-ble-manufacturers](https://github.com/reelyactive/advlib-ble-manufacturers) library which supports Espruino advertising packets.

Following a button press sequence, the Puck.js (v2) will return to low-power sleep, waking again on any subsequent button press. It will also periodically advertise the name "Knob.js".

## Adapt the code

See the reelyActive's [Puck.js Development Guide](https://reelyactive.github.io/diy/puckjs-dev/) to load the source code in the Espruino IDE and adapt it to meet your needs!
103 changes: 103 additions & 0 deletions apps/knobbutton/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright reelyActive 2022-2024
* We believe in an open Internet of Things
*/


// User-configurable constants
const LED_BLINK_MILLISECONDS = 50;
const STABLE_ACCELERATION_TOLERANCE_G = 0.1;
const ANGLE_ADVERTISING_DURATION_MILLISECONDS = 5000;
const ANGLE_ADVERTISING_PERIOD_MILLISECONDS = 500;
const NAME_ADVERTISING_PERIOD_MILLISECONDS = 5000;


// Non-user-configurable constants
const ACC_SAMPLE_RATE_HZ = 12.5; // Valid values are 1.6, 12.5, 26, 52, 104, 208
const ACC_PER_G = 8192;
const DEG_PER_RAD = 180 / Math.PI;


// Global variables
let advertisingTimeoutId;


// Calculate the angle of rotation based on the given accelerometer reading
function calculateAngleOfRotation(acc) {
let ratioXY = ((acc.y === 0) ? Infinity : Math.abs(acc.x / acc.y));
let ratioYX = ((acc.x === 0) ? Infinity : Math.abs(acc.y / acc.x));

if((acc.x >= 0) && (acc.y >= 0)) {
return Math.round(Math.atan(ratioYX) * DEG_PER_RAD);
}
if((acc.x <= 0) && (acc.y >= 0)) {
return Math.round(90 + (Math.atan(ratioXY) * DEG_PER_RAD));
}
if((acc.x <= 0) && (acc.y <= 0)) {
return Math.round(180 + (Math.atan(ratioYX) * DEG_PER_RAD));
}
if((acc.x >= 0) && (acc.y <= 0)) {
return Math.round(270 + (Math.atan(ratioXY) * DEG_PER_RAD));
}
}


// Advertise the name "Knob.js"
function advertiseName() {
NRF.setAdvertising({}, {
showName: false,

Check warning on line 48 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
manufacturer: 0x0590,

Check warning on line 49 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
manufacturerData: JSON.stringify({ name: "Knob.js" }),

Check warning on line 50 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
interval: NAME_ADVERTISING_PERIOD_MILLISECONDS

Check warning on line 51 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
});
}


// Advertise the angle of rotation for a specific period
function advertiseAngleOfRotation(angleOfRotation) {
if(advertisingTimeoutId) {
clearTimeout(advertisingTimeoutId);
}

NRF.setAdvertising({}, {
showName: false,

Check warning on line 63 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
manufacturer: 0x0590,

Check warning on line 64 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
manufacturerData: JSON.stringify({ angleOfRotation: angleOfRotation }),

Check warning on line 65 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
interval: ANGLE_ADVERTISING_PERIOD_MILLISECONDS

Check warning on line 66 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 6
});

advertisingTimeoutId = setTimeout(advertiseName,
ANGLE_ADVERTISING_DURATION_MILLISECONDS);

Check warning on line 70 in apps/knobbutton/app.js

View workflow job for this annotation

GitHub Actions / test

Expected indentation of 4 spaces but found 36
}


// Handle a button press: blink green LED and initiate accelerometer readings
function handleButton() {
Puck.accelOn(ACC_SAMPLE_RATE_HZ);
LED2.write(true);
setTimeout(function() { LED2.write(false); }, LED_BLINK_MILLISECONDS);
}


// Handle accelerometer reading: terminate accelerometer readings and advertise
// angle of rotation once magnitude is stable
function handleAcceleration(data) {
let magnitude = Math.sqrt((data.acc.x * data.acc.x) +
(data.acc.y * data.acc.y) +
(data.acc.z * data.acc.z)) / ACC_PER_G;
let isStableMagnitude = (magnitude < 1.0 + STABLE_ACCELERATION_TOLERANCE_G) &&
(magnitude > 1.0 - STABLE_ACCELERATION_TOLERANCE_G);

if(isStableMagnitude) {
let angleOfRotation = calculateAngleOfRotation(data.acc);

Puck.accelOff();
advertiseAngleOfRotation(angleOfRotation);
}
}


// Advertise "Knob.js", wake on button press and handle accelerometer readings
advertiseName();
Puck.on('accel', handleAcceleration);
setWatch(handleButton, BTN, { edge: "rising", repeat: true, debounce: 50 });
Binary file added apps/knobbutton/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e731d7b

Please sign in to comment.