mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-04 20:54:42 +00:00
Merge branch 'master' of https://github.com/mc-hamster/Meshtastic-device
This commit is contained in:
commit
38efb8b3ad
@ -820,6 +820,9 @@ void Screen::setup()
|
|||||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||||
if (textMessagePlugin)
|
if (textMessagePlugin)
|
||||||
textMessageObserver.observe(textMessagePlugin);
|
textMessageObserver.observe(textMessagePlugin);
|
||||||
|
|
||||||
|
// Plugins can notify screen about refresh
|
||||||
|
MeshPlugin::observeUIEvents(&uiFrameEventObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::forceDisplay()
|
void Screen::forceDisplay()
|
||||||
@ -1457,4 +1460,23 @@ int Screen::handleTextMessage(const MeshPacket *packet)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
||||||
|
{
|
||||||
|
if (showingNormalScreen) {
|
||||||
|
if (event->frameChanged)
|
||||||
|
{
|
||||||
|
setFrames(); // Regen the list of screens (will show new text message)
|
||||||
|
}
|
||||||
|
else if (event->needRedraw)
|
||||||
|
{
|
||||||
|
setFastFramerate();
|
||||||
|
// TODO: We might also want switch to corresponding frame,
|
||||||
|
// but we don't know the exact frame number.
|
||||||
|
//ui.switchToFrame(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
|
@ -40,6 +40,7 @@ class Screen
|
|||||||
#include "concurrency/OSThread.h"
|
#include "concurrency/OSThread.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include "mesh/MeshPlugin.h"
|
||||||
|
|
||||||
// 0 to 255, though particular variants might define different defaults
|
// 0 to 255, though particular variants might define different defaults
|
||||||
#ifndef BRIGHTNESS_DEFAULT
|
#ifndef BRIGHTNESS_DEFAULT
|
||||||
@ -90,6 +91,8 @@ class Screen : public concurrency::OSThread
|
|||||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||||
CallbackObserver<Screen, const MeshPacket *> textMessageObserver =
|
CallbackObserver<Screen, const MeshPacket *> textMessageObserver =
|
||||||
CallbackObserver<Screen, const MeshPacket *>(this, &Screen::handleTextMessage);
|
CallbackObserver<Screen, const MeshPacket *>(this, &Screen::handleTextMessage);
|
||||||
|
CallbackObserver<Screen, const UIFrameEvent *> uiFrameEventObserver =
|
||||||
|
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Screen(uint8_t address, int sda = -1, int scl = -1);
|
Screen(uint8_t address, int sda = -1, int scl = -1);
|
||||||
@ -218,6 +221,7 @@ class Screen : public concurrency::OSThread
|
|||||||
|
|
||||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||||
int handleTextMessage(const MeshPacket *arg);
|
int handleTextMessage(const MeshPacket *arg);
|
||||||
|
int handleUIFrameEvent(const UIFrameEvent *arg);
|
||||||
|
|
||||||
/// Used to force (super slow) eink displays to draw critical frames
|
/// Used to force (super slow) eink displays to draw critical frames
|
||||||
void forceDisplay();
|
void forceDisplay();
|
||||||
|
18
src/input/InputBroker.cpp
Normal file
18
src/input/InputBroker.cpp
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#include "InputBroker.h"
|
||||||
|
|
||||||
|
InputBroker *inputBroker;
|
||||||
|
|
||||||
|
InputBroker::InputBroker()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
void InputBroker::registerSource(Observable<const InputEvent *> *source)
|
||||||
|
{
|
||||||
|
this->inputEventObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
int InputBroker::handleInputEvent(const InputEvent *event)
|
||||||
|
{
|
||||||
|
this->notifyObservers(event);
|
||||||
|
return 0;
|
||||||
|
}
|
22
src/input/InputBroker.h
Normal file
22
src/input/InputBroker.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Observer.h"
|
||||||
|
|
||||||
|
typedef struct _InputEvent {
|
||||||
|
const char* source;
|
||||||
|
char inputEvent;
|
||||||
|
} InputEvent;
|
||||||
|
class InputBroker :
|
||||||
|
public Observable<const InputEvent *>
|
||||||
|
{
|
||||||
|
CallbackObserver<InputBroker, const InputEvent *> inputEventObserver =
|
||||||
|
CallbackObserver<InputBroker, const InputEvent *>(this, &InputBroker::handleInputEvent);
|
||||||
|
|
||||||
|
public:
|
||||||
|
InputBroker();
|
||||||
|
void registerSource(Observable<const InputEvent *> *source);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int handleInputEvent(const InputEvent *event);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern InputBroker *inputBroker;
|
148
src/input/RotaryEncoderInterruptBase.cpp
Normal file
148
src/input/RotaryEncoderInterruptBase.cpp
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#include "configuration.h"
|
||||||
|
#include "RotaryEncoderInterruptBase.h"
|
||||||
|
|
||||||
|
RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(
|
||||||
|
const char *name) :
|
||||||
|
concurrency::OSThread(name)
|
||||||
|
{
|
||||||
|
this->_originName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotaryEncoderInterruptBase::init(
|
||||||
|
uint8_t pinA, uint8_t pinB, uint8_t pinPress,
|
||||||
|
char eventCw, char eventCcw, char eventPressed,
|
||||||
|
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) :
|
||||||
|
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)())
|
||||||
|
{
|
||||||
|
this->_pinA = pinA;
|
||||||
|
this->_pinB = pinB;
|
||||||
|
this->_eventCw = eventCw;
|
||||||
|
this->_eventCcw = eventCcw;
|
||||||
|
this->_eventPressed = eventPressed;
|
||||||
|
|
||||||
|
pinMode(pinPress, INPUT_PULLUP);
|
||||||
|
pinMode(this->_pinA, INPUT_PULLUP);
|
||||||
|
pinMode(this->_pinB, INPUT_PULLUP);
|
||||||
|
|
||||||
|
// attachInterrupt(pinPress, onIntPress, RISING);
|
||||||
|
attachInterrupt(pinPress, onIntPress, RISING);
|
||||||
|
attachInterrupt(this->_pinA, onIntA, CHANGE);
|
||||||
|
attachInterrupt(this->_pinB, onIntB, CHANGE);
|
||||||
|
|
||||||
|
this->rotaryLevelA = digitalRead(this->_pinA);
|
||||||
|
this->rotaryLevelB = digitalRead(this->_pinB);
|
||||||
|
DEBUG_MSG("Rotary initialized (%d, %d, %d)\n",
|
||||||
|
this->_pinA, this->_pinB, pinPress);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t RotaryEncoderInterruptBase::runOnce()
|
||||||
|
{
|
||||||
|
InputEvent e;
|
||||||
|
e.inputEvent = InputEventChar_NULL;
|
||||||
|
e.source = this->_originName;
|
||||||
|
|
||||||
|
if (this->action == ROTARY_ACTION_PRESSED)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Rotary event Press\n");
|
||||||
|
e.inputEvent = this->_eventPressed;
|
||||||
|
}
|
||||||
|
else if (this->action == ROTARY_ACTION_CW)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Rotary event CW\n");
|
||||||
|
e.inputEvent = this->_eventCw;
|
||||||
|
}
|
||||||
|
else if (this->action == ROTARY_ACTION_CCW)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Rotary event CW\n");
|
||||||
|
e.inputEvent = this->_eventCcw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.inputEvent != InputEventChar_NULL)
|
||||||
|
{
|
||||||
|
this->notifyObservers(&e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->action = ROTARY_ACTION_NONE;
|
||||||
|
|
||||||
|
return 30000; // TODO: technically this can be MAX_INT
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void RotaryEncoderInterruptBase::intPressHandler()
|
||||||
|
{
|
||||||
|
this->action = ROTARY_ACTION_PRESSED;
|
||||||
|
setIntervalFromNow(20); // TODO: this modifies a non-volatile variable!
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotaryEncoderInterruptBase::intAHandler()
|
||||||
|
{
|
||||||
|
// CW rotation (at least on most common rotary encoders)
|
||||||
|
int currentLevelA = digitalRead(this->_pinA);
|
||||||
|
if (this->rotaryLevelA == currentLevelA)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->rotaryLevelA = currentLevelA;
|
||||||
|
intHandler(
|
||||||
|
currentLevelA == HIGH,
|
||||||
|
this->rotaryLevelB,
|
||||||
|
ROTARY_ACTION_CCW,
|
||||||
|
this->rotaryStateCCW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotaryEncoderInterruptBase::intBHandler()
|
||||||
|
{
|
||||||
|
// CW rotation (at least on most common rotary encoders)
|
||||||
|
int currentLevelB = digitalRead(this->_pinB);
|
||||||
|
if (this->rotaryLevelB == currentLevelB)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->rotaryLevelB = currentLevelB;
|
||||||
|
this->rotaryStateCW = intHandler(
|
||||||
|
currentLevelB == HIGH,
|
||||||
|
this->rotaryLevelA,
|
||||||
|
ROTARY_ACTION_CW,
|
||||||
|
this->rotaryStateCW);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rotary action implementation.
|
||||||
|
* We assume, the following pin setup:
|
||||||
|
* A --||
|
||||||
|
* GND --||]========
|
||||||
|
* B --||
|
||||||
|
*
|
||||||
|
* @return The new state for rotary pin.
|
||||||
|
*/
|
||||||
|
RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(
|
||||||
|
bool actualPinRaising,
|
||||||
|
int otherPinLevel,
|
||||||
|
RotaryEncoderInterruptBaseActionType action,
|
||||||
|
RotaryEncoderInterruptBaseStateType state)
|
||||||
|
{
|
||||||
|
RotaryEncoderInterruptBaseStateType newState =
|
||||||
|
state;
|
||||||
|
if (actualPinRaising && (otherPinLevel == LOW))
|
||||||
|
{
|
||||||
|
if (state == ROTARY_EVENT_CLEARED)
|
||||||
|
{
|
||||||
|
newState = ROTARY_EVENT_OCCURRED;
|
||||||
|
if ((this->action != ROTARY_ACTION_PRESSED)
|
||||||
|
&& (this->action != action))
|
||||||
|
{
|
||||||
|
this->action = action;
|
||||||
|
DEBUG_MSG("Rotary action\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!actualPinRaising && (otherPinLevel == HIGH))
|
||||||
|
{
|
||||||
|
// Logic to prevent bouncing.
|
||||||
|
newState = ROTARY_EVENT_CLEARED;
|
||||||
|
}
|
||||||
|
setIntervalFromNow(50); // TODO: this modifies a non-volatile variable!
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
}
|
57
src/input/RotaryEncoderInterruptBase.h
Normal file
57
src/input/RotaryEncoderInterruptBase.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SinglePortPlugin.h" // TODO: what header file to include?
|
||||||
|
#include "InputBroker.h"
|
||||||
|
|
||||||
|
enum RotaryEncoderInterruptBaseStateType
|
||||||
|
{
|
||||||
|
ROTARY_EVENT_OCCURRED,
|
||||||
|
ROTARY_EVENT_CLEARED
|
||||||
|
};
|
||||||
|
|
||||||
|
enum RotaryEncoderInterruptBaseActionType
|
||||||
|
{
|
||||||
|
ROTARY_ACTION_NONE,
|
||||||
|
ROTARY_ACTION_PRESSED,
|
||||||
|
ROTARY_ACTION_CW,
|
||||||
|
ROTARY_ACTION_CCW
|
||||||
|
};
|
||||||
|
|
||||||
|
class RotaryEncoderInterruptBase :
|
||||||
|
public Observable<const InputEvent *>,
|
||||||
|
private concurrency::OSThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RotaryEncoderInterruptBase(
|
||||||
|
const char *name);
|
||||||
|
void init(
|
||||||
|
uint8_t pinA, uint8_t pinB, uint8_t pinPress,
|
||||||
|
char eventCw, char eventCcw, char eventPressed,
|
||||||
|
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress);
|
||||||
|
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
|
||||||
|
void intPressHandler();
|
||||||
|
void intAHandler();
|
||||||
|
void intBHandler();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual int32_t runOnce();
|
||||||
|
RotaryEncoderInterruptBaseStateType intHandler(
|
||||||
|
bool actualPinRaising,
|
||||||
|
int otherPinLevel,
|
||||||
|
RotaryEncoderInterruptBaseActionType action,
|
||||||
|
RotaryEncoderInterruptBaseStateType state);
|
||||||
|
|
||||||
|
volatile RotaryEncoderInterruptBaseStateType rotaryStateCW = ROTARY_EVENT_CLEARED;
|
||||||
|
volatile RotaryEncoderInterruptBaseStateType rotaryStateCCW = ROTARY_EVENT_CLEARED;
|
||||||
|
volatile int rotaryLevelA = LOW;
|
||||||
|
volatile int rotaryLevelB = LOW;
|
||||||
|
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t _pinA;
|
||||||
|
uint8_t _pinB;
|
||||||
|
char _eventCw;
|
||||||
|
char _eventCcw;
|
||||||
|
char _eventPressed;
|
||||||
|
const char *_originName;
|
||||||
|
};
|
51
src/input/RotaryEncoderInterruptImpl1.cpp
Normal file
51
src/input/RotaryEncoderInterruptImpl1.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "RotaryEncoderInterruptImpl1.h"
|
||||||
|
#include "InputBroker.h"
|
||||||
|
|
||||||
|
RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
|
||||||
|
|
||||||
|
RotaryEncoderInterruptImpl1::RotaryEncoderInterruptImpl1() :
|
||||||
|
RotaryEncoderInterruptBase(
|
||||||
|
"rotEnc1")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotaryEncoderInterruptImpl1::init()
|
||||||
|
{
|
||||||
|
if (!radioConfig.preferences.rotary1_enabled)
|
||||||
|
{
|
||||||
|
// Input device is disabled.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pinA = radioConfig.preferences.rotary1_pin_a;
|
||||||
|
uint8_t pinB = radioConfig.preferences.rotary1_pin_b;
|
||||||
|
uint8_t pinPress = radioConfig.preferences.rotary1_pin_press;
|
||||||
|
char eventCw =
|
||||||
|
static_cast<char>(radioConfig.preferences.rotary1_event_cw);
|
||||||
|
char eventCcw =
|
||||||
|
static_cast<char>(radioConfig.preferences.rotary1_event_ccw);
|
||||||
|
char eventPressed =
|
||||||
|
static_cast<char>(radioConfig.preferences.rotary1_event_press);
|
||||||
|
|
||||||
|
//radioConfig.preferences.ext_notification_plugin_output
|
||||||
|
RotaryEncoderInterruptBase::init(
|
||||||
|
pinA, pinB, pinPress,
|
||||||
|
eventCw, eventCcw, eventPressed,
|
||||||
|
RotaryEncoderInterruptImpl1::handleIntA,
|
||||||
|
RotaryEncoderInterruptImpl1::handleIntB,
|
||||||
|
RotaryEncoderInterruptImpl1::handleIntPressed);
|
||||||
|
inputBroker->registerSource(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotaryEncoderInterruptImpl1::handleIntA()
|
||||||
|
{
|
||||||
|
rotaryEncoderInterruptImpl1->intAHandler();
|
||||||
|
}
|
||||||
|
void RotaryEncoderInterruptImpl1::handleIntB()
|
||||||
|
{
|
||||||
|
rotaryEncoderInterruptImpl1->intBHandler();
|
||||||
|
}
|
||||||
|
void RotaryEncoderInterruptImpl1::handleIntPressed()
|
||||||
|
{
|
||||||
|
rotaryEncoderInterruptImpl1->intPressHandler();
|
||||||
|
}
|
22
src/input/RotaryEncoderInterruptImpl1.h
Normal file
22
src/input/RotaryEncoderInterruptImpl1.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "RotaryEncoderInterruptBase.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The idea behind this class to have static methods for the event handlers.
|
||||||
|
* Check attachInterrupt() at RotaryEncoderInteruptBase.cpp
|
||||||
|
* Technically you can have as many rotary encoders hardver attached
|
||||||
|
* to your device as you wish, but you always need to have separate event
|
||||||
|
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
|
||||||
|
*/
|
||||||
|
class RotaryEncoderInterruptImpl1 :
|
||||||
|
public RotaryEncoderInterruptBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RotaryEncoderInterruptImpl1();
|
||||||
|
void init();
|
||||||
|
static void handleIntA();
|
||||||
|
static void handleIntB();
|
||||||
|
static void handleIntPressed();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
|
@ -235,3 +235,18 @@ std::vector<MeshPlugin *> MeshPlugin::GetMeshPluginsWithUIFrames()
|
|||||||
}
|
}
|
||||||
return pluginsWithUIFrames;
|
return pluginsWithUIFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MeshPlugin::observeUIEvents(
|
||||||
|
Observer<const UIFrameEvent *> *observer)
|
||||||
|
{
|
||||||
|
std::vector<MeshPlugin *> pluginsWithUIFrames;
|
||||||
|
for (auto i = plugins->begin(); i != plugins->end(); ++i) {
|
||||||
|
auto &pi = **i;
|
||||||
|
Observable<const UIFrameEvent *> *observable =
|
||||||
|
pi.getUIFrameObservable();
|
||||||
|
if (observable != NULL) {
|
||||||
|
DEBUG_MSG("Plugin wants a UI Frame\n");
|
||||||
|
observer->observe(observable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,6 +21,14 @@ enum class ProcessMessage
|
|||||||
STOP = 1,
|
STOP = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This struct is used by Screen to figure out whether screen frame should be updated.
|
||||||
|
*/
|
||||||
|
typedef struct _UIFrameEvent {
|
||||||
|
bool frameChanged;
|
||||||
|
bool needRedraw;
|
||||||
|
} UIFrameEvent;
|
||||||
|
|
||||||
/** A baseclass for any mesh "plugin".
|
/** A baseclass for any mesh "plugin".
|
||||||
*
|
*
|
||||||
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
|
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
|
||||||
@ -48,6 +56,7 @@ class MeshPlugin
|
|||||||
static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO);
|
static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO);
|
||||||
|
|
||||||
static std::vector<MeshPlugin *> GetMeshPluginsWithUIFrames();
|
static std::vector<MeshPlugin *> GetMeshPluginsWithUIFrames();
|
||||||
|
static void observeUIEvents(Observer<const UIFrameEvent *> *observer);
|
||||||
#ifndef NO_SCREEN
|
#ifndef NO_SCREEN
|
||||||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; }
|
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; }
|
||||||
#endif
|
#endif
|
||||||
@ -119,6 +128,7 @@ class MeshPlugin
|
|||||||
* @return true if you want to be alloced a UI screen frame
|
* @return true if you want to be alloced a UI screen frame
|
||||||
*/
|
*/
|
||||||
virtual bool wantUIFrame() { return false; }
|
virtual bool wantUIFrame() { return false; }
|
||||||
|
virtual Observable<const UIFrameEvent *>* getUIFrameObservable() { return NULL; }
|
||||||
|
|
||||||
MeshPacket *allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
|
MeshPacket *allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
|
||||||
|
|
||||||
|
280
src/plugins/CannedMessagePlugin.cpp
Normal file
280
src/plugins/CannedMessagePlugin.cpp
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
#include "configuration.h"
|
||||||
|
#include "CannedMessagePlugin.h"
|
||||||
|
#include "MeshService.h"
|
||||||
|
|
||||||
|
// TODO: reuse defined from Screen.cpp
|
||||||
|
#define FONT_SMALL ArialMT_Plain_10
|
||||||
|
#define FONT_MEDIUM ArialMT_Plain_16
|
||||||
|
#define FONT_LARGE ArialMT_Plain_24
|
||||||
|
|
||||||
|
// Remove Canned message screen if no action is taken for some milliseconds
|
||||||
|
#define INACTIVATE_AFTER_MS 20000
|
||||||
|
|
||||||
|
CannedMessagePlugin *cannedMessagePlugin;
|
||||||
|
|
||||||
|
CannedMessagePlugin::CannedMessagePlugin()
|
||||||
|
: SinglePortPlugin("canned", PortNum_TEXT_MESSAGE_APP),
|
||||||
|
concurrency::OSThread("CannedMessagePlugin")
|
||||||
|
{
|
||||||
|
if (radioConfig.preferences.canned_message_plugin_enabled)
|
||||||
|
{
|
||||||
|
if(this->splitConfiguredMessages() <= 0)
|
||||||
|
{
|
||||||
|
radioConfig.preferences.canned_message_plugin_enabled = false;
|
||||||
|
DEBUG_MSG("CannedMessagePlugin: No messages are configured. Plugin is disabled\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->inputObserver.observe(inputBroker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Items in array this->messages will be set to be pointing on the right
|
||||||
|
* starting points of the string radioConfig.preferences.canned_message_plugin_messages
|
||||||
|
*
|
||||||
|
* @return int Returns the number of messages found.
|
||||||
|
*/
|
||||||
|
int CannedMessagePlugin::splitConfiguredMessages()
|
||||||
|
{
|
||||||
|
int messageIndex = 0;
|
||||||
|
int i = 0;
|
||||||
|
this->messages[messageIndex++] =
|
||||||
|
radioConfig.preferences.canned_message_plugin_messages;
|
||||||
|
int upTo =
|
||||||
|
strlen(radioConfig.preferences.canned_message_plugin_messages) - 1;
|
||||||
|
|
||||||
|
while (i < upTo)
|
||||||
|
{
|
||||||
|
if (radioConfig.preferences.canned_message_plugin_messages[i] == '|')
|
||||||
|
{
|
||||||
|
// Message ending found, replace it with string-end character.
|
||||||
|
radioConfig.preferences.canned_message_plugin_messages[i] = '\0';
|
||||||
|
DEBUG_MSG("CannedMessage %d is: '%s'\n",
|
||||||
|
messageIndex-1, this->messages[messageIndex-1]);
|
||||||
|
|
||||||
|
if (messageIndex >= CANNED_MESSAGE_PLUGIN_MESSAGE_MAX_COUNT)
|
||||||
|
{
|
||||||
|
this->messagesCount = messageIndex;
|
||||||
|
return this->messagesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next message starts after pipe (|) just found.
|
||||||
|
this->messages[messageIndex++] =
|
||||||
|
(radioConfig.preferences.canned_message_plugin_messages + i + 1);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (strlen(this->messages[messageIndex-1]) > 0)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("CannedMessage %d is: '%s'\n",
|
||||||
|
messageIndex-1, this->messages[messageIndex-1]);
|
||||||
|
this->messagesCount = messageIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->messagesCount = messageIndex-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->messagesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CannedMessagePlugin::handleInputEvent(const InputEvent *event)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(strlen(radioConfig.preferences.canned_message_plugin_allow_input_origin) > 0) &&
|
||||||
|
(strcmp(radioConfig.preferences.canned_message_plugin_allow_input_origin, event->source) != 0) &&
|
||||||
|
(strcmp(radioConfig.preferences.canned_message_plugin_allow_input_origin, "_any") != 0))
|
||||||
|
{
|
||||||
|
// Event source is not accepted.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validEvent = false;
|
||||||
|
if (event->inputEvent == static_cast<char>(InputEventChar_UP))
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Canned message event UP\n");
|
||||||
|
this->action = CANNED_MESSAGE_ACTION_UP;
|
||||||
|
validEvent = true;
|
||||||
|
}
|
||||||
|
if (event->inputEvent == static_cast<char>(InputEventChar_DOWN))
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Canned message event DOWN\n");
|
||||||
|
this->action = CANNED_MESSAGE_ACTION_DOWN;
|
||||||
|
validEvent = true;
|
||||||
|
}
|
||||||
|
if (event->inputEvent == static_cast<char>(InputEventChar_SELECT))
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Canned message event Select\n");
|
||||||
|
this->action = CANNED_MESSAGE_ACTION_SELECT;
|
||||||
|
validEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validEvent)
|
||||||
|
{
|
||||||
|
// Let runOnce to be called immediately.
|
||||||
|
setIntervalFromNow(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CannedMessagePlugin::sendText(NodeNum dest,
|
||||||
|
const char* message,
|
||||||
|
bool wantReplies)
|
||||||
|
{
|
||||||
|
MeshPacket *p = allocDataPacket();
|
||||||
|
p->to = dest;
|
||||||
|
p->want_ack = true;
|
||||||
|
p->decoded.payload.size = strlen(message);
|
||||||
|
memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
|
||||||
|
if (radioConfig.preferences.canned_message_plugin_send_bell)
|
||||||
|
{
|
||||||
|
p->decoded.payload.bytes[p->decoded.payload.size-1] = 7; // Bell character
|
||||||
|
p->decoded.payload.bytes[p->decoded.payload.size] = '\0'; // Bell character
|
||||||
|
p->decoded.payload.size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// PacketId prevPacketId = p->id; // In case we need it later.
|
||||||
|
|
||||||
|
DEBUG_MSG("Sending message id=%d, msg=%.*s\n",
|
||||||
|
p->id, p->decoded.payload.size, p->decoded.payload.bytes);
|
||||||
|
|
||||||
|
service.sendToMesh(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CannedMessagePlugin::runOnce()
|
||||||
|
{
|
||||||
|
if (!radioConfig.preferences.canned_message_plugin_enabled)
|
||||||
|
{
|
||||||
|
return 30000; // TODO: should return MAX_VAL
|
||||||
|
}
|
||||||
|
DEBUG_MSG("Check status\n");
|
||||||
|
UIFrameEvent e = {false, true};
|
||||||
|
if (this->sendingState == SENDING_STATE_ACTIVE)
|
||||||
|
{
|
||||||
|
// TODO: might have some feedback of sendig state
|
||||||
|
this->sendingState = SENDING_STATE_NONE;
|
||||||
|
e.frameChanged = true;
|
||||||
|
this->notifyObservers(&e);
|
||||||
|
}
|
||||||
|
else if ((this->action != CANNED_MESSAGE_ACTION_NONE)
|
||||||
|
&& (this->currentMessageIndex == -1))
|
||||||
|
{
|
||||||
|
this->currentMessageIndex = 0;
|
||||||
|
DEBUG_MSG("First touch.\n");
|
||||||
|
e.frameChanged = true;
|
||||||
|
}
|
||||||
|
else if (this->action == CANNED_MESSAGE_ACTION_SELECT)
|
||||||
|
{
|
||||||
|
sendText(
|
||||||
|
NODENUM_BROADCAST,
|
||||||
|
this->messages[this->currentMessageIndex],
|
||||||
|
true);
|
||||||
|
this->sendingState = SENDING_STATE_ACTIVE;
|
||||||
|
this->currentMessageIndex = -1;
|
||||||
|
this->notifyObservers(&e);
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
else if (this->action == CANNED_MESSAGE_ACTION_UP)
|
||||||
|
{
|
||||||
|
this->currentMessageIndex = getPrevIndex();
|
||||||
|
DEBUG_MSG("MOVE UP");
|
||||||
|
}
|
||||||
|
else if (this->action == CANNED_MESSAGE_ACTION_DOWN)
|
||||||
|
{
|
||||||
|
this->currentMessageIndex = this->getNextIndex();
|
||||||
|
DEBUG_MSG("MOVE DOWN");
|
||||||
|
}
|
||||||
|
if (this->action != CANNED_MESSAGE_ACTION_NONE)
|
||||||
|
{
|
||||||
|
this->lastTouchMillis = millis();
|
||||||
|
this->action = CANNED_MESSAGE_ACTION_NONE;
|
||||||
|
this->notifyObservers(&e);
|
||||||
|
return INACTIVATE_AFTER_MS;
|
||||||
|
}
|
||||||
|
if ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)
|
||||||
|
{
|
||||||
|
// Reset plugin
|
||||||
|
DEBUG_MSG("Reset due the lack of activity.\n");
|
||||||
|
e.frameChanged = true;
|
||||||
|
this->currentMessageIndex = -1;
|
||||||
|
this->sendingState = SENDING_STATE_NONE;
|
||||||
|
this->notifyObservers(&e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 30000; // TODO: should return MAX_VAL
|
||||||
|
}
|
||||||
|
|
||||||
|
String CannedMessagePlugin::getCurrentMessage()
|
||||||
|
{
|
||||||
|
return this->messages[this->currentMessageIndex];
|
||||||
|
}
|
||||||
|
String CannedMessagePlugin::getPrevMessage()
|
||||||
|
{
|
||||||
|
return this->messages[this->getPrevIndex()];
|
||||||
|
}
|
||||||
|
String CannedMessagePlugin::getNextMessage()
|
||||||
|
{
|
||||||
|
return this->messages[this->getNextIndex()];
|
||||||
|
}
|
||||||
|
bool CannedMessagePlugin::shouldDraw()
|
||||||
|
{
|
||||||
|
if (!radioConfig.preferences.canned_message_plugin_enabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (currentMessageIndex != -1) || (this->sendingState != SENDING_STATE_NONE);
|
||||||
|
}
|
||||||
|
cannedMessagePluginSendigState CannedMessagePlugin::getSendingState()
|
||||||
|
{
|
||||||
|
return this->sendingState;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CannedMessagePlugin::getNextIndex()
|
||||||
|
{
|
||||||
|
if (this->currentMessageIndex >= (this->messagesCount -1))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this->currentMessageIndex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int CannedMessagePlugin::getPrevIndex()
|
||||||
|
{
|
||||||
|
if (this->currentMessageIndex <= 0)
|
||||||
|
{
|
||||||
|
return this->messagesCount - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this->currentMessageIndex - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CannedMessagePlugin::drawFrame(
|
||||||
|
OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
displayedNodeNum = 0; // Not currently showing a node pane
|
||||||
|
|
||||||
|
if (cannedMessagePlugin->getSendingState() == SENDING_STATE_NONE)
|
||||||
|
{
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->drawString(0 + x, 0 + y, cannedMessagePlugin->getPrevMessage());
|
||||||
|
display->setFont(FONT_MEDIUM);
|
||||||
|
display->drawString(0 + x, 0 + y + 8, cannedMessagePlugin->getCurrentMessage());
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->drawString(0 + x, 0 + y + 24, cannedMessagePlugin->getNextMessage());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
|
display->setFont(FONT_MEDIUM);
|
||||||
|
display->drawString(display->getWidth()/2 + x, 0 + y + 12, "Sending...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
69
src/plugins/CannedMessagePlugin.h
Normal file
69
src/plugins/CannedMessagePlugin.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "SinglePortPlugin.h"
|
||||||
|
#include "input/InputBroker.h"
|
||||||
|
|
||||||
|
enum cannedMessagePluginActionType
|
||||||
|
{
|
||||||
|
CANNED_MESSAGE_ACTION_NONE,
|
||||||
|
CANNED_MESSAGE_ACTION_SELECT,
|
||||||
|
CANNED_MESSAGE_ACTION_UP,
|
||||||
|
CANNED_MESSAGE_ACTION_DOWN
|
||||||
|
};
|
||||||
|
|
||||||
|
enum cannedMessagePluginSendigState
|
||||||
|
{
|
||||||
|
SENDING_STATE_NONE,
|
||||||
|
SENDING_STATE_ACTIVE
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define CANNED_MESSAGE_PLUGIN_MESSAGE_MAX_COUNT 50
|
||||||
|
|
||||||
|
class CannedMessagePlugin :
|
||||||
|
public SinglePortPlugin,
|
||||||
|
public Observable<const UIFrameEvent *>,
|
||||||
|
private concurrency::OSThread
|
||||||
|
{
|
||||||
|
CallbackObserver<CannedMessagePlugin, const InputEvent *> inputObserver =
|
||||||
|
CallbackObserver<CannedMessagePlugin, const InputEvent *>(
|
||||||
|
this, &CannedMessagePlugin::handleInputEvent);
|
||||||
|
public:
|
||||||
|
CannedMessagePlugin();
|
||||||
|
String getCurrentMessage();
|
||||||
|
String getPrevMessage();
|
||||||
|
String getNextMessage();
|
||||||
|
bool shouldDraw();
|
||||||
|
cannedMessagePluginSendigState getSendingState();
|
||||||
|
void eventUp();
|
||||||
|
void eventDown();
|
||||||
|
void eventSelect();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual int32_t runOnce();
|
||||||
|
|
||||||
|
void sendText(
|
||||||
|
NodeNum dest,
|
||||||
|
const char* message,
|
||||||
|
bool wantReplies);
|
||||||
|
|
||||||
|
int splitConfiguredMessages();
|
||||||
|
int getNextIndex();
|
||||||
|
int getPrevIndex();
|
||||||
|
|
||||||
|
int handleInputEvent(const InputEvent *event);
|
||||||
|
virtual bool wantUIFrame() { return this->shouldDraw(); }
|
||||||
|
virtual Observable<const UIFrameEvent *>* getUIFrameObservable() { return this; }
|
||||||
|
virtual void drawFrame(
|
||||||
|
OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
|
volatile cannedMessagePluginActionType action = CANNED_MESSAGE_ACTION_NONE;
|
||||||
|
int currentMessageIndex = -1;
|
||||||
|
cannedMessagePluginSendigState sendingState = SENDING_STATE_NONE;
|
||||||
|
|
||||||
|
char *messages[CANNED_MESSAGE_PLUGIN_MESSAGE_MAX_COUNT];
|
||||||
|
int messagesCount = 0;
|
||||||
|
unsigned long lastTouchMillis = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CannedMessagePlugin *cannedMessagePlugin;
|
@ -1,4 +1,6 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "input/InputBroker.h"
|
||||||
|
#include "input/RotaryEncoderInterruptImpl1.h"
|
||||||
#include "plugins/ExternalNotificationPlugin.h"
|
#include "plugins/ExternalNotificationPlugin.h"
|
||||||
#include "plugins/NodeInfoPlugin.h"
|
#include "plugins/NodeInfoPlugin.h"
|
||||||
#include "plugins/PositionPlugin.h"
|
#include "plugins/PositionPlugin.h"
|
||||||
@ -8,6 +10,7 @@
|
|||||||
#include "plugins/TextMessagePlugin.h"
|
#include "plugins/TextMessagePlugin.h"
|
||||||
#include "plugins/RoutingPlugin.h"
|
#include "plugins/RoutingPlugin.h"
|
||||||
#include "plugins/AdminPlugin.h"
|
#include "plugins/AdminPlugin.h"
|
||||||
|
#include "plugins/CannedMessagePlugin.h"
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
#include "plugins/esp32/SerialPlugin.h"
|
#include "plugins/esp32/SerialPlugin.h"
|
||||||
#include "plugins/esp32/EnvironmentalMeasurementPlugin.h"
|
#include "plugins/esp32/EnvironmentalMeasurementPlugin.h"
|
||||||
@ -20,6 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
void setupPlugins()
|
void setupPlugins()
|
||||||
{
|
{
|
||||||
|
inputBroker = new InputBroker();
|
||||||
adminPlugin = new AdminPlugin();
|
adminPlugin = new AdminPlugin();
|
||||||
nodeInfoPlugin = new NodeInfoPlugin();
|
nodeInfoPlugin = new NodeInfoPlugin();
|
||||||
positionPlugin = new PositionPlugin();
|
positionPlugin = new PositionPlugin();
|
||||||
@ -30,6 +34,10 @@ void setupPlugins()
|
|||||||
|
|
||||||
new RemoteHardwarePlugin();
|
new RemoteHardwarePlugin();
|
||||||
new ReplyPlugin();
|
new ReplyPlugin();
|
||||||
|
rotaryEncoderInterruptImpl1 =
|
||||||
|
new RotaryEncoderInterruptImpl1();
|
||||||
|
rotaryEncoderInterruptImpl1->init();
|
||||||
|
cannedMessagePlugin = new CannedMessagePlugin();
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
// Only run on an esp32 based device.
|
// Only run on an esp32 based device.
|
||||||
|
Loading…
Reference in New Issue
Block a user