-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from reelyactive/master
Added Knob Button app for Puck.js (v2)
- Loading branch information
Showing
5 changed files
with
142 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0.01: New App! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
manufacturer: 0x0590, | ||
manufacturerData: JSON.stringify({ name: "Knob.js" }), | ||
interval: NAME_ADVERTISING_PERIOD_MILLISECONDS | ||
}); | ||
} | ||
|
||
|
||
// Advertise the angle of rotation for a specific period | ||
function advertiseAngleOfRotation(angleOfRotation) { | ||
if(advertisingTimeoutId) { | ||
clearTimeout(advertisingTimeoutId); | ||
} | ||
|
||
NRF.setAdvertising({}, { | ||
showName: false, | ||
manufacturer: 0x0590, | ||
manufacturerData: JSON.stringify({ angleOfRotation: angleOfRotation }), | ||
interval: ANGLE_ADVERTISING_PERIOD_MILLISECONDS | ||
}); | ||
|
||
advertisingTimeoutId = setTimeout(advertiseName, | ||
ANGLE_ADVERTISING_DURATION_MILLISECONDS); | ||
} | ||
|
||
|
||
// 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 }); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.