Added wrapper for PowerScheduler

This commit is contained in:
Phillip Kühne 2024-12-22 22:07:54 +01:00
parent 915ad85526
commit fbe205035e
7 changed files with 219 additions and 127 deletions

View File

@ -9,6 +9,7 @@ Dezibot::Dezibot() : multiColorLight() {};
void Dezibot::begin(void)
{
Power.begin();
Wire.begin(SDA_PIN, SCL_PIN);
infraredLight.begin();
lightDetection.begin();
@ -17,10 +18,4 @@ void Dezibot::begin(void)
colorDetection.begin();
multiColorLight.begin();
display.begin();
Power.begin();
this->power = Power::getPowerManager();
if (!this->power.tryAccquirePowerAllowance(CONSUMPTION_ESP_BASE);)
{
throw "Could not allocate power for the base consumption of the ESP32";
}
};

View File

@ -53,7 +53,7 @@ class Motor{
uint8_t pin;
ledc_timer_t timer;
ledc_channel_t channel;
Power powerManager;
Power* powerManager;
uint16_t duty;
};

View File

@ -5,7 +5,7 @@ Motor::Motor(uint8_t pin, ledc_timer_t timer, ledc_channel_t channel){
this->channel = channel;
this->timer = timer;
this->duty = 0;
this->powerManager = *Power::getPowerManager();
this->powerManager =
};
void Motor::begin(void){
@ -25,10 +25,10 @@ void Motor::begin(void){
void Motor::setSpeed(uint16_t duty){
if(duty>0) {
powerManager.waitForPowerAllowance(CONSUMPTION_MOTOR, portMAX_DELAY);
powerManager->waitForPowerAllowance(CONSUMPTION_MOTOR, portMAX_DELAY);
Serial.println("Motor got power");
} else {
powerManager.releasePower(CONSUMPTION_MOTOR);
powerManager->releasePower(CONSUMPTION_MOTOR);
Serial.println("Motor released power");
}
int difference = duty-this->getSpeed();

View File

@ -10,114 +10,41 @@
#include "Power.h"
static portMUX_TYPE mux;
Power::Power()
{
// TODO: Create wrappper around all this which handles single-instancing so we look like a normal arduino library from the outside.
this->freePowerBudget = this->totalPowerBudget;
}
void Power::begin()
{
if (powerInstance == nullptr)
// Check if another instance of us already initialized the power scheduler,
// if not, we will do it.
if (powerScheduler == nullptr)
{
// Double check locking https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
taskENTER_CRITICAL(&mux);
if (powerInstance == nullptr)
powerScheduler = PowerScheduler::getPowerScheduler();
if (!(powerScheduler->tryAccquirePowerAllowance(CONSUMPTION_ESP_BASE)))
{
powerInstance = new Power();
Serial.println("Alledgedly not enough power available to reserve the ESP32s base power consumption. Something is wrong.");
return;
}
taskEXIT_CRITICAL(&mux);
}
else
{
// Power.begin() was called twice!
Serial.println("Power.begin() was called twice! No harm done, but this is a bug.");
}
}
Power *Power::getPowerManager()
uint16_t Power::getFreePowerBudget(void)
{
return powerInstance;
return powerScheduler->getFreePowerBudget();
}
bool Power::tryAccquirePowerAllowance(uint16_t neededPower)
{
if (this->freePowerBudget >= neededPower)
{
this->freePowerBudget -= neededPower;
return true;
}
else
{
return false;
}
return powerScheduler->tryAccquirePowerAllowance(neededPower);
}
bool Power::waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait)
{
return powerScheduler->waitForPowerAllowance(neededPower, TicksToWait);
}
void Power::beginPermanentDeepSleep(void)
{
return powerScheduler->beginPermanentDeepSleep();
}
void Power::releasePower(uint16_t power)
{
if (this->freePowerBudget + power <= this->totalPowerBudget)
{
this->freePowerBudget += power;
}
else // TODO: Maybe we should actually throw an error here, since obviouslsy someone used us wrong.
{
this->freePowerBudget = this->totalPowerBudget;
}
// Check if there are tasks waiting for power
checkWaitingTasks();
}
bool Power::waitForPowerAllowance(uint16_t neededPower, TickType_t ticksToWait)
{
if (tryAccquirePowerAllowance(neededPower))
{
return true;
}
else
{
// Suspend the task while waiting for power to be available
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
TickType_t initialTickCount = xTaskGetTickCount();
waitingTasks.push(currentTask);
uint32_t notificationValue;
BaseType_t notificationStatus = xTaskNotifyWait(0, 0, &notificationValue, ticksToWait);
// Code below will be executed after the task is woken up
while (notificationStatus == pdPASS)
{
if (notificationValue == POWER_AVAILABLE)
{
// We were woken up because new power is available, check if it is enough
if (tryAccquirePowerAllowance(neededPower))
{
return true;
}
else
{
// Still not enough power available for us. Wait the remaining ticks.
xTaskNotifyWait(0, 0, &notificationValue, ticksToWait - (xTaskGetTickCount() - initialTickCount));
}
}
}
if (notificationStatus == pdFALSE)
{
// We waited long enough...
return false;
}
else
{
// Should be impossible to reach
throw "Reached impossible state";
}
}
}
void Power::checkWaitingTasks(void)
{
// Check if there are tasks waiting for power
if (!waitingTasks.empty())
{
TaskHandle_t task = waitingTasks.front();
waitingTasks.pop();
xTaskNotify(task, POWER_AVAILABLE, eSetValueWithOverwrite);
}
}

View File

@ -7,37 +7,24 @@
* @date 2024-11-23
*/
#include <queue>
#include <Arduino.h>
#include "Consumptions.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "PowerScheduler.h"
#ifndef Power_h
#define Power_h
#define TOTAL_POWER_MILLIWATTS POWER_BUDGET
enum TaskResumptionReason {
enum TaskResumptionReason
{
POWER_AVAILABLE,
TIMEOUT
};
class Power {
protected:
static const uint16_t totalPowerBudget = TOTAL_POWER_MILLIWATTS;
uint16_t freePowerBudget;
std::queue<TaskHandle_t> waitingTasks;
void checkWaitingTasks(void);
bool takePowerIfAvailable(uint16_t neededPower);
static Power* powerInstance;
Power();
class Power
{
public:
public:
static void begin();
/// @brief Initialize the singleton instance of the power manager
/// @return reference to the power manager
static Power *getPowerManager();
Power();
uint16_t getFreePowerBudget(void);
/// @brief Request an allowance of a certain number of milliwatts from the power scheduler
/// @param neededPower the amount of power we want to be accounted for (in mW)
@ -52,9 +39,12 @@ class Power {
/// @param neededPower the amount of power we want to be accounted for (in mW)
/// @param TicksToWait the amount of time to wait for the power to become available
/// @return whether the power could be successfully allocatedy
bool waitForPowerAllowance(uint16_t neededPower,TickType_t TicksToWait);
bool waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait);
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown.
void beginPermanentDeepSleep(void);
protected:
static PowerScheduler *powerScheduler;
};
#endif //Power
#endif // Power

View File

@ -0,0 +1,109 @@
/**
* @file PowerScheduler.cpp
* @author Phillip Kühne
* @brief The actual power scheduler class, which keeps track of the power budget and allocates power to different components.
* @version 0.1
* @date 2024-12-21
*
* @copyright (c) 2024
*
*/
#include "PowerScheduler.h"
bool PowerScheduler::tryAccquirePowerAllowance(uint16_t neededPower)
{
if (this->freePowerBudget >= neededPower)
{
this->freePowerBudget -= neededPower;
return true;
}
else
{
return false;
}
}
void PowerScheduler::releasePower(uint16_t power)
{
if (this->freePowerBudget + power <= this->totalPowerBudget)
{
this->freePowerBudget += power;
}
else // TODO: Maybe we should actually throw an error here, since obviouslsy someone used us wrong.
{
this->freePowerBudget = this->totalPowerBudget;
}
// Check if there are tasks waiting for power
checkWaitingTasks();
}
bool PowerScheduler::waitForPowerAllowance(uint16_t neededPower, TickType_t ticksToWait)
{
if (tryAccquirePowerAllowance(neededPower))
{
return true;
}
else
{
// Suspend the task while waiting for power to be available
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
TickType_t initialTickCount = xTaskGetTickCount();
waitingTasks.push(currentTask);
uint32_t notificationValue;
BaseType_t notificationStatus = xTaskNotifyWait(0, 0, &notificationValue, ticksToWait);
// Code below will be executed after the task is woken up
while (notificationStatus == pdPASS)
{
if (notificationValue == POWER_AVAILABLE)
{
// We were woken up because new power is available, check if it is enough
if (tryAccquirePowerAllowance(neededPower))
{
return true;
}
else
{
// Still not enough power available for us. Wait the remaining ticks.
xTaskNotifyWait(0, 0, &notificationValue, ticksToWait - (xTaskGetTickCount() - initialTickCount));
}
}
}
if (notificationStatus == pdFALSE)
{
// We waited long enough...
return false;
}
else
{
// Should be impossible to reach
throw "Reached impossible state";
}
}
}
void PowerScheduler::checkWaitingTasks(void)
{
// Check if there are tasks waiting for power
if (!waitingTasks.empty())
{
TaskHandle_t task = waitingTasks.front();
waitingTasks.pop();
xTaskNotify(task, POWER_AVAILABLE, eSetValueWithOverwrite);
}
}
PowerScheduler *PowerScheduler::getPowerScheduler()
{
if (powerSchedulerInstance == nullptr)
{
// Double check locking https://www.aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
taskENTER_CRITICAL(&mux);
if (powerSchedulerInstance == nullptr)
{
powerSchedulerInstance = new PowerScheduler();
}
taskEXIT_CRITICAL(&mux);
}
return powerSchedulerInstance;
}

View File

@ -0,0 +1,71 @@
/**
* @file PowerScheduler.hpp
* @author Phillip Kühne
* @brief The actual power scheduler class, which keeps track of the power budget and allocates power to different components.
* @version 0.1
* @date 2024-12-21
*
* @copyright (c) 2024
*
*/
#include <queue>
#include <Arduino.h>
#include "Consumptions.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifndef PowerScheduler_h
#define PowerScheduler_h
#define TOTAL_POWER_MILLIWATTS POWER_BUDGET
class PowerScheduler
{
private:
/* data */
public:
PowerScheduler();
~PowerScheduler();
/// @brief Initialize the singleton instance of the power manager
/// @return reference to the power manager
static PowerScheduler *getPowerScheduler();
uint16_t getFreePowerBudget(void);
/// @brief Request an allowance of a certain number of milliwatts from the power scheduler
/// @param neededPower the amount of power we want to be accounted for (in mW)
/// @return whether the power could be successfully allocated
bool tryAccquirePowerAllowance(uint16_t neededPower);
/// @brief "Return" a certain amount of power when it is no longer needed
/// @param neededPower the amount of power to return (in mW)
/// @return whether the power
void releasePower(uint16_t power);
/// @brief Wait for a certain amount of power to be available
/// @param neededPower the amount of power we want to be accounted for (in mW)
/// @param TicksToWait the amount of time to wait for the power to become available
/// @return whether the power could be successfully allocatedy
bool waitForPowerAllowance(uint16_t neededPower, TickType_t TicksToWait);
/// @brief Put the ESP32 into deep sleep mode, without a method to wake up again. Basically this is a shutdown.
void beginPermanentDeepSleep(void);
protected:
static const uint16_t totalPowerBudget = TOTAL_POWER_MILLIWATTS;
uint16_t freePowerBudget;
std::queue<TaskHandle_t> waitingTasks;
void checkWaitingTasks(void);
bool takePowerIfAvailable(uint16_t neededPower);
};
PowerScheduler::PowerScheduler(/* args */)
{
// TODO: Create wrappper around all this which handles single-instancing so we look like a normal arduino library from the outside.
this->freePowerBudget = TOTAL_POWER_MILLIWATTS;
}
PowerScheduler::~PowerScheduler()
{
}
static PowerScheduler *powerSchedulerInstance;
#endif // PowerScheduler_h