Skip to main content

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.