diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 1277bba9..0eee45ae 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -41,6 +41,6 @@ public: void disableSerial() { _serial->disable(); } 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 soundBuzzer(UIEventType bet = UIEventType::none) = 0; + virtual void notify(UIEventType t = UIEventType::none) = 0; virtual void loop() = 0; }; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 04d5577e..a2c2f8f8 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -243,7 +243,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } } else { #ifdef DISPLAY_CLASS - if (_ui) _ui->soundBuzzer(UIEventType::newContactMessage); + if (_ui) _ui->notify(UIEventType::newContactMessage); #endif } @@ -353,7 +353,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe if (should_display && _ui) { _ui->newMsg(path_len, from.name, text, offline_queue_len); if (!_serial->isConnected()) { - _ui->soundBuzzer(UIEventType::contactMessage); + _ui->notify(UIEventType::contactMessage); } } #endif @@ -412,7 +412,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe _serial->writeFrame(frame, 1); } else { #ifdef DISPLAY_CLASS - if (_ui) _ui->soundBuzzer(UIEventType::channelMessage); + if (_ui) _ui->notify(UIEventType::channelMessage); #endif } #ifdef DISPLAY_CLASS diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7f167f9a..248b9bd5 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -356,9 +356,7 @@ public: return true; } if (c == KEY_ENTER && _page == HomePage::ADVERT) { - #ifdef PIN_BUZZER - _task->soundBuzzer(UIEventType::ack); - #endif + _task->notify(UIEventType::ack); if (the_mesh.advert()) { _task->showAlert("Advert sent!", 1000); } else { @@ -495,6 +493,10 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no buzzer.begin(); #endif +#ifdef PIN_VIBRATION + vibration.begin(); +#endif + ui_started_at = millis(); _alert_expiry = 0; @@ -509,9 +511,9 @@ void UITask::showAlert(const char* text, int duration_millis) { _alert_expiry = millis() + duration_millis; } -void UITask::soundBuzzer(UIEventType bet) { +void UITask::notify(UIEventType t) { #if defined(PIN_BUZZER) -switch(bet){ +switch(t){ case UIEventType::contactMessage: // gemini's pick buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); @@ -529,8 +531,16 @@ switch(bet){ break; } #endif + +#ifdef PIN_VIBRATION + // Trigger vibration for all UI events except none + if (t != UIEventType::none) { + vibration.trigger(); + } +#endif } + void UITask::msgRead(int msgcount) { _msgcount = msgcount; if (msgcount == 0) { @@ -699,6 +709,10 @@ void UITask::loop() { #endif } +#ifdef PIN_VIBRATION + vibration.loop(); +#endif + #ifdef AUTO_SHUTDOWN_MILLIVOLTS if (millis() > next_batt_chck) { uint16_t milliVolts = getBattMilliVolts(); @@ -767,11 +781,11 @@ void UITask::toggleGPS() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _sensors->setSettingValue("gps", "0"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); showAlert("GPS: Disabled", 800); } else { _sensors->setSettingValue("gps", "1"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); showAlert("GPS: Enabled", 800); } _next_refresh = 0; @@ -786,7 +800,7 @@ void UITask::toggleBuzzer() { #ifdef PIN_BUZZER if (buzzer.isQuiet()) { buzzer.quiet(false); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); showAlert("Buzzer: ON", 800); } else { buzzer.quiet(true); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 769b2c64..bc12c679 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -11,6 +11,9 @@ #ifdef PIN_BUZZER #include #endif +#ifdef PIN_VIBRATION + #include +#endif #include "../AbstractUITask.h" #include "../NodePrefs.h" @@ -20,6 +23,9 @@ class UITask : public AbstractUITask { SensorManager* _sensors; #ifdef PIN_BUZZER genericBuzzer buzzer; +#endif +#ifdef PIN_VIBRATION + genericVibration vibration; #endif unsigned long _next_refresh, _auto_off; NodePrefs* _node_prefs; @@ -71,7 +77,7 @@ public: // from AbstractUITask void msgRead(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 shutdown(bool restart = false); diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 29d995a7..045c955d 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -88,9 +88,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no ui_started_at = millis(); } -void UITask::soundBuzzer(UIEventType bet) { +void UITask::notify(UIEventType t) { #if defined(PIN_BUZZER) -switch(bet){ +switch(t){ case UIEventType::contactMessage: // gemini's pick buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); @@ -108,8 +108,8 @@ switch(bet){ break; } #endif -// Serial.print("DBG: Buzzzzzz -> "); -// Serial.println((int) bet); +// Serial.print("DBG: Alert user -> "); +// Serial.println((int) t); } void UITask::msgRead(int msgcount) { @@ -370,7 +370,7 @@ void UITask::handleButtonDoublePress() { MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert"); // ADVERT #ifdef PIN_BUZZER - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); #endif if (the_mesh.advert()) { MESH_DEBUG_PRINTLN("Advert sent!"); @@ -388,7 +388,7 @@ void UITask::handleButtonTriplePress() { #ifdef PIN_BUZZER if (buzzer.isQuiet()) { buzzer.quiet(false); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); sprintf(_alert, "Buzzer: ON"); } else { buzzer.quiet(true); @@ -407,11 +407,11 @@ void UITask::handleButtonQuadruplePress() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _sensors->setSettingValue("gps", "0"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); sprintf(_alert, "GPS: Disabled"); } else { _sensors->setSettingValue("gps", "1"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); sprintf(_alert, "GPS: Enabled"); } break; diff --git a/examples/companion_radio/ui-orig/UITask.h b/examples/companion_radio/ui-orig/UITask.h index a59ddc41..60cd0d04 100644 --- a/examples/companion_radio/ui-orig/UITask.h +++ b/examples/companion_radio/ui-orig/UITask.h @@ -66,7 +66,7 @@ public: // from AbstractUITask void msgRead(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 shutdown(bool restart = false); diff --git a/src/helpers/ui/vibration.cpp b/src/helpers/ui/vibration.cpp new file mode 100644 index 00000000..37db54ed --- /dev/null +++ b/src/helpers/ui/vibration.cpp @@ -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 diff --git a/src/helpers/ui/vibration.h b/src/helpers/ui/vibration.h new file mode 100644 index 00000000..42cd0f67 --- /dev/null +++ b/src/helpers/ui/vibration.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef PIN_VIBRATION + +#include + +/* + * 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