The Algorithm
Now your DirtyWatts Micro-Indicator is on and working, let’s look at what your light is trying to tell you. You can see how this colour is chosen in src/powerstations.cpp
.
void PowerStations::calculateInstructionPoint()
{
// Initialise the instruction point
memset(instructionPoint.color, 0, sizeof(instructionPoint.color)); // fill the array with zeros
instructionPoint.percentRenewable = 0;
instructionPoint.powerSocketEnabled = true;
// Calculate the total generation
double totalRenewable = battery.generation_mw + geothermal.generation_mw + hydro.generation_mw + wind.generation_mw;
double totalNonRenewable = co_gen.generation_mw + coal.generation_mw + gas.generation_mw + diesel.generation_mw;
double totalGeneration = totalRenewable + totalNonRenewable;
// Check for problems in source data
if (totalGeneration == 0)
{
Serial.println("No generation?!?");
memcpy(instructionPoint.color, errorColor, sizeof(instructionPoint.color));
return;
}
double percentageRenewable = totalRenewable / totalGeneration;
instructionPoint.percentRenewable = percentageRenewable;
if (coal.generation_mw > 0 || diesel.generation_mw > 0)
{
// Calculate the color if coal or diesel power is in use
double halfMaxCoal = round(coal.capacity_mw / 2 + diesel.capacity_mw / 2);
double badGeneration = min((coal.generation_mw + diesel.generation_mw) / halfMaxCoal, (double)1);
instructionPoint.color[0] = 150 + round(105 * badGeneration);
instructionPoint.color[1] = round(95 - (95 * badGeneration));
instructionPoint.powerSocketEnabled = false;
}
else
{
// Calculate the color if both coal and diesel power is not in use
double halfMaxGas = round(gas.capacity_mw / 2);
double mediumGeneration = min(gas.generation_mw / halfMaxGas, (double)1);
instructionPoint.color[0] = round(160 * mediumGeneration);
instructionPoint.color[1] = round(255 - (127 * mediumGeneration));
instructionPoint.powerSocketEnabled = true;
}
}
This algorithm was written by Taine Reader during the original 2022 GovHack Competition. Let's disect what this massive blob of complicated looking things does.
Algorithm Dissection
double totalRenewable = battery.generation_mw + geothermal.generation_mw + hydro.generation_mw + wind.generation_mw;
double totalNonRenewable = co_gen.generation_mw + coal.generation_mw + gas.generation_mw + diesel.generation_mw;
double totalGeneration = totalRenewable + totalNonRenewable;
...
double percentageRenewable = totalRenewable / totalGeneration;
The first thing it does is calculate the percentage renewable according to the following table. It doesn’t use the percentage to calculate the colour but is worked out to be displayed in text output logs, such as in a Watch face.
Non-renewable Energy |
Renewable Energy |
Coal | Hydropower |
Gas | Wind |
Cogeneration | Geothermal |
Diesel | Battery |
If coal is in use, then it will start with a yellow base colour, then progressively redden the colour as both coal and diesel usage increases.
if (coal.generation_mw > 0 || diesel.generation_mw > 0)
{
// Calculate the color if coal or diesel power is in use
double halfMaxCoal = round(coal.capacity_mw / 2 + diesel.capacity_mw / 2);
double badGeneration = min((coal.generation_mw + diesel.generation_mw) / halfMaxCoal, (double)1);
instructionPoint.color[0] = 150 + round(105 * badGeneration);
instructionPoint.color[1] = round(95 - (95 * badGeneration));
instructionPoint.powerSocketEnabled = false;
}
If coal isn’t in use, which is most of the time, it will start with a green base colour, and yellow the colour as the amount of gas power generation increases.
else
{
// Calculate the color if both coal and diesel power is not in use
double halfMaxGas = round(gas.capacity_mw / 2);
double mediumGeneration = min(gas.generation_mw / halfMaxGas, (double)1);
instructionPoint.color[0] = round(160 * mediumGeneration);
instructionPoint.color[1] = round(255 - (127 * mediumGeneration));
instructionPoint.powerSocketEnabled = true;
}
Your own algorithm
Now you understand how the default one works, it’s up to you to make your own variation on it. To do this, you’ll going to want to investigate the src/neolights.cpp
file, and the updateNeoPixels function within it.
void updateNeoPixels(PowerStations powerstationData) {
int (&defaultColor)[3] = powerstationData.instructionPoint.color;
pixels.fill(pixels.Color(defaultColor[0], defaultColor[1], defaultColor[2]), 0, NeoPixelCount);
pixels.show();
}
All this function does, is get the latest PowerGrid data, and choose how to display it on your NeoPixel lights. Right now, it’s a bit boring, it just gets the single colour chosen by the default algorithm and displays it on every pixel. You can do much better!
For example, if you wanted to only light up when coal power is on, you could:
void updateNeoPixels(PowerStations powerstationData) {
if (powerstationData.coal.generation_mw > 0) {
// If coal is being generated, show all red
pixels.fill(pixels.Color(255, 0, 0), 0, NeoPixelCount);
} else {
// Otherwise, show turn off the lights
pixels.clear();
}
pixels.show();
}
What about displaying a scale of amount of renewable energy?
void updateNeoPixels(PowerStations powerstationData) {
for (int i = 0; i < NeoPixelCount; i++) {
// Calculate the percentage of the NeoPixel strip that should be lit up
float percent = (float)i / (float)NeoPixelCount;
// If the percentage of renewable power is greater than the percentage of the NeoPixel strip, turn it on
if (powerstationData.instructionPoint.percentRenewable > percent) {
// Green Pixel
pixels.setPixelColor(i, pixels.Color(0, 255, 0));
} else {
// Red Pixel
pixels.setPixelColor(i, pixels.Color(255, 0, 0));
}
}
pixels.show();
}
Get creative here, this is how you can customise your DirtyWatts Micro-Indicator! Every time you are ready to test your changes, you can plug-in your NodeMCU and use the same Upload and Monitor
button as before.