/**
* \file HCS.ino
* \brief main file for Handheld Controller Station
* \author Uav4africa Pty Ltd
* \date December 2023
*/
//#include "HCS_Sim.h"
#include "HCS_functions.h"
//#include "HCS_OLED.h"
//#include "HCS_mission_control.h"
//#include "HCS_manual_control.h"
// global variables
RF_struct_comms RF_data; ///< RF data packet
selector_struct selector; ///< selector object
float current_time, last_time;
//-------------------- START OF HCS_encoder.cpp----------------------------------
/// global variables
bool button = false; //select
uint8_t buttonPin=25;
uint8_t pinA=35; //clk
uint8_t pinB=34; //dt
uint8_t pinAcurrentState;
uint8_t pinAlastState;
uint8_t pinBcurrentState;
uint8_t pinBlastState;
bool press_hold = false;
uint8_t hold_counter = 0;
bool CW_turn_request = false;
bool CCW_turn_request = false;
menu_struct PageMenu[5]; ///< Front Page menu
int8_t display_cursor = 0; ///< cursor to be changed with encoder selector
uint8_t current_page = 0; ///< Current page selected
bool custom_page_enable = false; ///< enable the display of custom page
uint8_t custom_page_index = 0; ///< index allocated to a custom page
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void initEncoder(selector_struct *selector)
{
// configure pins on rotary encoder
pinMode(buttonPin,INPUT_PULLUP);
pinMode(pinA, INPUT);
pinMode(pinB, INPUT);
//attachInterrupt(digitalPinToInterrupt(pinA), rotaryEncoderCheck, CHANGE); //the interrupt is triggered when there's a change at B
pinAlastState = digitalRead(pinA);
pinBlastState = digitalRead(pinB);
pinAcurrentState = pinAlastState;
pinBcurrentState = pinBlastState;
selector->CW_turn = false;
selector->CCW_turn = false;
selector->button_press = false;
selector->updated = false;
}
void exeEncoder(selector_struct *selector)
{
if (selector->updated == false)
{
rotaryEncoderCheck(selector);
buttonCheck(selector);
}
else
{
// re-initialize the encoder readings
pinAcurrentState = digitalRead(pinA);
pinBcurrentState = digitalRead(pinB);
pinAlastState = pinAcurrentState;
pinBlastState = pinBcurrentState;
}
}
void rotaryEncoderCheck(selector_struct *selector)
{
//checking rotation direction
pinAcurrentState = digitalRead(pinA);
pinBcurrentState = digitalRead(pinB);
// If last and current state of pinA are different, then pulse occurred
// React to only 1 state change to avoid double count
if((pinAlastState != pinAcurrentState) && (pinBcurrentState==pinBlastState))
{
if (pinAcurrentState != pinBcurrentState)
{
selector->CW_turn = true; //CW rotation
selector->CCW_turn = false;
}
else if (pinAcurrentState == pinBcurrentState)
{
selector->CCW_turn = true;
selector->CW_turn = false; //CCW rotation
}
// confirm turn trigger has occurred
if ((selector->CW_turn) || (selector->CCW_turn))
{
selector->updated = true;
}
}
else
{
selector->CW_turn = false;
selector->CCW_turn = false;
}
pinAlastState = pinAcurrentState;
pinBlastState = pinBcurrentState;
// Put in a slight delay to help debounce the reading
// delay(1);
}
/// check button for single press - ignore press and hold
void buttonCheck(selector_struct *selector)
{
if((digitalRead(buttonPin) == LOW) && (!selector->button_press) && !press_hold)
{
selector->button_press = true;
selector->updated = true;
delay(1); // to prevent bouncing
}
else if ((digitalRead(buttonPin) == LOW) && (selector->button_press))
{
selector->button_press = false;
press_hold = true;
}
// hold counter to long button press
if (press_hold)
{
hold_counter ++;
}
// set long press once threshold has been reached
if (hold_counter > 100) // equivalent to 2 sec for 50 Hz refresh rate
{
selector->button_long_press = true;
selector->updated = true;
}
// reset
if (digitalRead(buttonPin) == HIGH)
{
hold_counter = 0;
press_hold = false;
selector->button_press = false;
selector->button_long_press = false;
}
}
//---------------------END OF HCS_encoder.h ------------------------------------
//--------------------- START OF HCS_OLED.h ------------------------------------
void initDisplay(RF_struct_comms *RF_data_comms)
{
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
{
Serial.println("SSD1306 initialization failed!");
while (true); // Halt execution if the display fails to initialize
}
//getRFdata_OLED_display(RF_data_comms);
definePages(); // set the pages structure
drawHeader();
print_display();
}
void exeDisplay(selector_struct *selector)
{
drawHeader();
drawMenu(selector);
print_display();
}
void drawHeader(void)
{
display.setTextSize(1);
display.clearDisplay();
display.setTextColor(WHITE, BLACK);
display.setCursor(5, 0);
display.print("LOCUS - HCS v1.0.1");
display.drawFastHLine(0, 10, 120, WHITE);
}
void print_display(void)
{
display.display();
}
void definePages(void)
{
// Front page
PageMenu[0].numOfentries = 3;
PageMenu[0].parent_page = -1; // exception
strcpy(PageMenu[0].menulist[0], "Mission Control");
strcpy(PageMenu[0].menulist[1], "Remote Control");
strcpy(PageMenu[0].menulist[2], "Settings");
strcpy(PageMenu[0].menu_name, "Home");
// Mission Control page
PageMenu[1].numOfentries = 5;
PageMenu[1].parent_page = 0; // top level page
strcpy(PageMenu[1].menulist[0], "...");
strcpy(PageMenu[1].menulist[1], "Create/Edit Mission");
strcpy(PageMenu[1].menulist[2], "Save/Delete Mission");
strcpy(PageMenu[1].menulist[3], "Start Mission");
strcpy(PageMenu[1].menulist[4], "View Mission");
strcpy(PageMenu[1].menu_name, "Mission Control");
// Remote Control page
PageMenu[2].numOfentries = 5;
PageMenu[2].parent_page = 0; // top level page
strcpy(PageMenu[2].menulist[0], "...");
strcpy(PageMenu[2].menulist[1], "Range Check"); // not sure how this is done
strcpy(PageMenu[2].menulist[2], "ALT/HDG Control");
strcpy(PageMenu[2].menulist[3], "Flare Control");
strcpy(PageMenu[2].menulist[4], "Return To Base");
strcpy(PageMenu[2].menu_name, "Remote Control");
// Settings page
PageMenu[3].numOfentries = 5;
PageMenu[3].parent_page = 0; // top level page
strcpy(PageMenu[3].menulist[0], "...");
strcpy(PageMenu[3].menulist[1], "WiFi Settings"); // include GPS location and
strcpy(PageMenu[3].menulist[2], "Pre-flight checks"); // include GPS location and
strcpy(PageMenu[3].menulist[3], "Display");
strcpy(PageMenu[3].menulist[4], "About");
strcpy(PageMenu[3].menu_name, "Settings");
// WiFi Settings page
PageMenu[4].numOfentries = 4;
PageMenu[4].parent_page = 3; // top level page
strcpy(PageMenu[4].menulist[0], "...");
strcpy(PageMenu[4].menulist[1], "Scan Networks"); // include GPS location and
strcpy(PageMenu[4].menulist[2], "Connect Network"); // include GPS location and
strcpy(PageMenu[4].menulist[3], "Network Info");
strcpy(PageMenu[4].menu_name, "WiFi Settings");
// Turn-On WiFi page
// Scan Networks page
PageMenu[5].numOfentries = 4; // Simulated networks
PageMenu[5].parent_page = 4; // WiFi Settings is the parent
strcpy(PageMenu[5].menulist[0], "...");
strcpy(PageMenu[5].menulist[1], "Network 1");
strcpy(PageMenu[5].menulist[2], "Network 2");
strcpy(PageMenu[5].menulist[3], "Network 3");
strcpy(PageMenu[5].menu_name, "Scan Results");
// Network Info page
}
void drawMenu(selector_struct *selector)
{
int i;
int x_pos = 15; // print cursor position below display Header
bool selected = false;
updateCursor(selector);
// change the page to display_cursor if the button is pressed
if (selector->button_press)
{
if (strcmp(PageMenu[current_page].menu_name, "Home") == 0) // By default the Home menu has submenus
{
current_page = (uint8_t)display_cursor + 1; // change current page based on layout of front page - offset by one row
}
else if (display_cursor == 0) // button pressed when cursor is on Return to parent page "..."
{
current_page = PageMenu[current_page].parent_page;
}
else if (strcmp(PageMenu[current_page].menu_name, "Settings") == 0 && display_cursor == 1) // WiFi Settings
{
current_page = 4; // Navigate to WiFi Settings page (index 4 in PageMenu)
}
display_cursor = 0; // reset cursor once current_page has changed
}
// define the position and background of the selected menu
for (i = 0; i < PageMenu[current_page].numOfentries; i++)
{
if (display_cursor == i)
{
selected = true;
}
else
{
selected = false;
}
displayMenuItem(PageMenu[current_page].menulist[i], x_pos, selected);
x_pos += 10;
}
}
void displayMenuItem(char *item, uint8_t position, bool selected)
{
int offset = 0;
if (selected)
{
display.setTextColor(BLACK, WHITE);
}
else
{
display.setTextColor(WHITE, BLACK);
}
display.setCursor(0, position);
while (*(item + offset) != '\0')
{
display.print(*(item + offset));
++offset;
}
}
void updateCursor(selector_struct *selector)
{
// only update display cursor if turn trigger is HIGH
if (selector->CW_turn && selector->updated)
{
display_cursor += (uint8_t)selector->CW_turn;
if (display_cursor > PageMenu[current_page].numOfentries - 1)
{
display_cursor = 0;
} // reset
}
else if (selector->CCW_turn && selector->updated)
{
display_cursor -= 1;
if (display_cursor < 0)
{
display_cursor = PageMenu[current_page].numOfentries - 1;
} // reset
}
}
//--------------------- END OF HCS_OLED.h ------------------------------------
void setup() {
initEncoder(&selector);
initDisplay(&RF_data);
Serial.begin(115200);
last_time = 0;
}
void loop () {
current_time = millis()/1000.0f;
RF_data.var1_uint8 = 0;
exeEncoder(&selector);
if ((current_time - last_time) > 0.5f)
{
exeDisplay(&selector);
last_time = current_time;
// request a new update of selector
selector.updated = false;
}
}