2
\$\begingroup\$

I am working on a project that involves scheduling real world events for a pool. Example: turn a pump on at 8AM, turn heater on at 9 AM, set heating temperature to 80 degrees, turn the pump off at 10 AM, etc. Events can be inserted into the queue in order by time and be popped off and processed at a later time. I also wanted the events to be repeatable, aka once placed in the schedule and given a repeat length they would be reinserted to the schedule at a later date. My Schedule class does not process the events, but is rather a queue that stores the necessary information.

Context information:

The project will be running on an Arduino Mega so all std::cout etc. calls will be replaced with Serial.write() when actually implemented. I will be using a timestamp class to use in the project that uses the Time Arduino library. The timestamp can be converted to a time_t value (UNIX time) which is why my class uses unsigned int for time to order the Schedule list.

I would like to follow C++ conventions and make my code more efficient. What works just fine, what could be improved, any errors in logic? Am I missing obvious things a person would want to control in a pool? Performance is not a huge deal since most of the time the program will be sitting idle but I would like to increase readability or make the class easier to use.

Schedule.h

#ifndef _Schedule_h
#define _Schedule_h

#include <iostream>

// Specification file for the Schedule class
class Schedule{
    private:
        // Declare a structure for the list
        struct ListNode {
            unsigned int    _time;  // The value in this node
            int     _instruction;   // ScheduleCommand for scheduling
            int     _temperature;   // Temperature for setting temp command
            unsigned int    _repeatTime;    // Added to previous time for repeated events
            struct ListNode* next;  // To point to the next node
        };

        ListNode* headPtr;          // List head pointer

    public:
        Schedule()  { headPtr = NULL; } // Constructor
        ~Schedule();                    // Destructor

        // Schedule commands
        enum ScheduleCommands {PumpOn, PumpOff, HeaterOn, HeaterOff, SetLowTemp, SetHighTemp};

        // Displays what's currently in the schedule (for testing only)
        void    displayList() const;

        // Inserts an event to the schedule based on the time
        void    addEvent(unsigned int time, int command, unsigned int repeatTime, int temperature);
        void    addEvent(unsigned int time, int command, unsigned int repeatTime);
        void    addEvent(unsigned int time, int command);

        // Removes event from the schedule
        bool    deleteEvent(unsigned int time, int command);

        // For polling whether a new event should be processed
        bool    timeForNextEvent(unsigned int time) const;

        // Gets the next event in the schedule and removes it from the list, returning info
        bool    popOffNextEvent(int& command, int& temp, unsigned int& repeatTime);

        // If a searched for event repeats, it doesn't anymore
        bool    stopEventRepeat(unsigned int time, int command);
};

#endif /* _Schedule_h */

Schedule.cpp

#include "Schedule.h"

// Displays what's currently in the schedule (for testing only)
void Schedule::displayList() const {
    ListNode*   nodePtr = headPtr;      //  To move through the list, starting at head

    std::cout << "\n";

    //  While nodePtr points to a node, traverse the list
    while (nodePtr != NULL) {
        // Display info about each node
        std::cout << "\nTime: " << nodePtr->_time
            << " | Command: " << nodePtr->_instruction
            << " | Repeated: " << nodePtr->_repeatTime;

        if (nodePtr->_temperature > 0)
            std::cout << "\tTemp: " << nodePtr->_temperature;

        nodePtr = nodePtr->next;        //  Move to the next node
    }

    std::cout << std::endl;
}

void Schedule::addEvent(unsigned int time, int command){
    Schedule::addEvent(time, command, 0, 0);
}

void Schedule::addEvent(unsigned int time, int command, unsigned int repeatTime = 0) {
    Schedule::addEvent(time, command, repeatTime, 0);
}

// Inserts a scheduled event into the queue in order by time
void Schedule::addEvent(unsigned int time, int command, unsigned int repeatTime = 0, int temperature = 0) {
    ListNode*   newNode;                    //  Dynamically allocated ListNode
    ListNode*   nodePtr;                    //  Goes through list
    ListNode*   prevNode = NULL;            //  Previous nodePtr

    // Allocate a new node and store info there
    newNode = new ListNode;
    newNode->_time = time;
    newNode->_instruction = command;
    newNode->_repeatTime = repeatTime;
    newNode->_temperature = temperature;

    //  If no nodes, newNode is first node
    if (headPtr == NULL) {
        headPtr = newNode;
        newNode->next = NULL;
    }
    else {
        nodePtr = headPtr;                  //  Initialize nodePtr to head of list
        prevNode = NULL;                    //  No previous node yet

        //  Find the node in the list that's higher than passed in value
        while ( (nodePtr != NULL) && (nodePtr->_time < time) ) {
            prevNode = nodePtr;
            nodePtr = nodePtr->next;
        }

        //  Passed in time lower than first in list, so goes first
        if (prevNode == NULL) {
            headPtr = newNode;              //  newNode = head node
            newNode->next = nodePtr;        //  newNode is only node, so NULL
        }
        else {                              //  Insert after previous node
            prevNode->next = newNode;
            newNode->next = nodePtr;
        }
    }
}

// Will remove event and repeated events from schedule
bool Schedule::deleteEvent(unsigned int time, int command) {
    ListNode*   nodePtr;                //  Current node
    ListNode*   prevNode;               //  Previous node
    bool    nodeDeleted = false;

    if (headPtr == NULL) {              //  If no nodes to delete
        //std::cout << "\n No nodes to be deleted!\n\n";
        return nodeDeleted;
    }

    //  If first node is to one to deleted
    if (headPtr->_time == time && headPtr->_instruction == command) {
        nodePtr = headPtr->next;
        delete headPtr;
        headPtr = nodePtr;
        nodeDeleted = true;
    }
    else {
        nodePtr = headPtr;              //  Start at start of list

        //  Skip all nodes not equal to passing in value
        while ( (nodePtr != NULL) && (nodePtr->_time != time) && (nodePtr->_instruction != command) ) {
            prevNode = nodePtr;
            nodePtr = nodePtr->next;
        }
        //  If node not at end of list, link prev node to node after
        //  nodePtr, then delete nodePtr
        if (nodePtr != NULL) {
            prevNode->next = nodePtr->next;
            delete nodePtr;
            nodeDeleted = true;
        }
        else {
            //std::cout << "\n Event " << std::endl << time << " is not in the list to delete!";
        }
    }

    return nodeDeleted;
}

// Returns whether it's time for next event processing
bool Schedule::timeForNextEvent(unsigned int time) const {
    bool    eventNeedsProcessed = false;    // Returns whether it's time for next scheduled event

    if ( (headPtr != NULL) && (headPtr->_time <= time) )    //  Next event's time is passed
        eventNeedsProcessed = true;

    return eventNeedsProcessed;
}

// Gets the next scheduled events information. It will delete the event but if the event is
// meant to be repeated then it will add a copy of the event at the new location
bool Schedule::popOffNextEvent(int& command, int& temp, unsigned int& repeatTime) {
    bool    popSuccessful = false;  // Returns whether anything was popped
    ListNode* nodePtr;              // For temp storing

    // If there's something in the list, delete it and return values
    if (headPtr != NULL) {
        command = headPtr->_instruction;
        temp = headPtr->_temperature;
        repeatTime = headPtr->_repeatTime;
        popSuccessful = true;

        // If repeat time was specified, re-add the event back into the list at the new time
        if (headPtr->_repeatTime > 0) {
            addEvent(headPtr->_time + headPtr->_repeatTime, headPtr->_instruction, 
                headPtr->_repeatTime, headPtr->_temperature);
        }

        nodePtr = headPtr->next;
        delete headPtr;
        headPtr = nodePtr;
    }

    return popSuccessful;
}

// Stop a currently reoccuring scheduled event from repeating again after the next
// time it is popped off (processed)
bool Schedule::stopEventRepeat(unsigned int time, int command) {
    bool    eventRepeatStopped = false;     // Has the event been stopped from repeating?
    ListNode* nodePtr;

    if (headPtr == NULL) {
        // Nothing to stop repeating
    }
    else {
        nodePtr = headPtr;                  //  Initialize nodePtr to head of list

        //  Find the node in the list that is being searched for
        while ( (nodePtr != NULL) && 
            (nodePtr->_time != time) && (nodePtr->_instruction != command) && (nodePtr->_repeatTime != 0) )
            nodePtr = nodePtr->next;

        //  If node not at end of list, the event was found. Repeating stopped
        if (nodePtr != NULL) {
            nodePtr->_repeatTime = 0;
            eventRepeatStopped = true;
        }
        else {
            //std::cout << std::endl << "\n Event is not in the list to stop its repeating!";
        }
    }

    return eventRepeatStopped;
}

Schedule::~Schedule() {
    ListNode*   nodePtr = headPtr;      //  To traverse the list
    ListNode*   nextNode;               //  To point to the next node

    while (nodePtr != NULL) {           //  'Til end of list
        nextNode = nodePtr->next;       //  Store next node address
        delete nodePtr;                 //  Delete current address
        nodePtr = nextNode;             //  Move to next node
    }
}

Main.cpp (testing the Schedule class)

#include "Schedule.h"

// Will be removed when moved to Arduino and replaced with Serial.write() calls
#include <iostream>

void timeForNextEventTest(Schedule* scheduleList, unsigned int time);
void popOffNextEventTest(Schedule* scheduleList, int command, int temperature, unsigned int repeatTime);
void deleteEventTest(Schedule* scheduleList, unsigned int time, int command);
void stopEventRepeatTest(Schedule* scheduleList, unsigned int time, int command);

// Tests the Schedule class
void main() {
    Schedule scheduleList;
    unsigned int time = 0;
    int command = 0;
    int temperature = 0;
    unsigned int repeatTime = 0;

    timeForNextEventTest(&scheduleList, 1005);

    scheduleList.addEvent(1000, Schedule::PumpOff);
    scheduleList.addEvent(2000, Schedule::PumpOn);
    scheduleList.addEvent(1500, Schedule::HeaterOn, 0, 80);
    scheduleList.addEvent(1750, Schedule::HeaterOff, 150, 75);
    scheduleList.addEvent(1600, Schedule::SetLowTemp, 160, 60);

    scheduleList.displayList();

    timeForNextEventTest(&scheduleList, 995);
    timeForNextEventTest(&scheduleList, 1000);
    timeForNextEventTest(&scheduleList, 1005);

    popOffNextEventTest(&scheduleList, command, temperature, repeatTime);
    scheduleList.displayList();

    popOffNextEventTest(&scheduleList, command, temperature, repeatTime);
    scheduleList.displayList();

    popOffNextEventTest(&scheduleList, command, temperature, repeatTime);
    scheduleList.displayList();

    popOffNextEventTest(&scheduleList, command, temperature, repeatTime);
    scheduleList.displayList();

    time = 2000;
    command = Schedule::PumpOn;
    deleteEventTest(&scheduleList, time, command);
    deleteEventTest(&scheduleList, time, command);
    scheduleList.displayList();

    time = 111;
    command = 9;
    stopEventRepeatTest(&scheduleList, time, command);

    time = 1760;
    command = 3;
    stopEventRepeatTest(&scheduleList, time, command);
    scheduleList.displayList();

    popOffNextEventTest(&scheduleList, command, temperature, repeatTime);
    scheduleList.displayList();

    popOffNextEventTest(&scheduleList, command, temperature, repeatTime);
    scheduleList.displayList();

    std::cout << std::endl;
}

void timeForNextEventTest(Schedule* scheduleList, unsigned int time) {
    if (!scheduleList->timeForNextEvent(time))
        std::cout << "\n No Events Scheduled By: " << time;
    else
        std::cout << "\n Next Event Ready By: " << time;
    }

void popOffNextEventTest(Schedule* scheduleList, int command, int temperature, unsigned int repeatTime) {
    if (scheduleList->popOffNextEvent(command, temperature, repeatTime)) {
        std::cout << "\n Event Popped. Command: " << command
            << " RepeatTime: " << repeatTime;
        if (temperature > 0)
            std::cout << " Temperature: " << temperature;
    }
    else {
        std::cout << "\n No event left in queue.";
    }
}

void deleteEventTest(Schedule* scheduleList, unsigned int time, int command) {
    if (scheduleList->deleteEvent(time, command))
        std::cout << "\n Delete Successful of Time: " << time << " Command: " << command;
    else
        std::cout << "\n Failure to Delete Time: " << time << " Command: " << command;
}

void stopEventRepeatTest(Schedule* scheduleList, unsigned int time, int command) {
    if (scheduleList->stopEventRepeat(time, command))
        std::cout << "\n Event Repeat Stopped. Time: " << time << " Command: " << command;
    else
        std::cout << "\n No Event with specified repeat found. Time: " << time << " Command: " << command;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to CodeReview, Dimmins. Interesting program you have here. As an aside, You don't need to look for related questions before posting yours. Unlike Stack Overflow, the only true duplicates here are essentially copy-pastes. We have many Project Euler 1-10s and Fizz Buzz problems, CR prioritizes helping the individual(s). Hope that clarifies some things, and that you receive some fine answers. Enjoy the site! \$\endgroup\$ – Legato Apr 10 '15 at 11:16
  • \$\begingroup\$ Ahh, thank you for clarifying what CodeReview is about. Still, seeing if my question had already been answered would save everyone's time.I could learn from the answers there then I could have improved my code further. Thank you for the welcome! \$\endgroup\$ – Dimmins Apr 10 '15 at 11:48
1
\$\begingroup\$

I suggest using a specialized priority queue of some kind to store the pending events rather than a ordered linked list. There are several options but most have worst case a O(log(n)) insertion and O(log(n)) popMin.

One of the things you will want is a time until next event query. This allows you to put it in a sleep() to save power (if you are willing to be less accurate).

\$\endgroup\$
  • \$\begingroup\$ Thank you for your suggestion, ratchet freak. I do not know how using a priority queue would be superior to a list ordered by time since what I care about is the next event scheduled in regards to time. Am I misunderstanding what a priority queue is? I do not believe I have ever used a heap. A sleep() call would be useful since parts of the system will be running on battery. I understand conceptually how to use the wait, but if it is 10 AM, next scheduled task is at 6 PM, but the user inserts something at 4 PM, would the new task inserted at the beginning not be processed until 6 PM? \$\endgroup\$ – Dimmins Apr 10 '15 at 11:38
  • \$\begingroup\$ The priority used by the heap would be "time until it needs to run next". \$\endgroup\$ – ratchet freak Apr 10 '15 at 11:40
  • \$\begingroup\$ Would it be a good idea to modify the timeForNextEvent function where it returns the next event's time and inside that function puts the Schedule to sleep. When the user either reaches that time or a new event is added to the Schedule, it would wake back up? \$\endgroup\$ – Dimmins Apr 12 '15 at 19:59
  • \$\begingroup\$ if you call it waitForNextEvent yeah \$\endgroup\$ – ratchet freak Apr 12 '15 at 20:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.