Skip to content

Commit 05db092

Browse files
committed
Implement publishing back to IO, T5 driver for pre-2025
1 parent b5e5334 commit 05db092

File tree

28 files changed

+1079
-426
lines changed

28 files changed

+1079
-426
lines changed

Doxyfile

Lines changed: 833 additions & 372 deletions
Large diffs are not rendered by default.

src/Wippersnapper.cpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,10 +1684,30 @@ bool cbDecodeDisplayMsg(pb_istream_t *stream, const pb_field_t *field,
16841684
}
16851685

16861686
// Attempt to add or replace a display component
1687-
bool did_add =
1688-
WS._displayController->Handle_Display_AddOrReplace(&msgAddReq);
1689-
// TODO: Add response handling and publishing here, for now it always
1690-
// returns true and doesnt publish back to the broker
1687+
bool did_add = WS._displayController->Handle_Display_AddOrReplace(&msgAddReq);
1688+
1689+
// Create a DisplayResponse message
1690+
wippersnapper_signal_v1_DisplayResponse msgResp = wippersnapper_signal_v1_DisplayResponse_init_zero;
1691+
msgResp.which_payload = wippersnapper_signal_v1_DisplayResponse_display_added_tag;
1692+
msgResp.payload.display_added.did_add = did_add;
1693+
strncpy(msgResp.payload.display_added.name, msgAddReq.name, sizeof(msgResp.payload.display_added.name));
1694+
1695+
// Encode and publish response back to broker
1696+
memset(WS._buffer_outgoing, 0, sizeof(WS._buffer_outgoing));
1697+
pb_ostream_t ostream = pb_ostream_from_buffer(WS._buffer_outgoing, sizeof(WS._buffer_outgoing));
1698+
if (!ws_pb_encode(&ostream, wippersnapper_signal_v1_DisplayResponse_fields, &msgResp)) {
1699+
WS_DEBUG_PRINTLN("ERROR: Unable to encode display response message!");
1700+
return false;
1701+
}
1702+
1703+
size_t msgSz;
1704+
pb_get_encoded_size(&msgSz, wippersnapper_signal_v1_DisplayResponse_fields, &msgResp);
1705+
WS_DEBUG_PRINT("Publishing DisplayResponse Message...");
1706+
if (!WS._mqtt->publish(WS._topic_signal_display_device, WS._buffer_outgoing, msgSz, 1)) {
1707+
WS_DEBUG_PRINTLN("ERROR: Failed to DisplayResponse Response!");
1708+
} else {
1709+
WS_DEBUG_PRINTLN("Published!");
1710+
}
16911711
} else if (field->tag ==
16921712
wippersnapper_signal_v1_DisplayRequest_display_write_tag) {
16931713
// Decode message into a DisplayAddRequest
@@ -1698,7 +1718,23 @@ bool cbDecodeDisplayMsg(pb_istream_t *stream, const pb_field_t *field,
16981718
WS_DEBUG_PRINTLN("ERROR: Failure decoding DisplayWrite message!");
16991719
return false;
17001720
}
1721+
// Attempt to write to a display
17011722
WS._displayController->Handle_Display_Write(&msgWrite);
1723+
} else if (field->tag == wippersnapper_signal_v1_DisplayRequest_display_remove_tag) {
1724+
// Decode message into a DisplayRemoveRequest
1725+
wippersnapper_display_v1_DisplayRemove msgRemove =
1726+
wippersnapper_display_v1_DisplayRemove_init_zero;
1727+
if (!ws_pb_decode(stream,
1728+
wippersnapper_display_v1_DisplayRemove_fields,
1729+
&msgRemove)) {
1730+
WS_DEBUG_PRINTLN("ERROR: Failure decoding DisplayRemove message!");
1731+
return false;
1732+
}
1733+
// Attempt to remove a display
1734+
WS._displayController->Handle_Display_Remove(&msgRemove);
1735+
} else {
1736+
WS_DEBUG_PRINTLN("ERROR: Display message type not found!");
1737+
return false;
17021738
}
17031739
return true;
17041740
}

src/components/display/controller.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ DisplayController::DisplayController() {
2525
@brief Destructor
2626
*/
2727
DisplayController::~DisplayController() {
28-
// TODO
28+
// Clean up all display hardware instances
29+
for (DisplayHardware *hw_instance : _hw_instances) {
30+
delete hw_instance;
31+
}
32+
_hw_instances.clear();
2933
}
3034

3135
/*!

src/components/display/controller.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ class DisplayController {
3535
wippersnapper_display_v1_DisplayAddOrReplace *msgAdd);
3636
bool Handle_Display_Remove(wippersnapper_display_v1_DisplayRemove *msgRemove);
3737
bool Handle_Display_Write(wippersnapper_display_v1_DisplayWrite *msgWrite);
38-
3938
private:
4039
std::vector<DisplayHardware *>
4140
_hw_instances; ///< Holds pointers to DisplayHardware instances

src/components/display/drivers/dispDrvBase.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
#ifndef WS_DISP_DRV_BASE_H
1616
#define WS_DISP_DRV_BASE_H
1717

18-
#include "Adafruit_ThinkInk.h"
1918
#include "Wippersnapper.h"
19+
#include "Adafruit_ThinkInk.h"
2020

2121
/*!
2222
@brief Abstract base class for display drivers.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*!
2+
* @file src/components/display/drivers/ThinkInk_290_Grayscale4_T5.h
3+
*
4+
* Driver for ThinkInk 2.9" Grayscale 4-level T5 display (present on the
5+
* pre-2025 version of the Adafruit MagTag)
6+
*
7+
* Adafruit invests time and resources providing this open source code,
8+
* please support Adafruit and open-source hardware by purchasing
9+
* products from Adafruit!
10+
*
11+
* Copyright (c) Brent Rubell 2025 for Adafruit Industries.
12+
*
13+
* BSD license, all text here must be included in any redistribution.
14+
*
15+
*/
16+
#ifndef WS_DRV_THINKINK_GRAYSCALE4_T5_H
17+
#define WS_DRV_THINKINK_GRAYSCALE4_T5_H
18+
19+
#include "dispDrvBase.h"
20+
21+
/*!
22+
@brief Driver for a ThinkInk 2.9" Grayscale 4-level T5 display (pre-2025 version of the Adafruit MagTag).
23+
*/
24+
class dispDrvThinkInkGrayscale4T5 : public dispDrvBase {
25+
public:
26+
/*!
27+
@brief Constructor for the ThinkInk Grayscale 4-level EAAMFGN display
28+
driver.
29+
@param dc
30+
Data/Command pin for the display.
31+
@param rst
32+
Reset pin for the display.
33+
@param cs
34+
Chip Select pin for the display.
35+
@param sram_cs
36+
Optional SRAM Chip Select pin for E-Ink displays that support it.
37+
@param busy
38+
Optional Busy pin for the display.
39+
*/
40+
dispDrvThinkInkGrayscale4T5(int16_t dc, int16_t rst, int16_t cs,
41+
int16_t sram_cs = -1, int16_t busy = -1)
42+
: dispDrvBase(dc, rst, cs, sram_cs, busy), _display(nullptr) {}
43+
44+
~dispDrvThinkInkGrayscale4T5() {
45+
if (_display) {
46+
delete _display;
47+
_display = nullptr;
48+
}
49+
}
50+
51+
/*!
52+
@brief Attempts to initialize the ThinkInk Grayscale 4-level EAAMFGN
53+
display driver.
54+
@param mode
55+
The ThinkInk mode to use for the display.
56+
@param reset
57+
Whether to reset the display before initialization.
58+
@return True if the display was initialized successfully, false otherwise.
59+
*/
60+
bool begin(thinkinkmode_t mode, bool reset = true) override {
61+
_display = new ThinkInk_290_Grayscale4_T5(_pin_dc, _pin_rst, _pin_cs,
62+
_pin_sram_cs, _pin_busy);
63+
if (!_display)
64+
return false; // Allocation failed
65+
66+
// Initialize the display
67+
_display->begin(mode);
68+
// Configure display settings
69+
_text_sz = 3;
70+
_display->setTextSize(_text_sz);
71+
_display->setTextColor(EPD_BLACK);
72+
_display->setTextWrap(false);
73+
_height = _display->height();
74+
_width = _display->width();
75+
// Clear the display buffer
76+
_display->clearBuffer();
77+
_display->display();
78+
79+
return true;
80+
}
81+
82+
/*!
83+
@brief Writes a message to the display.
84+
@param message
85+
The message to write to the display.
86+
@note This method overrides the base class method to provide specific
87+
functionality for the Think Ink Grayscale 4 EAAMGFGN driver.
88+
*/
89+
virtual void writeMessage(const char *message) override {
90+
if (_display == nullptr)
91+
return;
92+
93+
// Start with a fresh display buffer
94+
_display->clearBuffer();
95+
int16_t y_idx = 0;
96+
_display->setCursor(0, y_idx);
97+
98+
// Calculate the line height based on the text size (NOTE: base height is
99+
// 8px)
100+
int16_t line_height = 8 * _text_sz;
101+
uint16_t c_idx = 0;
102+
size_t msg_size = strlen(message);
103+
for (size_t i = 0; i < msg_size && c_idx < msg_size; i++) {
104+
if (y_idx + line_height > _height)
105+
break;
106+
if (message[i] == '\\' && i + 1 < msg_size &&
107+
(message[i + 1] == 'n' || message[i + 1] == 'r')) {
108+
// Handle \r\n sequence as a single newline
109+
if (message[i + 1] == 'r' && i + 3 < msg_size &&
110+
message[i + 2] == '\\' && message[i + 3] == 'n') {
111+
// Skip to the next line
112+
if (y_idx + line_height > _height)
113+
break;
114+
y_idx += line_height;
115+
_display->setCursor(0, y_idx);
116+
i += 3;
117+
} else if (message[i + 1] == 'n') {
118+
// Skip to the next line
119+
if (y_idx + line_height > _height)
120+
break;
121+
y_idx += line_height;
122+
_display->setCursor(0, y_idx);
123+
i++;
124+
}
125+
} else if (message[i] == 0xC2 && message[i + 1] == 0xB0) {
126+
_display->write(char(248));
127+
i++;
128+
} else {
129+
_display->print(message[i]);
130+
}
131+
}
132+
_display->display();
133+
}
134+
135+
private:
136+
ThinkInk_290_Grayscale4_T5 *_display;
137+
};
138+
139+
#endif // WS_DRV_THINKINK_GRAYSCALE4_T5_H

src/components/display/hardware.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,30 @@ using FnCreateDispDrv =
2323
// Factory for creating a new display drivers
2424
// NOTE: When you add a new display driver, make sure to add it to the factory!
2525
static const std::map<std::string, FnCreateDispDrv> FactoryDrvDisp = {
26-
{"grayscale4_eaamfgn",
26+
{"thinkink-gs4-eaamfgn",
2727
[](int16_t dc, int16_t rst, int16_t cs, int16_t sram_cs,
2828
int16_t busy) -> dispDrvBase * {
2929
return new drvDispThinkInkGrayscale4Eaamfgn(dc, rst, cs, sram_cs, busy);
3030
}},
31-
{"magtag_2025",
31+
{"thinkink-magtag-2025",
3232
[](int16_t dc, int16_t rst, int16_t cs, int16_t sram_cs,
3333
int16_t busy) -> dispDrvBase * {
3434
return new drvDispThinkInkGrayscale4Eaamfgn(dc, rst, cs, sram_cs, busy);
35-
}}};
35+
}},
36+
{"thinkink-gs4-t5",
37+
[](int16_t dc, int16_t rst, int16_t cs, int16_t sram_cs,
38+
int16_t busy) -> dispDrvBase * {
39+
return new dispDrvThinkInkGrayscale4T5(dc, rst, cs, sram_cs, busy);
40+
}},
41+
{"thinkink-magtag-pre-2025",
42+
[](int16_t dc, int16_t rst, int16_t cs, int16_t sram_cs,
43+
int16_t busy) -> dispDrvBase * {
44+
return new dispDrvThinkInkGrayscale4T5(dc, rst, cs, sram_cs, busy);
45+
}}
46+
};
47+
3648

49+
3750
/*!
3851
@brief Creates a new display driver instance based on the driver name.
3952
@param driver_name

src/components/display/hardware.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
#ifndef WS_DISPLAY_HARDWARE_H
1616
#define WS_DISPLAY_HARDWARE_H
1717
#include "Wippersnapper.h"
18-
#include "drivers/dispDrvBase.h"
19-
#include "drivers/dispDrvThinkInkGrayscale4Eaamfgn.h"
2018
#include <functional>
2119
#include <map>
20+
#include "drivers/dispDrvBase.h"
21+
#include "drivers/dispDrvThinkInkGrayscale4Eaamfgn.h"
22+
#include "drivers/dispDrvThinkInkGrayscale4T5.h"
2223

2324
/*!
2425
@brief Interface for interacting with display hardware (TFT, eInk,

src/wippersnapper/description/v1/description.pb.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Automatically generated nanopb constant definitions */
2-
/* Generated by nanopb-0.4.5-dev at Tue Aug 19 17:48:25 2025. */
2+
/* Generated by nanopb-0.4.5-dev at Wed Aug 20 17:46:06 2025. */
33

44
#include "wippersnapper/description/v1/description.pb.h"
55
#if PB_PROTO_HEADER_VERSION != 40

src/wippersnapper/description/v1/description.pb.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Automatically generated nanopb header */
2-
/* Generated by nanopb-0.4.5-dev at Tue Aug 19 17:48:25 2025. */
2+
/* Generated by nanopb-0.4.5-dev at Wed Aug 20 17:46:06 2025. */
33

44
#ifndef PB_WIPPERSNAPPER_DESCRIPTION_V1_WIPPERSNAPPER_DESCRIPTION_V1_DESCRIPTION_PB_H_INCLUDED
55
#define PB_WIPPERSNAPPER_DESCRIPTION_V1_WIPPERSNAPPER_DESCRIPTION_V1_DESCRIPTION_PB_H_INCLUDED

0 commit comments

Comments
 (0)