How to Create a MakeCode Package for Micro:Bit

Pages
Contributors: Englandsaurus
Favorited Favorite 2

What to Change

Let's go ahead and create a new GitHub repository for our new MakeCode package, we'll call it pxt-gator-moisture. Clone this repo into your GitHub folder, then go ahead and copy over the contents of the pxt-gator-light repo. We'll mainly be looking at the two gatorlight files, the pxt.JSON file, the README.MD, and eventually the icon.png.

pxt-gator-moisture

We must change these files. Click the image to enlarge

First, we're going to go through and rename everything to gatormoisture, so go ahead and rename the two gatorlight files, once we've renamed them, go ahead and open up pxt.json and replace every instance of the word light with moisture. The *.json file tells MakeCode which files to include, and since we changed our filenames, we'll have to change them here as well. Don't neglect to roll the version number back to 0.0.1 as well as change the description to something that makes more sense. We're then going to open up both gatormoisture files and replace every instance of light with moisture once again.

Let's now look at where the code behind our blocks actually lives, the blocks live in the *.ts file while the actual functions live in the *.cpp. Let's check out the *.cpp first. We make sure to include pxt.h with every package, as well as use the pxt namespace. We then create a namespace, "gatormoisture" and put the function that calculates lux from a given ADC value. It's a pretty simple function but we'll be able to call it from our *.ts file, which is what we really wanted.

language:c
#include "pxt.h"
#include <cstdint>
#include <math.h>

using namespace pxt;

namespace gatorMoisture {
    /*
    * Calculates the light in Lux based on the ADC value passed in. 1 step in adcVal is equal to .488 uA or .976 lux at 5V
    */
    //%
    uint16_t getLux(int16_t ADCVal) {
        return ADCVal * .976;
    }

}

While we're here, let's change getLux to a getMoisture function that returns a float in between 0 and 1 instead of a value in lux. For this, we will simply divide the ADCVal passed in by the full-scale range of the ADC (1023). In the end, our gatormoisture.cpp looks like the following.

language:c
#include "pxt.h"
#include <cstdint>
#include <math.h>

using namespace pxt;

namespace gatorMoisture {
    /*
    * Calculates the light in Lux based on the ADC value passed in. 1 step in adcVal is equal to .488 uA or .976 lux at 5V
    */
    //%
    float getMoisture(int16_t ADCVal) {
        return ADCVal / 1023.0;
    }
}

Now let's check out how our blocks are created in the *.ts file, which should look like the following after we've changed everything from light to moisture.

language:c
enum gatorMoistureType{
    moisture=1,
    adcVal=2,
}

//% color=#f44242 icon="\uf185"
namespace gatorMoisture {

    // Functions for reading moisture from the gatormoisture in moisture or straight adv value

    /**
    * Reads the number
    */
    //% weight=30 blockId="gatorMoisture_moisture" block="Get moisture on pin %pin | in %gatorMoistureType"
    export function moisture(pin: AnalogPin, type: gatorMoistureType): number{
        let ADCVal = pins.analogReadPin(pin)
        switch(type){
            case gatorMoistureType.moisture: return getMoisture(ADCVal)
            case gatorMoistureType.adcVal: return ADCVal
            default: return -11111111
        }
    }

    /**
     * Function used for simulator, actual implementation is in gatormoisture.cpp
     */
    //% shim=gatorMoisture::getMoisture
    function getMoisture(ADCVal: number) {
        // Fake function for simulator
        return 0
    }
}

If we want to have options in dropdowns for our blocks, we create them using an enum. We will be able to choose whether we want moisture, a value between 0 and 1, or the straight adcVal. So outside of the namespace, we create an enum (shown below) for our possible data types.

language:c
enum gatorMoistureType{
    moisture=1,
    adcVal=2,
}

We then must pick a color and icon for our extension, which is done in the line before we declare our namespace. The color can be any 6-digit, hexadecimal value, while the icon will use it's identifier from the FontAwesome icon library. The color and icon declarations are shown below.

language:c
//% color=#f44242 icon="\uf185"

We then need to define what our block looks like and where it sits relative to other blocks. This is done by setting weight, blockId, and block. A block with weight 100 will list itself above blocks with weights below 100 and and below blocks with weights above 100. This allows you to decide how you want all of your blocks to be listed. The blockId MUST be mynamespacetitle_functionTitle so for our moisture block, which is in the gatorMoisture namespace, our blockId will be gatorMoisture_moisture. Finally, we decide exactly what goes into the text for the block using the block string. Any variable that we want to become a dropdown will be prefaced with a %. The following code will create a block with a dropdown for pin selection and a dropdown that allows you to choose between moisture and adcVal. This block will call the function moisture using whatever arguments that have been selected from the dropdown.

language:c
//% weight=30 blockId="gatorMoisture_moisture" block="Get moisture on pin %pin | in %gatorMoistureType"

Finally, we'll need to write the function that actually reads our pin. Any function that is declared as export will appear as a block in MakeCode. Arguments for this function will be whatever we declared as dropdown capable variables and we'll usually need to set up a switch statement based on the type to return the proper values for each type selected. Notice how we call getMoisture, the function contained in our *.cpp when our type is moisture. We also must declare what the function returns, in this case, a number.

language:c
export function moisture(pin: AnalogPin, type: gatorMoistureType): number{
    let ADCVal = pins.analogReadPin(pin)
    switch(type){
        case gatorMoistureType.moisture: return getMoisture(ADCVal)
        case gatorMoistureType.adcVal: return ADCVal
        default: return -11111111
    }
}

Any functions in our *.cpp will need a dummy function for the simulator. We create the analog for our getMoisture function as follows. Notice how, since it isn't exported, we won't see it in MakeCode.

language:c
//% shim=gatorMoisture::getMoisture
function getMoisture(ADCVal: number) {
    // Fake function for simulator
    return 0
}

Finally, we'll need to change the final part of the README (line 49) to our namespace followed by the GitHub address, this looks like gatorMoisture=github:sparkfun/pxt-gator-soil and allows the package to be recognized as a MakeCode extension.