Skip to content

Commit e731d7b

Browse files
authoredAug 12, 2024··
Merge pull request #5 from reelyactive/master
Added Knob Button app for Puck.js (v2)
2 parents 1893504 + a27eef0 commit e731d7b

File tree

5 files changed

+142
-0
lines changed

5 files changed

+142
-0
lines changed
 

‎apps.json

+12
Original file line numberDiff line numberDiff line change
@@ -277,5 +277,17 @@
277277
"storage": [
278278
{"name":".bootcde","url":"app.js"}
279279
]
280+
},
281+
{ "id": "knobbutton",
282+
"name": "Knob Button",
283+
"icon": "icon.png",
284+
"version":"0.01",
285+
"description": "Use the Puck.js (v2) as an anywhere knob: push the button to transmit the angle of rotation in BLE advertising packets.",
286+
"tags": "bluetooth",
287+
"readme": "README.md",
288+
"needsFeatures":["BLE","ACCEL"],
289+
"storage": [
290+
{"name":".bootcde","url":"app.js"}
291+
]
280292
}
281293
]

‎apps/knobbutton/ChangeLog

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.01: New App!

‎apps/knobbutton/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Knob Button
2+
3+
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.
4+
5+
## Usage
6+
7+
Load the app onto the Puck.js (v2) and then:
8+
- hold the Puck.js in a vertical position (ex: against a wall)
9+
- rotate to the desired angle
10+
- press the button
11+
- observe the green LED flash
12+
- observe BLE advertising packets with the angle of rotation data for ~5 seconds
13+
14+
## How it works
15+
16+
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:
17+
18+
{angleOfRotation:123}
19+
20+
[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.
21+
22+
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".
23+
24+
## Adapt the code
25+
26+
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!

‎apps/knobbutton/app.js

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Copyright reelyActive 2022-2024
3+
* We believe in an open Internet of Things
4+
*/
5+
6+
7+
// User-configurable constants
8+
const LED_BLINK_MILLISECONDS = 50;
9+
const STABLE_ACCELERATION_TOLERANCE_G = 0.1;
10+
const ANGLE_ADVERTISING_DURATION_MILLISECONDS = 5000;
11+
const ANGLE_ADVERTISING_PERIOD_MILLISECONDS = 500;
12+
const NAME_ADVERTISING_PERIOD_MILLISECONDS = 5000;
13+
14+
15+
// Non-user-configurable constants
16+
const ACC_SAMPLE_RATE_HZ = 12.5; // Valid values are 1.6, 12.5, 26, 52, 104, 208
17+
const ACC_PER_G = 8192;
18+
const DEG_PER_RAD = 180 / Math.PI;
19+
20+
21+
// Global variables
22+
let advertisingTimeoutId;
23+
24+
25+
// Calculate the angle of rotation based on the given accelerometer reading
26+
function calculateAngleOfRotation(acc) {
27+
let ratioXY = ((acc.y === 0) ? Infinity : Math.abs(acc.x / acc.y));
28+
let ratioYX = ((acc.x === 0) ? Infinity : Math.abs(acc.y / acc.x));
29+
30+
if((acc.x >= 0) && (acc.y >= 0)) {
31+
return Math.round(Math.atan(ratioYX) * DEG_PER_RAD);
32+
}
33+
if((acc.x <= 0) && (acc.y >= 0)) {
34+
return Math.round(90 + (Math.atan(ratioXY) * DEG_PER_RAD));
35+
}
36+
if((acc.x <= 0) && (acc.y <= 0)) {
37+
return Math.round(180 + (Math.atan(ratioYX) * DEG_PER_RAD));
38+
}
39+
if((acc.x >= 0) && (acc.y <= 0)) {
40+
return Math.round(270 + (Math.atan(ratioXY) * DEG_PER_RAD));
41+
}
42+
}
43+
44+
45+
// Advertise the name "Knob.js"
46+
function advertiseName() {
47+
NRF.setAdvertising({}, {
48+
showName: false,
49+
manufacturer: 0x0590,
50+
manufacturerData: JSON.stringify({ name: "Knob.js" }),
51+
interval: NAME_ADVERTISING_PERIOD_MILLISECONDS
52+
});
53+
}
54+
55+
56+
// Advertise the angle of rotation for a specific period
57+
function advertiseAngleOfRotation(angleOfRotation) {
58+
if(advertisingTimeoutId) {
59+
clearTimeout(advertisingTimeoutId);
60+
}
61+
62+
NRF.setAdvertising({}, {
63+
showName: false,
64+
manufacturer: 0x0590,
65+
manufacturerData: JSON.stringify({ angleOfRotation: angleOfRotation }),
66+
interval: ANGLE_ADVERTISING_PERIOD_MILLISECONDS
67+
});
68+
69+
advertisingTimeoutId = setTimeout(advertiseName,
70+
ANGLE_ADVERTISING_DURATION_MILLISECONDS);
71+
}
72+
73+
74+
// Handle a button press: blink green LED and initiate accelerometer readings
75+
function handleButton() {
76+
Puck.accelOn(ACC_SAMPLE_RATE_HZ);
77+
LED2.write(true);
78+
setTimeout(function() { LED2.write(false); }, LED_BLINK_MILLISECONDS);
79+
}
80+
81+
82+
// Handle accelerometer reading: terminate accelerometer readings and advertise
83+
// angle of rotation once magnitude is stable
84+
function handleAcceleration(data) {
85+
let magnitude = Math.sqrt((data.acc.x * data.acc.x) +
86+
(data.acc.y * data.acc.y) +
87+
(data.acc.z * data.acc.z)) / ACC_PER_G;
88+
let isStableMagnitude = (magnitude < 1.0 + STABLE_ACCELERATION_TOLERANCE_G) &&
89+
(magnitude > 1.0 - STABLE_ACCELERATION_TOLERANCE_G);
90+
91+
if(isStableMagnitude) {
92+
let angleOfRotation = calculateAngleOfRotation(data.acc);
93+
94+
Puck.accelOff();
95+
advertiseAngleOfRotation(angleOfRotation);
96+
}
97+
}
98+
99+
100+
// Advertise "Knob.js", wake on button press and handle accelerometer readings
101+
advertiseName();
102+
Puck.on('accel', handleAcceleration);
103+
setWatch(handleButton, BTN, { edge: "rising", repeat: true, debounce: 50 });

‎apps/knobbutton/icon.png

1.25 KB
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.