hms11

ProgrammableRelayBoard.04

May 29th, 2025
24
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.83 KB | None | 0 0
  1. #include <Wire.h>
  2. #include <Adafruit_GFX.h>
  3. #include <Adafruit_SSD1306.h>
  4.  
  5. #include <EEPROM.h>
  6.  
  7. #define EEPROM_START_ADDR 0 // starting EEPROM address for the map
  8. #define EEPROM_MAP_SIZE (4 * 4) // 16 bytes (one byte per bool)
  9.  
  10. // 4 inputs, each can control 4 relays (true = ON, false = OFF)
  11. bool inputRelayMap[4][4] = {
  12. {false, false, false, false},
  13. {false, false, false, false},
  14. {false, false, false, false},
  15. {false, false, false, false}
  16. };
  17.  
  18. // === OLED ===
  19. #define SCREEN_WIDTH 128
  20. #define SCREEN_HEIGHT 64
  21. #define OLED_RESET -1
  22. #define OLED_ADDR 0x3C
  23. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  24.  
  25. volatile int8_t encoderDelta = 0;
  26. volatile uint8_t lastEncoderState = 0;
  27.  
  28. uint8_t editingInputIndex = 0; // Which input we are editing (0-3)
  29. uint8_t editingRelayIndex = 0; // Which relay (0-3) cursor is on in edit mode
  30. bool inEditMode = false; // True when in the relay map editing screen
  31.  
  32.  
  33. // === Input Pins ===
  34. const uint8_t INPUT_PINS[4] = {7, 6, 5, 4};
  35. const uint8_t ENCODER_PIN_A = 2;
  36. const uint8_t ENCODER_PIN_B = 3;
  37. const uint8_t ENCODER_BUTTON = 8;
  38.  
  39. // === Analog Inputs ===
  40. const uint8_t SENSOR_PIN = A1;
  41. const uint8_t BATTERY_PIN = A0;
  42.  
  43. // === Relay Outputs ===
  44. const uint8_t RELAY_PINS[4] = {10, 11, 12, 13};
  45.  
  46. // === Menu Navigation State ===
  47. #define MAX_MENU_DEPTH 4
  48.  
  49. struct MenuNode {
  50. const char* label;
  51. void (*action)();
  52. MenuNode* parent;
  53. MenuNode** children;
  54. uint8_t childCount;
  55. };
  56.  
  57. MenuNode* currentMenu = nullptr;
  58. uint8_t currentIndex = 0;
  59.  
  60. MenuNode* menuStack[MAX_MENU_DEPTH];
  61. uint8_t indexStack[MAX_MENU_DEPTH];
  62. uint8_t menuDepth = 0;
  63.  
  64. void encoderISR() {
  65. uint8_t state = (digitalRead(ENCODER_PIN_A) << 1) | digitalRead(ENCODER_PIN_B);
  66. uint8_t transition = (lastEncoderState << 2) | state;
  67.  
  68. switch (transition) {
  69. case 0b0001:
  70. case 0b0111:
  71. case 0b1110:
  72. case 0b1000:
  73. encoderDelta++;
  74. break;
  75. case 0b0010:
  76. case 0b0100:
  77. case 0b1101:
  78. case 0b1011:
  79. encoderDelta--;
  80. break;
  81. default:
  82. break;
  83. }
  84.  
  85. lastEncoderState = state;
  86. }
  87.  
  88.  
  89. void loadInputRelayMapFromEEPROM() {
  90. for (uint8_t input = 0; input < 4; input++) {
  91. for (uint8_t relay = 0; relay < 4; relay++) {
  92. uint8_t val = EEPROM.read(EEPROM_START_ADDR + input * 4 + relay);
  93. inputRelayMap[input][relay] = (val == 1);
  94. }
  95. }
  96. }
  97.  
  98. void saveInputRelayMapToEEPROM() {
  99. for (uint8_t input = 0; input < 4; input++) {
  100. for (uint8_t relay = 0; relay < 4; relay++) {
  101. EEPROM.update(EEPROM_START_ADDR + input * 4 + relay, inputRelayMap[input][relay] ? 1 : 0);
  102. }
  103. }
  104. }
  105.  
  106. // Show the menu with Back option if applicable
  107. void renderMenu() {
  108. display.clearDisplay();
  109. display.setTextSize(1);
  110. display.setTextColor(SSD1306_WHITE);
  111.  
  112. uint8_t itemCount = currentMenu->childCount;
  113. bool showBack = (currentMenu->parent != nullptr);
  114. if (showBack) itemCount++; // add Back option
  115.  
  116. for (uint8_t i = 0; i < itemCount; i++) {
  117. int y = i * 8;
  118. display.setCursor(0, y);
  119. if (i == currentIndex) display.print("> ");
  120. else display.print(" ");
  121.  
  122. if (showBack && i == itemCount - 1) {
  123. display.println("< Back");
  124. } else {
  125. char label[18];
  126. strncpy(label, currentMenu->children[i]->label, 16);
  127. label[16] = '\0';
  128. display.println(label);
  129. }
  130. }
  131. display.display();
  132. }
  133.  
  134. void renderRelayMapEditor() {
  135. display.clearDisplay();
  136. display.setTextSize(1);
  137. display.setTextColor(SSD1306_WHITE);
  138.  
  139. // Title e.g. "Input 1 Relay Map"
  140. display.setCursor(0, 0);
  141. display.print("Input ");
  142. display.print(editingInputIndex + 1);
  143. display.println(" Relay Map");
  144.  
  145. // Show each relay with ON/OFF status
  146. for (uint8_t i = 0; i < 4; i++) {
  147. display.setCursor(0, 16 + i * 10);
  148.  
  149. // Cursor indicator
  150. if (i == editingRelayIndex) display.print("> ");
  151. else display.print(" ");
  152.  
  153. // Show relay number
  154. display.print("Relay ");
  155. display.print(i + 1);
  156. display.print(": ");
  157.  
  158. // Show ON/OFF status
  159. if (inputRelayMap[editingInputIndex][i]) {
  160. display.println("[X]");
  161. } else {
  162. display.println("[ ]");
  163. }
  164.  
  165. // Show Back option
  166. display.setCursor(0, 60);
  167. if (editingRelayIndex == 4) display.print("> ");
  168. else display.print(" ");
  169. display.println("Back");
  170.  
  171. display.display();
  172. }
  173. }
  174.  
  175.  
  176. void goBack() {
  177. if (menuDepth > 0) {
  178. menuDepth--;
  179. currentMenu = menuStack[menuDepth];
  180. currentIndex = indexStack[menuDepth];
  181. renderMenu();
  182. }
  183. }
  184.  
  185. void enterMenu(MenuNode* menu) {
  186. if (menuDepth < MAX_MENU_DEPTH) {
  187. menuStack[menuDepth] = currentMenu;
  188. indexStack[menuDepth] = currentIndex;
  189. menuDepth++;
  190.  
  191. currentMenu = menu;
  192. currentIndex = 0;
  193. renderMenu();
  194. }
  195. }
  196.  
  197. // === Menu Structure ===
  198.  
  199. void editInputRelayMap0() { editingInputIndex = 0; editingRelayIndex = 0; inEditMode = true; renderRelayMapEditor(); }
  200. void editInputRelayMap1() { editingInputIndex = 1; editingRelayIndex = 0; inEditMode = true; renderRelayMapEditor(); }
  201. void editInputRelayMap2() { editingInputIndex = 2; editingRelayIndex = 0; inEditMode = true; renderRelayMapEditor(); }
  202. void editInputRelayMap3() { editingInputIndex = 3; editingRelayIndex = 0; inEditMode = true; renderRelayMapEditor(); }
  203.  
  204.  
  205. /// 2. Leaf nodes
  206. MenuNode input1 = { "Input 1 -> Relay Map", editInputRelayMap0, nullptr, nullptr, 0 };
  207. MenuNode input2 = { "Input 2 -> Relay Map", editInputRelayMap1, nullptr, nullptr, 0 };
  208. MenuNode input3 = { "Input 3 -> Relay Map", editInputRelayMap2, nullptr, nullptr, 0 };
  209. MenuNode input4 = { "Input 4 -> Relay Map", editInputRelayMap3, nullptr, nullptr, 0 };
  210.  
  211. // 3. Array of pointers
  212. MenuNode* inputItems[] = { &input1, &input2, &input3, &input4 };
  213.  
  214. // 4. Submenu node
  215. MenuNode inputConfig = { "Input Config", nullptr, nullptr, inputItems, 4 };
  216.  
  217. // 5. Other leaf nodes
  218. MenuNode viewStatus = { "View Status", showStatus, nullptr, nullptr, 0 };
  219. MenuNode saveSettingsItem = { "Save Settings", saveSettings, nullptr, nullptr, 0 };
  220.  
  221. // 6. Root menu items
  222. MenuNode* rootItems[] = { &viewStatus, &inputConfig, &saveSettingsItem };
  223.  
  224. // 7. Root menu node
  225. MenuNode root = { "Main Menu", nullptr, nullptr, rootItems, 3 };
  226.  
  227. void handleEncoder() {
  228. static unsigned long lastButtonTime = 0;
  229. static bool buttonHandled = false;
  230.  
  231. noInterrupts();
  232. int8_t delta = encoderDelta;
  233. encoderDelta = 0;
  234. interrupts();
  235.  
  236. // === Relay Map Editing Mode ===
  237. if (inEditMode) {
  238. if (delta != 0) {
  239. editingRelayIndex = (editingRelayIndex + delta + 5) % 5;
  240. renderRelayMapEditor();
  241. }
  242.  
  243. if (digitalRead(ENCODER_BUTTON) == LOW) {
  244. if (!buttonHandled && millis() - lastButtonTime > 200) {
  245. buttonHandled = true;
  246. lastButtonTime = millis();
  247.  
  248. if (editingRelayIndex == 4) {
  249. // Back selected
  250. inEditMode = false;
  251. currentMenu = &inputConfig;
  252. currentIndex = editingInputIndex; // Return to same input in menu
  253. renderMenu();
  254. } else {
  255. // Toggle the selected relay
  256. inputRelayMap[editingInputIndex][editingRelayIndex] =
  257. !inputRelayMap[editingInputIndex][editingRelayIndex];
  258. renderRelayMapEditor();
  259. }
  260. }
  261. } else {
  262. buttonHandled = false;
  263. }
  264.  
  265. return; // Skip normal menu logic while editing
  266. }
  267.  
  268. // === Standard Menu Navigation ===
  269. if (delta != 0) {
  270. uint8_t itemCount = currentMenu->childCount + (currentMenu->parent ? 1 : 0);
  271. currentIndex = (currentIndex + delta + itemCount) % itemCount;
  272. renderMenu();
  273. }
  274.  
  275. if (digitalRead(ENCODER_BUTTON) == LOW) {
  276. if (!buttonHandled && millis() - lastButtonTime > 200) {
  277. buttonHandled = true;
  278. lastButtonTime = millis();
  279.  
  280. uint8_t itemCount = currentMenu->childCount + (currentMenu->parent ? 1 : 0);
  281. if (currentIndex == itemCount - 1 && currentMenu->parent != nullptr) {
  282. goBack();
  283. } else {
  284. MenuNode* selected = currentMenu->children[currentIndex];
  285. if (selected->childCount > 0) {
  286. enterMenu(selected);
  287. } else if (selected->action) {
  288. selected->action();
  289. renderMenu();
  290. }
  291. }
  292. }
  293. } else {
  294. buttonHandled = false;
  295. }
  296. }
  297.  
  298.  
  299. void showStatus() {
  300. Serial.println("Status screen");
  301. }
  302.  
  303. void saveSettings() {
  304. Serial.println("Saving settings to EEPROM...");
  305. saveInputRelayMapToEEPROM();
  306. Serial.println("Settings saved.");
  307. }
  308.  
  309.  
  310.  
  311. void setupMenuParents() {
  312. for (uint8_t i = 0; i < inputConfig.childCount; i++) {
  313. inputConfig.children[i]->parent = &inputConfig;
  314. }
  315.  
  316. inputConfig.parent = &root;
  317.  
  318. root.children[1]->parent = &root;
  319. root.children[1]->children = inputItems;
  320. root.children[1]->childCount = 4;
  321. }
  322.  
  323.  
  324. void setup() {
  325. Serial.begin(9600);
  326.  
  327. for (uint8_t i = 0; i < 4; i++) {
  328. pinMode(INPUT_PINS[i], INPUT_PULLUP);
  329. pinMode(RELAY_PINS[i], OUTPUT);
  330. digitalWrite(RELAY_PINS[i], LOW);
  331. }
  332.  
  333. pinMode(ENCODER_PIN_A, INPUT_PULLUP);
  334. pinMode(ENCODER_PIN_B, INPUT_PULLUP);
  335. pinMode(ENCODER_BUTTON, INPUT);
  336.  
  337. if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
  338. Serial.println(F("OLED init failed"));
  339. while (true);
  340. }
  341.  
  342. display.clearDisplay();
  343. display.setTextSize(1);
  344. display.setTextColor(SSD1306_WHITE);
  345. display.setCursor(0, 0);
  346. display.println(F("System Booting..."));
  347. display.display();
  348. delay(1000);
  349.  
  350. setupMenuParents();
  351.  
  352. // **Load EEPROM settings here:**
  353. loadInputRelayMapFromEEPROM();
  354.  
  355. currentMenu = &root;
  356. currentIndex = 0;
  357. menuDepth = 0;
  358.  
  359. renderMenu();
  360.  
  361. lastEncoderState = (digitalRead(ENCODER_PIN_A) << 1) | digitalRead(ENCODER_PIN_B);
  362. attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), encoderISR, CHANGE);
  363. attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), encoderISR, CHANGE);
  364. }
  365.  
  366. void loop() {
  367. handleEncoder();
  368. }
  369.  
Advertisement
Add Comment
Please, Sign In to add comment