/**
* \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"
#include <WiFi.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)
{
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
//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 = 5;
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].menulist[4], "Keyboard");
strcpy(PageMenu[4].menu_name, "WiFi Settings");
//Keyboard Page
PageMenu[5].numOfentries = 4; // Number of entries on the Keyboard page (e.g., for character sets or actions)
PageMenu[5].parent_page = 4; // Parent page, assuming this page is accessed from page 3 (you can adjust this based on your page structure)
strcpy(PageMenu[5].menulist[0], "Enter Text"); // Entry to allow text input
strcpy(PageMenu[5].menulist[1], "Special Characters"); // Entry for special characters or advanced input
strcpy(PageMenu[5].menulist[2], "Backspace"); // Entry for backspace action
strcpy(PageMenu[5].menulist[3], "Submit"); // Entry for submitting the input
strcpy(PageMenu[5].menu_name, "Keyboard");
// Scan Networks page
// Network Info page
}
//---------------------------START SCAN NETWORK---------------------------------
//---------------------------END SCAN NETWORK---------------------------------
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;
}
// Turn-On WiFi 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)
}
// Add transition to Keyboard page when "Keyboard" is selected in WiFi Settings
else if (strcmp(PageMenu[current_page].menu_name, "WiFi Settings") == 0 && display_cursor == 4) // "Keyboard" option
{
current_page = 5; // Navigate to the Keyboard page (index 5 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;
}
}
//--------------------------START OF KEYBOARD----------------------------
// //Function to handle the Keyboard page
//void keyboardPage() {
// // Redirect to password input for testing
// inputPasswordPage();
//}
// //Function for password input
//void inputPasswordPage() {
// // Logic for displaying and interacting with the keyboard
// // This will be implemented with the ASCII-based array logic
//} // Note: Ensure rotary encoder is integrated for navigation
// Keyboard Array Logic Example
//const char keyboardBuckets[4][10] = {
// Numbers 0-9
// {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'},
// Capital Letters A-Z
//{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'},
// Small Letters a-z
//{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'},
// Special Characters
//{'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+'}
//};
// Logic for rotary encoder to navigate buckets and characters
//void navigateKeyboard() {
// int bucketIndex = 0; // Start with numbers
//int charIndex = 0; // Start with the first character
// Handle scrolling to navigate within a bucket
//if (scrollDirection == CLOCKWISE) {
// charIndex = (charIndex + 1) % (sizeof(keyboardBuckets[bucketIndex]) / sizeof(keyboardBuckets[bucketIndex][0]));
//} else if (scrollDirection == COUNTERCLOCKWISE) {
// charIndex = (charIndex - 1 + (sizeof(keyboardBuckets[bucketIndex]) / sizeof(keyboardBuckets[bucketIndex][0]))) % (sizeof(keyboardBuckets[bucketIndex]) / sizeof(keyboardBuckets[bucketIndex][0]));
//}
// Handle long press to switch between buckets
//if (longPressDetected()) {
// bucketIndex = (bucketIndex + 1) % 4; // Move to the next bucket
//charIndex = 0; // Reset character index for the new bucket
//}
// Display the current character
// displayCharacter(keyboardBuckets[bucketIndex][charIndex]);
//}
// Function to display the selected character
//void displayCharacter(char character) {
// Logic to show the character on the screen
// Serial.print("Selected character: ");
//Serial.println(character);
//}
//--------------------------END OF KEYBOARD----------------------------