/
takeOverDisplay.ino
304 lines (238 loc) · 10.8 KB
/
takeOverDisplay.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#include "takeOverDisplay_menu.h"
#include <EepromAbstractionWire.h>
#include <IoAbstractionWire.h>
#include <TaskManager.h>
#include <RemoteAuthentication.h>
#include <RemoteMenuItem.h>
#include <IoLogging.h>
// contains the graphical widget title components.
#include "stockIcons/wifiAndConnectionIconsLCD.h"
/**
* This TcMenu example shows how to take over the display for your own purposes from a menu item.
* It also shows how to use the dialog facilities to locally show an information dialog and also a
* question dialog.
*
* For more detail see the README.md file
*/
// Set up ethernet, the usual default settings are chosen. Change to your preferred values or use DHCP.
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
// In the designer UI we configured io23017 as an IoExpander variable for both the input and display.
// We must now create it as an MCP23017 expander. Address is 0x20 with interrupt pin connected to pin 2.
// make sure you've arranged for !RESET pin to be held HIGH!!
IoAbstractionRef io23017 = ioFrom23017(0x20, ACTIVE_LOW_OPEN, 2);
// a counter that we use in the display function when we take over the display.
int counter = 0;
// if you don't have an i2c rom uncomment the avr variant and remove the i2c one.
// AvrEeprom eeprom;
I2cAt24Eeprom eeprom(0x50, PAGESIZE_AT24C128); // page size 64 for AT24-128 model
// we want to authenticate connections, the easiest and quickest way is to use the EEPROM
// authenticator where pairing requests add a new item into the EEPROM. Any authentication
// requests are then handled by looking in the EEPROM.
EepromAuthenticatorManager authManager;
// Here we create two additional menus, that will be added manually to handle the connectivity
// status and authentication keys. In a future version these will be added to th desinger.
RemoteMenuItem menuRemoteMonitor(1001, 2);
EepromAuthenicationInfoMenuItem menuAuthKeyMgr(1002, &authManager, &menuRemoteMonitor);
// We add a title widget that shows when a user is connected to the device. Connection icons
// are in the standard icon set we included at the top.
// Yes even on LCD we now support title widgets, but they eat up a few of your custom chars.
// The width must always be 1, and the height is the first custom character that is used.
TitleWidget connectedWidget(iconsConnection, 2, 1, 0);
// when there's a change in communication status (client connects for example) this gets called.
void onCommsChange(CommunicationInfo info) {
if(info.remoteNo == 0) {
connectedWidget.setCurrentState(info.connected ? 1 : 0);
}
// this relies on logging in IoAbstraction's ioLogging.h, to turn it on visit the file for instructions.
serdebugF4("Comms notify (rNo, con, enum)", info.remoteNo, info.connected, info.errorMode);
}
void setup() {
//
// If you are using serial (connectivity or logging) and wire they must be initialised
// before their first use.
//
Serial.begin(115200);
Wire.begin();
// When the renderer times out and is about to reset to main menu, you can get a callback.
// For example if the menu should only be displayed during configuration.
//
// Call BEFORE setupMenu to ensure it takes effect immediately, call AFTER setupMenu if you
// want to start in menu mode, but then apply the reset handler from that point onwards.
renderer.setResetCallback([] {
counter = 0;
renderer.takeOverDisplay(myDisplayFunction);
});
serdebug("Added the reset callback");
// now we enable authentication using EEPROM authentication. Where the EEPROM is
// queried for authentication requests, and any additional pairs are stored there too.
// first we initialise the authManager, then pass it to the class.
// Always call BEFORE setupMenu()
authManager.initialise(&eeprom, 100);
remoteServer.setAuthenticator(&authManager);
menuMgr.setAuthenticator(&authManager);
// Here we add two additional menus for managing the connectivity and authentication keys.
// In the future, there will be an option to autogenerate these from the designer.
menuConnectivityIPAddress.setNext(&menuAuthKeyMgr);
menuRemoteMonitor.addConnector(remoteServer.getRemoteConnector(0));
menuRemoteMonitor.registerCommsNotification(onCommsChange);
menuAuthKeyMgr.setLocalOnly(true);
// here we need to access the ip address before the menu is fully initialised, so we just load the item
// be careful not to use any infrastructure of the menu in its callback
if(!loadMenuItem(&eeprom, &menuConnectivityIPAddress)) {
menuConnectivityIPAddress.setIpAddress(192, 168, 0, 200);
}
// spin up the Ethernet library, get the IP address from the menu
byte* rawIp = menuConnectivityIPAddress.getIpAddress();
IPAddress ip(rawIp[0], rawIp[1], rawIp[2], rawIp[3]);
Ethernet.begin(mac, ip);
// Here we add a widget to the display, it will display connected status.
renderer.setFirstWidget(&connectedWidget);
// this is put in by the menu designer and must be called (always ensure devices are setup first).
setupMenu();
menuMgr.load(eeprom);
// and print out the IP address
char sz[20];
menuConnectivityIPAddress.copyValue(sz, sizeof(sz));
serdebugF2("Device IP is: ", sz);
authManager.copyPinToBuffer(sz, sizeof(sz));
menuConnectivityChangePin.setTextValue(sz);
menuConnectivityChangePin.setPasswordField(true);
switches.addSwitch(4, [](uint8_t, bool held) {
serdebugF2("Extra switch ", (held ? "held" : "pressed"));
}, 20, true);
}
//
// standard setup for all taskManager based sketches. Always call runLoop in the loop.
// Never do anything long running in here.
//
void loop() {
taskManager.runLoop();
}
//
// When the food choice option is changed on the menu, this function is called, it takes
// the value from menuFood and renders it as text in the menuText text item.
//
void CALLBACK_FUNCTION onFoodChoice(int /*id*/) {
// copy the enum text for the current value
char enumStr[20];
int enumVal = menuFood.getCurrentValue();
menuFood.copyEnumStrToBuffer(enumStr, sizeof(enumStr), enumVal);
serdebugF2("Changed food choice to ", enumStr);
// and put it into a text menu item
menuText.setTextValue(enumStr);
}
//
// this is the function called by the renderer every 1/5 second once the display is
// taken over, we pass this function to takeOverDisplay below.
//
void myDisplayFunction(unsigned int encoderValue, RenderPressMode clicked) {
// we initialise the display on the first call.
if(counter == 0) {
switches.changeEncoderPrecision(999, 50);
lcd.clear();
lcd.print("We have the display!");
lcd.setCursor(0, 1);
lcd.print("OK button for menu..");
}
// We are told when the button is pressed in by the boolean parameter.
// When the button is clicked, we give back to the menu..
if(clicked) {
renderer.giveBackDisplay();
counter = 0;
}
else {
char buffer[5];
// otherwise update the counter.
lcd.setCursor(0, 2);
ltoaClrBuff(buffer, ++counter, 4, ' ', sizeof(buffer));
lcd.print(buffer);
lcd.setCursor(12, 2);
ltoaClrBuff(buffer, encoderValue, 4, '0', sizeof(buffer));
lcd.print(buffer);
}
}
//
// We have an option on the menu to take over the display, this function is called when that
// option is chosen.
//
void CALLBACK_FUNCTION onTakeOverDisplay(int /*id*/) {
// in order to take over rendering onto the display we just request the display
// at which point tcMenu will stop rendering until the display is "given back".
// Don't forget that LiquidCrystalIO uses task manager and things can be happening
// in the background. Always ensure all operations with the LCD occur on the rendering
// call back.
counter = 0;
renderer.takeOverDisplay(myDisplayFunction);
}
const char pgmInfoHeader[] PROGMEM = "Information dialog";
const char pgmQuestionHeader[] PROGMEM = "Order Food?";
void CALLBACK_FUNCTION onInfoDlg(int /*id*/) {
// every renderer apart from NoRenderer has a dialog, that can be used to present
// very basic info locally onto any display. Used in situations where something
// needs to be confirmed / printed onto the local display.
BaseDialog* dlg = renderer.getDialog();
if(!dlg) return;
// first we set the buttons how we want them. BTNTYPE_NONE means no button.
dlg->setButtons(BTNTYPE_NONE, BTNTYPE_CLOSE);
// then we show the dialog - 2nd boolean parameter is if dialog is local only
dlg->show(pgmInfoHeader, true);
// and then we set the second line (buffer) - must be after show.
dlg->copyIntoBuffer("to be set..");
// you can set the dialog buffer at some point later, it's safe, even if it's been dismissed.
taskManager.scheduleOnce(1000, [] {
BaseDialog* dlg = renderer.getDialog();
dlg->copyIntoBuffer("now it's set..");
});
}
//
// It's also possible to know when the dialog has finished, and what button was pressed.
// This is done by passing a function like below as second parameter to show.
//
void onFinished(ButtonType btn, void* /*userData*/) {
if(btn == BTNTYPE_ACCEPT) {
char sz[20];
menuFood.copyEnumStrToBuffer(sz, sizeof(sz), menuFood.getCurrentValue());
serdebugF2("Chosen food ", sz);
}
else {
serdebugF("User did not choose to proceed.");
}
}
void CALLBACK_FUNCTION onQuestionDlg(int /*id*/) {
// yet another dialog, to ask a question this time.
BaseDialog* dlg = renderer.getDialog();
// this time we use two buttons and provide the selected index at the end (zero based)
dlg->setButtons(BTNTYPE_ACCEPT, BTNTYPE_CANCEL, 1);
// we can optionally set some data that will be given to us in the finished call back.
dlg->setUserData(NULL);
// now we show the dialog (also giving the finished callback)
dlg->show(pgmQuestionHeader, true, onFinished);
// and lastly we set the text in the buffer area (2nd line)
char sz[20];
menuFood.copyEnumStrToBuffer(sz, sizeof(sz), menuFood.getCurrentValue());
dlg->copyIntoBuffer(sz);
}
//
// We have a save option on the menu to save the settings. In a real system we could instead
// look at using a power down detection circuit to do this. For more info see below link.
// https://www.thecoderscorner.com/electronics/microcontrollers/psu-control/detecting-power-loss-in-powersupply/
//
void CALLBACK_FUNCTION onSaveSettings(int /*id*/) {
menuMgr.save(eeprom);
}
const char pgmPinTooShort[] PROGMEM = "Pin too short";
void CALLBACK_FUNCTION onChangePin(int id) {
const char* sz = menuConnectivityChangePin.getTextValue();
if (strlen(sz) < 4) {
BaseDialog* dlg = renderer.getDialog();
dlg->setButtons(BTNTYPE_NONE, BTNTYPE_CLOSE);
dlg->copyIntoBuffer(sz);
dlg->show(pgmPinTooShort, false);
}
else {
authManager.changePin(sz);
serdebugF2("Pin changed to ", sz);
}
}