Răsfoiți Sursa

Merge pull request #708 from csrutil/feature/vibration-feedback

✨ feat: add vibration feedback system
ripplebiz 10 luni în urmă
părinte
comite
b3e9fd76ce

+ 1 - 1
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;
 };

+ 3 - 3
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

+ 22 - 8
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);

+ 7 - 1
examples/companion_radio/ui-new/UITask.h

@@ -11,6 +11,9 @@
 #ifdef PIN_BUZZER
   #include <helpers/ui/buzzer.h>
 #endif
+#ifdef PIN_VIBRATION
+  #include <helpers/ui/vibration.h>
+#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);

+ 8 - 8
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;

+ 1 - 1
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);

+ 43 - 0
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

+ 34 - 0
src/helpers/ui/vibration.h

@@ -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