Skip to content
Merged
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
2 changes: 2 additions & 0 deletions examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file
uint32_t ble_pin = 0;
uint8_t advert_loc_policy = 0;
uint8_t buzzer_quiet = 0;
uint8_t vibe_quiet = 0;
uint8_t gps_enabled = 0; // GPS enabled flag (0=disabled, 1=enabled)
uint32_t gps_interval = 0; // GPS read interval in seconds
uint8_t autoadd_config = 0; // bitmask for auto-add contacts config
Expand Down Expand Up @@ -101,6 +102,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file
def("defs_key", (void *) _parent->default_scope_key, sizeof(_parent->default_scope_key));
def("pin", _parent->ble_pin);
def("buzz_q", _parent->buzzer_quiet);
def("vibe_q", _parent->vibe_quiet);
def("auto_add", _parent->autoadd_config); // bitmask for auto-add contacts config
def("man_add", _parent->manual_add_contacts);
def("tel_base", _parent->telemetry_mode_base);
Expand Down
154 changes: 149 additions & 5 deletions examples/companion_radio/ui-orig/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
#define AUTO_OFF_MILLIS 15000 // 15 seconds
#define BOOT_SCREEN_MILLIS 3000 // 3 seconds

#ifdef PIN_STATUS_LED
#if defined(PIN_STATUS_LED) || defined(PIN_STATUS_LED_R)
#define LED_ON_MILLIS 20
#define LED_ON_MSG_MILLIS 200
#define LED_CYCLE_MILLIS 4000
#endif

#if defined(PIN_STATUS_LED_R) && defined(PIN_STATUS_LED_G) && defined(PIN_STATUS_LED_B)
#define STATUS_LED_RGB 1
#ifndef LOW_BATT_MILLIVOLTS
#define LOW_BATT_MILLIVOLTS 3500
#endif
#endif

#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif
Expand Down Expand Up @@ -60,6 +67,11 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
buzzer.startup();
#endif

#ifdef HAS_DRV2605
vibration.begin();
vibration.quiet(_node_prefs->vibe_quiet);
#endif

// Initialize digital button if available
#ifdef PIN_USER_BTN
_userButton = new Button(PIN_USER_BTN, USER_BTN_PRESSED);
Expand Down Expand Up @@ -130,6 +142,10 @@ void UITask::clearMsgPreview() {
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
_msgcount = msgcount;

#ifdef HAS_DRV2605
vibration.trigger(); // vibrate even while the app is connected (honors quiet + cooldown)
#endif

if (path_len == 0xFF) {
sprintf(_origin, "(F) %s", from_name);
} else {
Expand Down Expand Up @@ -260,8 +276,118 @@ void UITask::renderCurrScreen() {
_need_refresh = false;
}

#ifdef STATUS_LED_RGB
static void statusLedWrite(uint8_t r, uint8_t g, uint8_t b) {
analogWrite(PIN_STATUS_LED_R, r);
analogWrite(PIN_STATUS_LED_G, g);
analogWrite(PIN_STATUS_LED_B, b);
}

static void statusLedWheel(uint8_t pos) { // smooth hue sweep, 0..255
if (pos < 85) {
statusLedWrite(255 - pos * 3, pos * 3, 0);
} else if (pos < 170) {
pos -= 85;
statusLedWrite(0, 255 - pos * 3, pos * 3);
} else {
pos -= 170;
statusLedWrite(pos * 3, 0, 255 - pos * 3);
}
}
#endif

void UITask::userLedHandler() {
#ifdef PIN_STATUS_LED
#ifdef STATUS_LED_RGB
static bool booted = false;
static bool flourish_active = false;
static unsigned long flourish_until = 0;
static int prev_msgcount = 0;
static int state = 0;
static unsigned long next_change = 0;
static int last_increment = 0;
static unsigned long next_batt_check = 0;
static bool low_batt = false;

unsigned long cur_time = millis();

if (!booted) { // color sweep on boot
booted = true;
flourish_until = cur_time + 1200;
flourish_active = true;
}
if (_msgcount > prev_msgcount) { // color sweep when a new message arrives
flourish_until = cur_time + 800;
flourish_active = true;
}
prev_msgcount = _msgcount;

if (flourish_active) {
if (cur_time < flourish_until) {
statusLedWheel((cur_time % 1200) * 255 / 1200);
return;
}
statusLedWrite(0, 0, 0);
flourish_active = false;
state = 0;
next_change = cur_time;
}

if (cur_time > next_batt_check) { // battery reads are not free, keep them rare
low_batt = _board->getBattMilliVolts() < LOW_BATT_MILLIVOLTS;
next_batt_check = cur_time + 60000;
}

#ifdef EXT_CHRG_DETECT
// charge status display while docked (skipped when messages are waiting,
// so the unread indication is never masked)
static unsigned long next_pwr_check = 0;
static bool ext_powered = false, ext_charging = false;
if (cur_time > next_pwr_check) {
ext_powered = _board->isExternalPowered();
bool chrg = digitalRead(EXT_CHRG_DETECT) == LOW;
#ifdef EXT_CHRG_DONE
if (digitalRead(EXT_CHRG_DONE) == LOW) chrg = false; // charge-done wins
#endif
ext_charging = ext_powered && chrg;
next_pwr_check = cur_time + 1000;
}
if (ext_powered && _msgcount == 0) {
if (ext_charging) {
// amber breathing while charging
int ph = cur_time % 2000;
int v = ph < 1000 ? ph : 2000 - ph;
uint8_t lvl = (uint8_t)(v * 255 / 1000);
statusLedWrite(lvl, (uint8_t)(lvl * 35 / 100), 0);
} else {
statusLedWrite(0, 40, 0); // dim solid green: charge complete
}
state = 0;
next_change = cur_time;
return;
}
#endif

if (cur_time > next_change) {
if (state == 0) {
state = 1;
last_increment = (_msgcount > 0) ? LED_ON_MSG_MILLIS : LED_ON_MILLIS;
next_change = cur_time + last_increment;
if (low_batt) {
statusLedWrite(255, 0, 0); // red: battery low
} else if (_msgcount > 0) {
statusLedWrite(255, 90, 0); // amber: unread messages
} else if (_connected) {
statusLedWrite(0, 0, 255); // blue: app connected
} else {
statusLedWrite(0, 255, 0); // green: heartbeat
}
} else {
state = 0;
next_change = cur_time + LED_CYCLE_MILLIS - last_increment;
statusLedWrite(0, 0, 0);
}
}
#elif defined(PIN_STATUS_LED)
static int state = 0;
static int next_change = 0;
static int last_increment = 0;
Expand Down Expand Up @@ -406,8 +532,26 @@ void UITask::handleButtonDoublePress() {

void UITask::handleButtonTriplePress() {
MESH_DEBUG_PRINTLN("UITask: triple press triggered");
// Toggle buzzer quiet mode
#ifdef PIN_BUZZER
#if defined(PIN_BUZZER) && defined(HAS_DRV2605)
// cycle alert modes: Buzz+Vibe -> Buzz only -> Vibe only -> Silent
int mode = (_node_prefs->buzzer_quiet ? 2 : 0) | (_node_prefs->vibe_quiet ? 1 : 0);
mode = (mode + 1) & 3;
_node_prefs->buzzer_quiet = (mode & 2) ? 1 : 0;
_node_prefs->vibe_quiet = (mode & 1) ? 1 : 0;
buzzer.quiet(_node_prefs->buzzer_quiet);
vibration.quiet(_node_prefs->vibe_quiet);
// audible/tactile confirmation of the new mode (no screen on some boards)
if (!_node_prefs->buzzer_quiet) notify(UIEventType::ack);
if (!_node_prefs->vibe_quiet) vibration.trigger(true);
switch (mode) {
case 0: sprintf(_alert, "Alerts: Buzz+Vibe"); break;
case 1: sprintf(_alert, "Alerts: Buzz only"); break;
case 2: sprintf(_alert, "Alerts: Vibe only"); break;
default: sprintf(_alert, "Alerts: Silent"); break;
}
the_mesh.savePrefs();
_need_refresh = true;
#elif defined(PIN_BUZZER)
if (buzzer.isQuiet()) {
buzzer.quiet(false);
notify(UIEventType::ack);
Expand All @@ -419,7 +563,7 @@ void UITask::handleButtonTriplePress() {
_node_prefs->buzzer_quiet = buzzer.isQuiet();
the_mesh.savePrefs();
_need_refresh = true;
#endif
#endif
}

void UITask::handleButtonQuadruplePress() {
Expand Down
7 changes: 7 additions & 0 deletions examples/companion_radio/ui-orig/UITask.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@

#include "Button.h"

#ifdef HAS_DRV2605
#include <helpers/ui/DRV2605Vibration.h>
#endif

class UITask : public AbstractUITask {
DisplayDriver* _display;
SensorManager* _sensors;
#ifdef PIN_BUZZER
genericBuzzer buzzer;
#endif
#ifdef HAS_DRV2605
DRV2605Vibration vibration;
#endif
unsigned long _next_refresh, _auto_off;
NodePrefs* _node_prefs;
Expand Down
47 changes: 47 additions & 0 deletions src/helpers/ui/DRV2605Vibration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifdef HAS_DRV2605

#include "DRV2605Vibration.h"
#include <Wire.h>

void DRV2605Vibration::begin() {
#ifdef PIN_DRV_EN
pinMode(PIN_DRV_EN, OUTPUT);
digitalWrite(PIN_DRV_EN, HIGH); // power up the haptic driver
delay(10);
#endif
if (!drv.begin(&Wire)) {
return; // no haptic driver found, stay silent
}
#ifdef DRV2605_USE_LRA
// LRA mode: 4x brake factor, medium loop gain, back-EMF gain 2
drv.writeRegister8(DRV2605_REG_FEEDBACK, 0xB6);
#endif
drv.selectLibrary(1);
drv.setMode(DRV2605_MODE_INTTRIG);
_ready = true;
}

void DRV2605Vibration::trigger(bool force) {
if (!_ready || _quiet) return;
unsigned long now = millis();
if (!force && _last_trigger != 0 && now - _last_trigger < VIBRATION_TIMEOUT) return;
_last_trigger = now;
drv.setWaveform(0, DRV2605_EFFECT);
drv.setWaveform(1, 0); // pause
drv.setWaveform(2, DRV2605_EFFECT);
drv.setWaveform(3, 0); // end of sequence
drv.go();
}

void DRV2605Vibration::loop() {
}

bool DRV2605Vibration::isVibrating() {
return false; // effects are short, treat as instantaneous
}

void DRV2605Vibration::stop() {
if (_ready) drv.stop();
}

#endif // ifdef HAS_DRV2605
39 changes: 39 additions & 0 deletions src/helpers/ui/DRV2605Vibration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#ifdef HAS_DRV2605

#include <Arduino.h>
#include <Adafruit_DRV2605.h>

/*
* Vibration control class for boards where the motor is behind a
* DRV2605 haptic driver on I2C (e.g. Seeed SenseCAP MeshTracker X1).
* Same interface as GenericVibration.
*/

#ifndef VIBRATION_TIMEOUT
#define VIBRATION_TIMEOUT 5000 // cooldown between vibrations
#endif

#ifndef DRV2605_EFFECT
#define DRV2605_EFFECT 16 // "1000 ms alert" from the DRV2605 effect library
#endif

class DRV2605Vibration {
public:
void begin(); // power up and init the DRV2605
void trigger(bool force = false); // trigger vibration; force skips the cooldown
void loop(); // no-op, the DRV2605 plays effects autonomously
bool isVibrating();
void stop(); // stop vibration immediately
void quiet(bool q) { _quiet = q; }
bool isQuiet() const { return _quiet; }

private:
Adafruit_DRV2605 drv;
bool _ready = false;
bool _quiet = false;
unsigned long _last_trigger = 0;
};

#endif // ifdef HAS_DRV2605
13 changes: 13 additions & 0 deletions variants/meshtracker_x1/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ build_flags = ${nrf52_base.build_flags}
-D PIN_USER_BTN=6
-D USER_BTN_PRESSED=HIGH
-D PIN_STATUS_LED=24
-D PIN_STATUS_LED_R=3 ; P0.3 red
-D PIN_STATUS_LED_G=24 ; P0.24 green
-D PIN_STATUS_LED_B=28 ; P0.28 blue
-D RADIO_CLASS=CustomLR2021
-D USE_LR2021
-D WRAPPER_CLASS=CustomLR2021Wrapper
Expand All @@ -29,6 +32,8 @@ build_flags = ${nrf52_base.build_flags}
build_src_filter = ${nrf52_base.build_src_filter}
+<helpers/*.cpp>
+<../variants/meshtracker_x1>
lib_deps = ${nrf52_base.lib_deps}
https://github.com/adafruit/Adafruit_SPA06_003/archive/refs/tags/1.0.2.zip
debug_tool = jlink
upload_protocol = nrfutil

Expand Down Expand Up @@ -77,16 +82,20 @@ build_flags = ${MeshTracker_X1.build_flags}
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=NullDisplayDriver
-D PIN_BUZZER=25
-D HAS_DRV2605=1
-D DRV2605_USE_LRA=1
-D ENABLE_USB_INTERFACE
build_src_filter = ${MeshTracker_X1.build_src_filter}
+<helpers/ui/buzzer.cpp>
+<helpers/ui/NullDisplayDriver.cpp>
+<helpers/ui/DRV2605Vibration.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-orig/*.cpp>
lib_deps = ${MeshTracker_X1.lib_deps}
densaugeo/base64 @ ~1.4.0
stevemarple/MicroNMEA @ ^2.0.6
end2endzone/NonBlockingRTTTL@^1.3.0
adafruit/Adafruit DRV2605 Library @ ^1.2.4

[env:MeshTracker_X1_companion_radio_ble]
extends = MeshTracker_X1
Expand All @@ -104,14 +113,18 @@ build_flags = ${MeshTracker_X1.build_flags}
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=NullDisplayDriver
-D PIN_BUZZER=25
-D HAS_DRV2605=1
-D DRV2605_USE_LRA=1
-D ADVERT_NAME='"@@MAC"'
build_src_filter = ${MeshTracker_X1.build_src_filter}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<helpers/ui/buzzer.cpp>
+<helpers/ui/NullDisplayDriver.cpp>
+<helpers/ui/DRV2605Vibration.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-orig/*.cpp>
lib_deps = ${MeshTracker_X1.lib_deps}
densaugeo/base64 @ ~1.4.0
stevemarple/MicroNMEA @ ^2.0.6
end2endzone/NonBlockingRTTTL@^1.3.0
adafruit/Adafruit DRV2605 Library @ ^1.2.4
Loading
Loading