API Documentation v0.0.1
Loading...
Searching...
No Matches
Board.h
1/**********************************************************************************************************************
2 * ____ _ _ _
3 * / __ \ | | | | | |
4 * | | | |_ __ ___ _ __ | |__| | ___ _ __ _ __ ___| |_
5 * | | | | '_ \ / _ \ '_ \| __ |/ _ \| '__| '_ \ / _ \ __|
6 * | |__| | |_) | __/ | | | | | | (_) | | | | | | __/ |_
7 * \____/| .__/ \___|_| |_|_| |_|\___/|_| |_| |_|\___|\__|
8 * | |
9 * |_|
10 * ----------------------------------------------------------------------------------
11 *
12 * @file Board.h
13 * @author Ulukaii
14 * @date 08.Nov 2025
15 * @version t 0.3.5
16 * @copyright Copyright 2016-2025 OpenHornet. See 2A13-BACKLIGHT_CONTROLLER.ino for details.
17 * @brief The board class is responsible for the physical input/output: catch rotary encoder commands, update LEDs.
18 * @details During setup, a singleton board object is created. It manages the physical update of the LEDs centrally.
19 * It is the only place from which the expensive physical update (FastLED.show() function) is called.
20 * Additionally, it provides the logic to catch rotary encoder commands to cycle between three modes:
21 * - Normal mode 1 (DCS-BIOS controlled)
22 * - Manual mode 2(control backlights with rotary encoder)
23 * - Rainbow test mode 3
24 *********************************************************************************************************************/
25
26
27
28#ifndef __BOARD_H
29#define __BOARD_H
30
31#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega2560__)
32 #define DCSBIOS_IRQ_SERIAL
33#else
34 #define DCSBIOS_DEFAULT_SERIAL
35#endif
36
37#include "FastLED.h"
38#include "Channel.h"
39#include "DcsBios.h"
40#include "Colors.h"
41#include "LedUpdateState.h"
42#include "RotaryEncoder.h"
43#include "DCS_State_Checker.h"
44
45class Board {
46
47private:
48
49 int updCountdown; //DCS Bios cycle countdown before invoking FastLED.show()
50 static const int MAX_CHANNELS = 10; // Maximum number of channels
51 static const int MODE_NORMAL = 1; // Normal DCS-BIOS controlled mode
52 static const int MODE_MANUAL = 2; // Manual mode - control backlts with rotary encoder
53 static const int MODE_RAINBOW = 3; // Rainbow test mode
54 Channel* channels[MAX_CHANNELS]; // Array of channel pointers
55 int channelCount; // Current number of channels
56 int thisHue; // Current hue value for rainbow effect
57 int deltaHue; // Hue change between LEDs for rainbow effect
58 int currentMode; // Current operating mode
59 int mode2_brightness; // Current brightness level (0-255), for manual mode 2
60 int mode3_brightness; // Brightness level (0-255) for rainbow mode 3
61 int dcs_brightness_console; // Current brightness level (0-65535), for DCS-BIOS controlled mode
62 int dcs_brightness_instrument; // Current brightness level (0-65535), for DCS-BIOS controlled mode
63 int dcs_brightness_flood; // Current brightness level (0-65535), for DCS-BIOS controlled mode
64 int encSwPin; // Encoder switch pin
65 RotaryEncoder* encoder; // Pointer to encoder instance
66 int rotary_pos; // Current rotary encoder position
67 static Board* instance; // Static instance pointer to the Board class
68 DcsState prevDcsState = DcsState::EXITED; // Previous DCS state for transition detection
69
74 Board() {
75 updCountdown = 0; // Initialize with 0
76 channelCount = 0; // Initialize with 0 channels
77 thisHue = 0; // Initialize with 0
78 deltaHue = 3; // Initialize with 3
79 currentMode = MODE_NORMAL; // Initialize to normal mode 1
80 mode2_brightness = 64; // Initialize manual mode 2 brightness to 25%
81 mode3_brightness = 64; // Initialize rainbow mode 3 brightness to 25%
82 dcs_brightness_console = 0; // Initialize DCS brightness to 0
83 dcs_brightness_instrument = 0; // Initialize DCS brightness to 0
84 dcs_brightness_flood = 0; // Initialize DCS brightness to 0
85 rotary_pos = 0; // Initialize with 0
86 encoder = nullptr; // Initialize with nullptr
87 }
88
89
90public:
91
96 static Board* getInstance() {
97 return instance ? instance : (instance = new Board()); }
98
106 void setupRotaryEncoder(int encSwPin, int encAPin, int encBPin) {
107 this->encSwPin = encSwPin;
108 pinMode(encSwPin, INPUT_PULLUP); // Initialize mode change pin
109 encoder = new RotaryEncoder(encAPin, encBPin, RotaryEncoder::LatchMode::TWO03);
110 }
111
117 void registerChannel(Channel* channel) {
118 channels[channelCount++] = channel;
119 }
120
125 void updateLeds() {
126 if (LedUpdateState::getInstance()->getUpdateFlag()) {
127 updCountdown = (updCountdown == 0) ? 32 : updCountdown; // Countdown logic allows to collect LED updates
128 updCountdown--; // from 32 loop() calls into one FastLED.show()
129 if (updCountdown == 0) { // Trigger FastLED.show() at end of countdown
130 cli();
131 FastLED.show();
132 LedUpdateState::getInstance()->setUpdateFlag(false); // Reset update flag
133 sei();
134 }
135 }
136 }
137
138
145 static bool lastButtonState = HIGH;
146 static unsigned long lastButtonPressTime = 0;
147 const unsigned long BUTTON_WAIT = 1000; // Wait time in milliseconds between button presses
148
149 bool currentButtonState = digitalRead(encSwPin); // Read the state of the encoder switch
150 unsigned long currentTime = millis(); // Get current time in milliseconds
151
152 if (currentButtonState == LOW && lastButtonState == HIGH) { // Button has just been pressed
153 if (currentTime - lastButtonPressTime < BUTTON_WAIT) { // Only process if 1 sec passed since last press
154 lastButtonState = currentButtonState;
155 return currentMode;
156 }
157 lastButtonPressTime = currentTime; // Update last press time
158 int previousMode = currentMode; // Store previous mode
159 currentMode = (currentMode % 3) + 1; // Cycle to next mode
160
161 if (currentMode == MODE_NORMAL) {
163 sendDcsBiosMessage("CONSOLES_DIMMER", String(dcs_brightness_console).c_str()); // Send DCS-BIOS message to reset console dimmer
164 sendDcsBiosMessage("INST_PNL_DIMMER", String(dcs_brightness_instrument).c_str()); // Send DCS-BIOS message to reset instrument lighting
165 sendDcsBiosMessage("FLOOD_DIMMER", String(dcs_brightness_flood).c_str()); // Send DCS-BIOS message to reset floodlights dimmer
166 }
167 if (currentMode == MODE_MANUAL) {
168 mode2_brightness = 64; // Reset to 25% brightness
169 fillSolid(NVIS_GREEN_A); // Apply the brightness immediately
170 }
171 if (currentMode == MODE_RAINBOW) {
172 mode3_brightness = 64; // Reset to 25% brightness
173 encoder->tick(); // Update encoder state
174 rotary_pos = encoder->getPosition(); // Sync encoder position to avoid false change detection
175 setAllLightsOff(); // Clear any previous state
176 }
177
178 lastButtonState = currentButtonState;
179 delay(10); // Small delay to debounce switch
180 } else {
181 lastButtonState = currentButtonState;
182 }
183 return currentMode;
184 }
185
190 void processMode() {
191 int newPos = 0;
192 switch(currentMode) {
193 case MODE_NORMAL: // MODE 1: LEDs controlled by DCS BIOS
194 {
195 DcsState currentDcsState = getDcsState();
196 if (currentDcsState == DcsState::EXITED && prevDcsState != DcsState::EXITED) {
197 setAllLightsOff(); // DCS just exited: turn off all lights
198 } else if (prevDcsState == DcsState::EXITED &&
199 currentDcsState != DcsState::EXITED &&
200 currentDcsState != DcsState::PAUSED) {
201 for (int i = 0; i < channelCount; i++) { // DCS became active again: restore last known brightness
202 channels[i]->updateInstrLights(dcs_brightness_instrument);
203 channels[i]->updateConsoleLights(dcs_brightness_console);
204 channels[i]->updateFloodLights(dcs_brightness_flood);
205 }
207 }
208 // PAUSED: do nothing - keep current light state
209 prevDcsState = currentDcsState;
210 DcsBios::loop();
211 }
212 break;
213 case MODE_MANUAL: // MODE 2: LEDs controlled manually through BKLT switch
214 encoder->tick();
215 newPos = encoder->getPosition();
216 if (newPos != rotary_pos) {
217 RotaryEncoder::Direction direction = encoder->getDirection();
218 if (direction == RotaryEncoder::Direction::CLOCKWISE) {
219 mode2_brightness = (mode2_brightness < 224) ? mode2_brightness + 32 : 255; // Add 32 or cap at 255
220 } else {
221 mode2_brightness = (mode2_brightness > 32) ? mode2_brightness - 32 : 0; // Subtract 32 or cap at 0
222 }
223 rotary_pos = newPos;
224 fillSolid(NVIS_GREEN_A);
225 }
226 break;
227 case MODE_RAINBOW: // MODE 3: Rainbow test mode
228 encoder->tick();
229 newPos = encoder->getPosition();
230 if (newPos != rotary_pos) {
231 RotaryEncoder::Direction direction = encoder->getDirection();
232 if (direction == RotaryEncoder::Direction::CLOCKWISE) {
233 mode3_brightness = (mode3_brightness < 224) ? mode3_brightness + 32 : 255; // Add 32 or cap at 255
234 } else {
235 mode3_brightness = (mode3_brightness > 32) ? mode3_brightness - 32 : 0; // Subtract 32 or cap at 0
236 }
237 rotary_pos = newPos;
238 }
239 for (int i = 0; i < channelCount; i++) {
240 fill_rainbow(channels[i]->getLeds(), channels[i]->getLedCount(), thisHue, deltaHue);
241 // Scale down brightness to reduce maximum brightness
242 nscale8_video(channels[i]->getLeds(), channels[i]->getLedCount(), mode3_brightness);
243 }
244 thisHue++; // Increment the hue for the next frame
246 break;
247 }
248 }
249
250
257 void fillSolid(const CRGB& color, int brightness = -1) { // Fill all channels with a solid color
258 int targetBrightness = (brightness >= 0) ? brightness : this->mode2_brightness;
259 for (int i = 0; i < channelCount; i++) {
260 channels[i]->updateInstrLights(map(targetBrightness, 0, 255, 0, 65535), color);
261 channels[i]->updateConsoleLights(map(targetBrightness, 0, 255, 0, 65535), color);
262 }
264 }
265
270 void setAllLightsOff() { // Turn off all lights and reset brightness state
271 for (int i = 0; i < channelCount; i++) {
272 channels[i]->setAllLightsOff();
273 }
275 }
276
282 void updateInstrumentLights(uint16_t newValue) {
283 dcs_brightness_instrument = newValue; // In any mode, store the DCS-BIOS brightness value
284 if (currentMode != MODE_NORMAL) return; // But only in normal mode, actually send update to channels
285 for (int i = 0; i < channelCount; i++) {
286 channels[i]->updateInstrLights(newValue);
287 }
289
290 }
291
297 void updateConsoleLights(uint16_t newValue) {
298 dcs_brightness_console = newValue; // In any mode, store the DCS-BIOS brightness value
299 if (currentMode != MODE_NORMAL) return; // But only in normal mode, actually send update to channels
300 for (int i = 0; i < channelCount; i++) {
301 channels[i]->updateConsoleLights(newValue);
302 }
304
305 }
306
312 void updateFloodLights(uint16_t newValue) {
313 dcs_brightness_flood = newValue; // In any mode, store the DCS-BIOS brightness value
314 if (currentMode != MODE_NORMAL) return; // But only in normal mode, actually send update to channels
315 for (int i = 0; i < channelCount; i++) {
316 channels[i]->updateFloodLights(newValue);
317 }
319
320 }
321
322
328 static void onInstrIntLtChange(unsigned int newValue) {
329 if (instance) instance->updateInstrumentLights(newValue);
330 }
331 DcsBios::IntegerBuffer instrIntLtBuffer{FA_18C_hornet_INSTR_INT_LT, onInstrIntLtChange};
332
338 static void onConsolesDimmerChange(unsigned int newValue) {
339 if (instance) instance->updateConsoleLights(newValue);
340 }
341 DcsBios::IntegerBuffer consolesDimmerBuffer{FA_18C_hornet_CONSOLES_DIMMER, onConsolesDimmerChange};
342
348 static void onFloodDimmerChange(unsigned int newValue) {
349 if (instance) instance->updateFloodLights(newValue);
350 }
351 DcsBios::IntegerBuffer floodDimmerBuffer{FA_18C_hornet_FLOOD_DIMMER, onFloodDimmerChange};
352
353};
354
355
356
357
358// Initialize static instance pointer
359Board* Board::instance = nullptr;
360
361#endif
362
363
364
365
366
367 // TODO: the following lines contain code snippets for the future PREFLT mode that is not yet implemented
368
369 // static void onAcftNameChange(char* newValue) {
370 // if (!strcmp(newValue, "FA-18C_hornet")) {
371 // //cl_F18C.MakeCurrent();
372 // }
373 //}
374
375 // DCS-BIOS callbacks and buffers
376 //static void onAcftNameChange(char* newValue) {
377 // if (!strcmp(newValue, "FA-18C_hornet")) {
378 // //cl_F18C.MakeCurrent();
379 // }
380 //}
381 //DcsBios::StringBuffer<16> AcftNameBuffer{0x0000, onAcftNameChange};
382
383 /*
384 // This is the recommended approach and the ideal if we can work out all the kinks:
385 // If the mission time changes backwards, we have entered a new aircraft; Resync everything
386
387 unsigned long uLastModelTimeS = 0xFFFFFFFF; // Start big, to ensure the first step triggers a resync
388
389 void onModTimeChange(char* newValue) {
390 unsigned long currentModelTimeS = atol(newValue);
391
392 if( currentModelTimeS < uLastModelTimeS )
393 {
394 if( currentModelTimeS > 20 )// Delay to give time for DCS to finish loading and become stable and responsive
395 {
396 DcsBios::resetAllStates();
397 uLastModelTimeS = currentModelTimeS;
398 }
399 }
400 else
401 {
402 uLastModelTimeS = currentModelTimeS;
403 }
404 }
405 DcsBios::StringBuffer<5> modTimeBuffer(0x043e, onModTimeChange);
406
407 // Also we can check on weight on wheels change:
408 void onExtWowLeftChange(unsigned int newValue) {
409 // your code here
410 }
411 DcsBios::IntegerBuffer extWowLeftBuffer(FA_18C_hornet_EXT_WOW_LEFT, onExtWowLeftChange);
412
413 */
DCS State Monitor for OpenHornet.
DcsState getDcsState()
Determine the current DCS connection state (heartbeat-only variant)
Definition Board.h:45
static void onConsolesDimmerChange(unsigned int newValue)
Callback for console dimmer changes from DCS-BIOS.
Definition Board.h:338
static void onFloodDimmerChange(unsigned int newValue)
Callback for flood lighting changes from DCS-BIOS.
Definition Board.h:348
void setupRotaryEncoder(int encSwPin, int encAPin, int encBPin)
Sets up the rotary encoder with switch and encoder pins.
Definition Board.h:106
static Board * getInstance()
Gets or createsthe singleton instance of the Board class.
Definition Board.h:96
int handleModeChange()
Handles mode change button press and returns current mode.
Definition Board.h:144
static void onInstrIntLtChange(unsigned int newValue)
Callback for instrument lighting changes from DCS-BIOS.
Definition Board.h:328
void setAllLightsOff()
Turns off all lights in all channels and resets brightness state.
Definition Board.h:270
void updateConsoleLights(uint16_t newValue)
Updates all channels with new console lighting value.
Definition Board.h:297
void registerChannel(Channel *channel)
Registers a channel with the board.
Definition Board.h:117
void updateLeds()
Update the physical LED state.
Definition Board.h:125
void updateInstrumentLights(uint16_t newValue)
Updates all channels with new instrument lighting value.
Definition Board.h:282
void updateFloodLights(uint16_t newValue)
Updates all channels with new flood lighting value.
Definition Board.h:312
void processMode()
Processes the current mode.
Definition Board.h:190
void fillSolid(const CRGB &color, int brightness=-1)
Fills all channels with a solid color.
Definition Board.h:257
void updateInstrLights(uint16_t brightness, const CRGB &color=NVIS_GREEN_A)
Updates backlights for all panels in this channel.
Definition Channel.h:166
void updateConsoleLights(uint16_t brightness, const CRGB &color=NVIS_GREEN_A)
Updates console lights for all panels in this channel.
Definition Channel.h:180
void updateFloodLights(uint16_t brightness)
Updates flood lights for all panels in this channel.
Definition Channel.h:193
void setAllLightsOff()
Turns off all lights in all panels of this channel and resets brightness state.
Definition Channel.h:205
void setUpdateFlag(bool requireUpdate)
Sets the LED update flag in an atomic operation.
static LedUpdateState * getInstance()
Gets the singleton instance of the LedUpdateState class.