diff --git a/watering_system_mini_project/MenuItem.cpp b/watering_system_mini_project/MenuItem.cpp index 8660490..62e94d0 100644 --- a/watering_system_mini_project/MenuItem.cpp +++ b/watering_system_mini_project/MenuItem.cpp @@ -23,10 +23,19 @@ return NULL; } -String MenuItem::display() { - return "t " + String(MenuItem::name); +void MenuItem::selectLeft() { } +void MenuItem::selectRight() { +} + +bool MenuItem::select() { // returns wether it makes sense to 'select' this item + return false; +} + +String MenuItem::display() { + return MenuItem::name; +} BranchMenuItem::BranchMenuItem(const char* name, const MenuItem* child) { BranchMenuItem::name = name; @@ -42,30 +51,48 @@ } -IntValueItem::IntValueItem(char const* name, int initial) { +IntValueItem::IntValueItem(char const* name, int initial, int min, int max) { IntValueItem::name = name; IntValueItem::value = initial; + IntValueItem::min = min; + IntValueItem::max = max; + } -MenuItem* IntValueItem::goRight() { // increment - IntValueItem::value++; +void IntValueItem::selectRight() { // increment + if (IntValueItem::value < IntValueItem::max){ + IntValueItem::value++; + } +} +void IntValueItem::selectLeft() { // increment + if (IntValueItem::value > IntValueItem::min){ + IntValueItem::value--; + } } String IntValueItem::display() { - return String(IntValueItem::name) + ": " + String(IntValueItem::value); + return String(IntValueItem::name) + "\n" + String(IntValueItem::value); } - +bool IntValueItem::select() { + return true; +} BoolValueItem::BoolValueItem(char const* name, bool initial) { BoolValueItem::name = name; BoolValueItem::value = initial; } -MenuItem* BoolValueItem::goRight() { // toggle +void BoolValueItem::selectRight() { // toggle BoolValueItem::value = !BoolValueItem::value; - return NULL; +} + +void BoolValueItem::selectLeft() { // toggle + BoolValueItem::value = !BoolValueItem::value; } String BoolValueItem::display() { - return String(BoolValueItem::name) + ": " + (BoolValueItem::value ? "true" : "false"); + return String(BoolValueItem::name) + "\n" + (BoolValueItem::value ? "true" : "false"); +} +bool BoolValueItem::select() { + return true; } \ No newline at end of file diff --git a/watering_system_mini_project/MenuItem.h b/watering_system_mini_project/MenuItem.h index 1effc80..dac800b 100644 --- a/watering_system_mini_project/MenuItem.h +++ b/watering_system_mini_project/MenuItem.h @@ -8,10 +8,14 @@ MenuItem(); MenuItem(const char* name); const char* name; + MenuItem* parent; MenuItem* next; MenuItem* goBack(); MenuItem* goDown(); + virtual void selectLeft(); + virtual void selectRight(); + virtual bool select(); virtual MenuItem* goRight(); virtual String display(); }; @@ -20,15 +24,22 @@ public: BranchMenuItem(const char* name, const MenuItem* child); MenuItem* child; - MenuItem* goRight() override; - String display() override; + MenuItem* goRight(); + String display(); }; class IntValueItem: public MenuItem { public: - IntValueItem(const char* name, const int initialValue); + IntValueItem(const char* name, const int initialValue, const int min, const int max); int value; - MenuItem* IntValueItem::goRight(); + int min; + int max; + + bool IntValueItem::select(); + + void IntValueItem::selectLeft(); + void IntValueItem::selectRight(); + String IntValueItem::display(); }; @@ -36,8 +47,12 @@ public: BoolValueItem(const char* name, const bool initialValue); bool value; - MenuItem* BoolValueItem::goRight() override; - String BoolValueItem::display() override; + bool BoolValueItem::select(); + + void BoolValueItem::selectRight(); + void BoolValueItem::selectLeft(); + + String BoolValueItem::display(); }; #endif \ No newline at end of file diff --git a/watering_system_mini_project/watering_system_mini_project.ino b/watering_system_mini_project/watering_system_mini_project.ino index e539fe7..05ac36d 100644 --- a/watering_system_mini_project/watering_system_mini_project.ino +++ b/watering_system_mini_project/watering_system_mini_project.ino @@ -1,46 +1,243 @@ +# include "LiquidCrystal.h" + # include "MenuItem.h" - MenuItem* top = new MenuItem("Main menu"); - MenuItem* current = top; - MenuItem* toggle = new BoolValueItem("A switch", false); - MenuItem* sub = new BranchMenuItem("Sub menu", toggle); - MenuItem* next = new IntValueItem("Power", 1); +// PIN NUMBERS +# define JOY_HORIZ A0 +# define JOY_VERT A1 +# define JOY_BUTTON 6 +# define LED_RED 5 +# define LED_BLUE 3 + +# define cycle_extra_resolution 20 // number of 'substeps' per main step, allowing for extra resolution within the steps +# define stepTime 150 // step time in ms + +# define duty_cycle_max 10 // granularity of the duty cycle - the min value is always 0, and the max is this value, meaning 100% +# define duration_max 24 + +MenuItem* lightToggle = new BoolValueItem("Toggle", false); +IntValueItem* lightCyclePower = new IntValueItem("Cycle Power", 1, 0, 10); +IntValueItem* lightCycleOnDuty = new IntValueItem("Cycle On Duty", 1, 0, duty_cycle_max); +IntValueItem* lightCycleDuration = new IntValueItem("Cycle Duration", 1, 0, duration_max); + +MenuItem* waterToggle = new BoolValueItem("Toggle", false); +IntValueItem* waterCyclePower = new IntValueItem("Cycle Power", 1, 0, 10); +IntValueItem* waterCycleOnDuty = new IntValueItem("Cycle On Percent", 1, 0, duty_cycle_max); +IntValueItem* waterCycleDuration = new IntValueItem("Cycle Duration", 1, 0, duration_max); + +MenuItem* lightSettings = new BranchMenuItem("Light Settings", lightToggle); +MenuItem* waterSettings = new BranchMenuItem("Water Settings", waterToggle); + +MenuItem* top = new BranchMenuItem("Main menu", waterSettings); +MenuItem* current = top; + +// MenuItem* toggle = new BoolValueItem("A switch", false); +// MenuItem* sub = new BranchMenuItem("Sub menu", toggle); +// MenuItem* next = new IntValueItem("Power", 1); + +// with the Arduino pin number they are connected to +const int RS = 7, EN = 8, DB4 = 9, DB5 = 10, DB6 = 11, DB7 = 12; +LiquidCrystal lcd(RS, EN, DB4, DB5, DB6, DB7); + +// loop control +bool parse_control = false; // signal to tell further code to execute if the joystick has been pressed +bool control_locked = false; // if true prevents further commands from processing until joystick is in neutral + +// interface control +bool selected = false; // allows a menu item to be 'selected', disabling navigation and instead calling modification functions + +// cycle control +unsigned long lastStepTime = 0; +int waterCyclePosition = 0; +int lightCyclePosition = 0; void displayMenu() { - Serial.println("---- MENU ----"); - Serial.println(current->display()); - Serial.println("--------------"); + // grab the menu item's display string + String out = current->display(); + + // grab the position of \n, indicating the start point of the lower string + int sepPos = out.indexOf('\n'); + + // print line one + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print(out.substring(0, sepPos)); + lcd.setCursor(0, 1); + + + if(current->select()){ + if (selected) lcd.print("#"); + else lcd.print(":"); + } + + // only print if the string has two parts + if (sepPos != -1){ + lcd.print(out.substring(sepPos+1)); + } } +void handleInterface(){ + // grab joystick positions + int pos_x = analogRead(JOY_HORIZ); + int pos_y = analogRead(JOY_VERT); + bool pressed = not(digitalRead(JOY_BUTTON)); + + parse_control = false; + MenuItem* new_menu = NULL; + + // joystick handler if-else spaghetti + if (control_locked){ + // only disable control_locked if joystick is vaguely neutral. Sort of doubles as a kind of debouncer. + if (800 > pos_y && pos_y > 224 && 800 > pos_x && pos_x > 224 && not(pressed)) control_locked = false; + } + // vertical moves take priority over horizontal ones - i.e if joystick is pointed diagonally + else if (pos_y > 1000) { + // down + Serial.println("down"); + parse_control = true; + control_locked = true; + + new_menu = current->goDown(); + + } else if (pos_y < 24) { + // up + Serial.println("up"); + //parse_control = true; + control_locked = true; + + } else if (pos_x > 1000) { + // left + Serial.println("left"); + parse_control = true; + control_locked = true; + + if (selected) current->selectLeft(); + else new_menu = current->goBack(); + + } else if (pos_x < 24) { + // right + Serial.println("right"); + parse_control = true; + control_locked = true; + + if (selected) current->selectRight(); + else new_menu = current->goRight(); + } else if (pressed) { + Serial.println("pressed"); + if(current->select()){ + selected = not(selected); + } + + parse_control = true; + control_locked = true; + + } + + // parse the control signals processed by the joystick handler + if (parse_control){ + if (new_menu != NULL) { + current = new_menu; + // if selection for the new item doesn't make sense, disable selected + if(not(current->select())){ + selected = 0; + } + } + displayMenu(); + } +} + +void handleControl(){ + // each stepTime millis, advance one in the water cycle. + if (millis()-lastStepTime > stepTime){ + lastStepTime += stepTime; + // reset cycle after waterCycleDuration steps + // multiplying by the extra resolution parameter here, in order to avoid floating point division, and for more accurate start/stop times as defined by the cycleOnTime variable. + waterCyclePosition += 1; + if (waterCyclePosition == waterCycleDuration->value*cycle_extra_resolution) waterCyclePosition = 0; + + // magic no float equation to determine the current position in the cycle, and compare it to the duty cycle + if (duty_cycle_max*(waterCyclePosition) / (waterCycleDuration->value) < (waterCycleOnTime->value*cycle_extra_resolution)) { + + // if the current cycle position has passed the cycleontime variable, turn off the motor. else, turn it on. + // MOTORON + digitalWrite(LED_BLUE, HIGH); + } else { + // MOTOROFF + digitalWrite(LED_BLUE, LOW); + } + + // reset cycle after waterCycleDuration steps + // multiplying by the extra resolution parameter here, in order to avoid floating point division, and for more accurate start/stop times as defined by the cycleOnTime variable. + lightCyclePosition += 1; + if (lightCyclePosition == lightCycleDuration->value*cycle_extra_resolution) lightCyclePosition = 0; + + Serial.println(duty_cycle_max*(lightCyclePosition) / (lightCycleDuration->value)); + Serial.println(lightCycleOnDuty->value*cycle_extra_resolution); + + if (duty_cycle_max*(lightCyclePosition) / (lightCycleDuration->value) < (lightCycleOnDuty->value*cycle_extra_resolution)) { + // if the current cycle position has passed the cycleontime variable, turn off the motor. else, turn it on. + // LEDON + digitalWrite(LED_RED, HIGH); + } else { + digitalWrite(LED_RED, LOW); + // LEDOFF + } + } +} void setup() { Serial.begin(9600); + // pins setup - top->next = sub; - sub->next = toggle; - toggle->next = top; + // LCD brightness pin + pinMode(JOY_HORIZ, INPUT); + pinMode(JOY_VERT, INPUT); + pinMode(JOY_BUTTON, INPUT); + pinMode(LED_RED, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + + // initialization for menu items + { + lightSettings->next = waterSettings; + waterSettings->next = lightSettings; + + lightSettings->parent = top; + waterSettings->parent = top; + + top->next = top; + top->parent = top; + + lightToggle->next = lightCyclePower; + lightCyclePower->next = lightCycleOnDuty; + lightCycleOnDuty->next = lightCycleDuration; + lightCycleDuration->next = lightToggle; + + lightToggle->parent = lightSettings; + lightCyclePower->parent = lightSettings; + lightCycleOnDuty->parent = lightSettings; + lightCycleDuration->parent = lightSettings; + + waterToggle->next = waterCyclePower; + waterCyclePower->next = waterCycleOnDuty; + waterCycleOnDuty->next = waterCycleDuration; + waterCycleDuration->next = waterToggle; + + waterToggle->parent = waterSettings; + waterCyclePower->parent = waterSettings; + waterCycleOnDuty->parent = waterSettings; + waterCycleDuration->parent = waterSettings; + } + + + // init LCD + lcd.begin(16, 2); + + // display initial menu displayMenu(); } void loop() { - if (Serial.available()) { - int button = Serial.parseInt(); - MenuItem* new_menu = NULL; - if (button == 1) { - new_menu = current->goBack(); - Serial.println(1); - } else if (button == 2) { - new_menu = current->goDown(); - Serial.println(2); - } else if (button == 3) { - new_menu = current->goRight(); - Serial.println(3); - } - if (new_menu != NULL) { - current = new_menu; - } - displayMenu(); - - Serial.flush(); - } + handleInterface(); + handleControl(); + delay(10); } \ No newline at end of file