Merge pull request #708 from csrutil/feature/vibration-feedback
✨ feat: add vibration feedback system
This commit is contained in:
@@ -41,6 +41,6 @@ public:
|
|||||||
void disableSerial() { _serial->disable(); }
|
void disableSerial() { _serial->disable(); }
|
||||||
virtual void msgRead(int msgcount) = 0;
|
virtual void msgRead(int msgcount) = 0;
|
||||||
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0;
|
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0;
|
||||||
virtual void soundBuzzer(UIEventType bet = UIEventType::none) = 0;
|
virtual void notify(UIEventType t = UIEventType::none) = 0;
|
||||||
virtual void loop() = 0;
|
virtual void loop() = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->soundBuzzer(UIEventType::newContactMessage);
|
if (_ui) _ui->notify(UIEventType::newContactMessage);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +353,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
|||||||
if (should_display && _ui) {
|
if (should_display && _ui) {
|
||||||
_ui->newMsg(path_len, from.name, text, offline_queue_len);
|
_ui->newMsg(path_len, from.name, text, offline_queue_len);
|
||||||
if (!_serial->isConnected()) {
|
if (!_serial->isConnected()) {
|
||||||
_ui->soundBuzzer(UIEventType::contactMessage);
|
_ui->notify(UIEventType::contactMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -412,7 +412,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
|||||||
_serial->writeFrame(frame, 1);
|
_serial->writeFrame(frame, 1);
|
||||||
} else {
|
} else {
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->soundBuzzer(UIEventType::channelMessage);
|
if (_ui) _ui->notify(UIEventType::channelMessage);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
|
|||||||
@@ -356,9 +356,7 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
||||||
#ifdef PIN_BUZZER
|
_task->notify(UIEventType::ack);
|
||||||
_task->soundBuzzer(UIEventType::ack);
|
|
||||||
#endif
|
|
||||||
if (the_mesh.advert()) {
|
if (the_mesh.advert()) {
|
||||||
_task->showAlert("Advert sent!", 1000);
|
_task->showAlert("Advert sent!", 1000);
|
||||||
} else {
|
} else {
|
||||||
@@ -495,6 +493,10 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
|||||||
buzzer.begin();
|
buzzer.begin();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
vibration.begin();
|
||||||
|
#endif
|
||||||
|
|
||||||
ui_started_at = millis();
|
ui_started_at = millis();
|
||||||
_alert_expiry = 0;
|
_alert_expiry = 0;
|
||||||
|
|
||||||
@@ -509,9 +511,9 @@ void UITask::showAlert(const char* text, int duration_millis) {
|
|||||||
_alert_expiry = millis() + duration_millis;
|
_alert_expiry = millis() + duration_millis;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UITask::soundBuzzer(UIEventType bet) {
|
void UITask::notify(UIEventType t) {
|
||||||
#if defined(PIN_BUZZER)
|
#if defined(PIN_BUZZER)
|
||||||
switch(bet){
|
switch(t){
|
||||||
case UIEventType::contactMessage:
|
case UIEventType::contactMessage:
|
||||||
// gemini's pick
|
// gemini's pick
|
||||||
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
||||||
@@ -529,8 +531,16 @@ switch(bet){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
// Trigger vibration for all UI events except none
|
||||||
|
if (t != UIEventType::none) {
|
||||||
|
vibration.trigger();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UITask::msgRead(int msgcount) {
|
void UITask::msgRead(int msgcount) {
|
||||||
_msgcount = msgcount;
|
_msgcount = msgcount;
|
||||||
if (msgcount == 0) {
|
if (msgcount == 0) {
|
||||||
@@ -699,6 +709,10 @@ void UITask::loop() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
vibration.loop();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef AUTO_SHUTDOWN_MILLIVOLTS
|
#ifdef AUTO_SHUTDOWN_MILLIVOLTS
|
||||||
if (millis() > next_batt_chck) {
|
if (millis() > next_batt_chck) {
|
||||||
uint16_t milliVolts = getBattMilliVolts();
|
uint16_t milliVolts = getBattMilliVolts();
|
||||||
@@ -767,11 +781,11 @@ void UITask::toggleGPS() {
|
|||||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
||||||
_sensors->setSettingValue("gps", "0");
|
_sensors->setSettingValue("gps", "0");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
showAlert("GPS: Disabled", 800);
|
showAlert("GPS: Disabled", 800);
|
||||||
} else {
|
} else {
|
||||||
_sensors->setSettingValue("gps", "1");
|
_sensors->setSettingValue("gps", "1");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
showAlert("GPS: Enabled", 800);
|
showAlert("GPS: Enabled", 800);
|
||||||
}
|
}
|
||||||
_next_refresh = 0;
|
_next_refresh = 0;
|
||||||
@@ -786,7 +800,7 @@ void UITask::toggleBuzzer() {
|
|||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
if (buzzer.isQuiet()) {
|
if (buzzer.isQuiet()) {
|
||||||
buzzer.quiet(false);
|
buzzer.quiet(false);
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
showAlert("Buzzer: ON", 800);
|
showAlert("Buzzer: ON", 800);
|
||||||
} else {
|
} else {
|
||||||
buzzer.quiet(true);
|
buzzer.quiet(true);
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
#include <helpers/ui/buzzer.h>
|
#include <helpers/ui/buzzer.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
#include <helpers/ui/vibration.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "../AbstractUITask.h"
|
#include "../AbstractUITask.h"
|
||||||
#include "../NodePrefs.h"
|
#include "../NodePrefs.h"
|
||||||
@@ -20,6 +23,9 @@ class UITask : public AbstractUITask {
|
|||||||
SensorManager* _sensors;
|
SensorManager* _sensors;
|
||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
genericBuzzer buzzer;
|
genericBuzzer buzzer;
|
||||||
|
#endif
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
genericVibration vibration;
|
||||||
#endif
|
#endif
|
||||||
unsigned long _next_refresh, _auto_off;
|
unsigned long _next_refresh, _auto_off;
|
||||||
NodePrefs* _node_prefs;
|
NodePrefs* _node_prefs;
|
||||||
@@ -71,7 +77,7 @@ public:
|
|||||||
// from AbstractUITask
|
// from AbstractUITask
|
||||||
void msgRead(int msgcount) override;
|
void msgRead(int msgcount) override;
|
||||||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
||||||
void soundBuzzer(UIEventType bet = UIEventType::none) override;
|
void notify(UIEventType t = UIEventType::none) override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void shutdown(bool restart = false);
|
void shutdown(bool restart = false);
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
|||||||
ui_started_at = millis();
|
ui_started_at = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UITask::soundBuzzer(UIEventType bet) {
|
void UITask::notify(UIEventType t) {
|
||||||
#if defined(PIN_BUZZER)
|
#if defined(PIN_BUZZER)
|
||||||
switch(bet){
|
switch(t){
|
||||||
case UIEventType::contactMessage:
|
case UIEventType::contactMessage:
|
||||||
// gemini's pick
|
// gemini's pick
|
||||||
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
||||||
@@ -108,8 +108,8 @@ switch(bet){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Serial.print("DBG: Buzzzzzz -> ");
|
// Serial.print("DBG: Alert user -> ");
|
||||||
// Serial.println((int) bet);
|
// Serial.println((int) t);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UITask::msgRead(int msgcount) {
|
void UITask::msgRead(int msgcount) {
|
||||||
@@ -370,7 +370,7 @@ void UITask::handleButtonDoublePress() {
|
|||||||
MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert");
|
MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert");
|
||||||
// ADVERT
|
// ADVERT
|
||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
#endif
|
#endif
|
||||||
if (the_mesh.advert()) {
|
if (the_mesh.advert()) {
|
||||||
MESH_DEBUG_PRINTLN("Advert sent!");
|
MESH_DEBUG_PRINTLN("Advert sent!");
|
||||||
@@ -388,7 +388,7 @@ void UITask::handleButtonTriplePress() {
|
|||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
if (buzzer.isQuiet()) {
|
if (buzzer.isQuiet()) {
|
||||||
buzzer.quiet(false);
|
buzzer.quiet(false);
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
sprintf(_alert, "Buzzer: ON");
|
sprintf(_alert, "Buzzer: ON");
|
||||||
} else {
|
} else {
|
||||||
buzzer.quiet(true);
|
buzzer.quiet(true);
|
||||||
@@ -407,11 +407,11 @@ void UITask::handleButtonQuadruplePress() {
|
|||||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
||||||
_sensors->setSettingValue("gps", "0");
|
_sensors->setSettingValue("gps", "0");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
sprintf(_alert, "GPS: Disabled");
|
sprintf(_alert, "GPS: Disabled");
|
||||||
} else {
|
} else {
|
||||||
_sensors->setSettingValue("gps", "1");
|
_sensors->setSettingValue("gps", "1");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
sprintf(_alert, "GPS: Enabled");
|
sprintf(_alert, "GPS: Enabled");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public:
|
|||||||
// from AbstractUITask
|
// from AbstractUITask
|
||||||
void msgRead(int msgcount) override;
|
void msgRead(int msgcount) override;
|
||||||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
||||||
void soundBuzzer(UIEventType bet = UIEventType::none) override;
|
void notify(UIEventType t = UIEventType::none) override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void shutdown(bool restart = false);
|
void shutdown(bool restart = false);
|
||||||
|
|||||||
43
src/helpers/ui/vibration.cpp
Normal file
43
src/helpers/ui/vibration.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
#include "vibration.h"
|
||||||
|
|
||||||
|
void genericVibration::begin()
|
||||||
|
{
|
||||||
|
pinMode(PIN_VIBRATION, OUTPUT);
|
||||||
|
digitalWrite(PIN_VIBRATION, LOW);
|
||||||
|
duration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void genericVibration::trigger()
|
||||||
|
{
|
||||||
|
duration = millis();
|
||||||
|
digitalWrite(PIN_VIBRATION, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void genericVibration::loop()
|
||||||
|
{
|
||||||
|
if (isVibrating()) {
|
||||||
|
if ((millis() / 1000) % 2 == 0) {
|
||||||
|
digitalWrite(PIN_VIBRATION, LOW);
|
||||||
|
} else {
|
||||||
|
digitalWrite(PIN_VIBRATION, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() - duration > VIBRATION_TIMEOUT) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool genericVibration::isVibrating()
|
||||||
|
{
|
||||||
|
return duration > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void genericVibration::stop()
|
||||||
|
{
|
||||||
|
duration = 0;
|
||||||
|
digitalWrite(PIN_VIBRATION, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ifdef PIN_VIBRATION
|
||||||
34
src/helpers/ui/vibration.h
Normal file
34
src/helpers/ui/vibration.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Vibration motor control class
|
||||||
|
*
|
||||||
|
* Provides vibration feedback for events like new messages and new contacts
|
||||||
|
* Features:
|
||||||
|
* - 1-second vibration pulse
|
||||||
|
* - 5-second nag timeout (cooldown between vibrations)
|
||||||
|
* - Non-blocking operation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VIBRATION_TIMEOUT
|
||||||
|
#define VIBRATION_TIMEOUT 5000 // 5 seconds default
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class genericVibration
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void begin(); // set up vibration pin
|
||||||
|
void trigger(); // trigger vibration if cooldown has passed
|
||||||
|
void loop(); // non-blocking timer handling
|
||||||
|
bool isVibrating(); // returns true if currently vibrating
|
||||||
|
void stop(); // stop vibration immediately
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ifdef PIN_VIBRATION
|
||||||
Reference in New Issue
Block a user