PWM

The reason why the PWM module was developed was to have a way to generate an analog output voltage using an external RC filter circuit. This was in order to control the fan of the ceiling-mounted air-conditioning units, whose fan controller accepts a voltage of between 0 and 10 volts.

An interesting feature of this module is that it has its own binary protocol to allow for remote control, which is how the air-conditioning service can directly control the fan speeds via the ceiling-mounted nodes:

#include "base_module.h"

#include <HardwarePWM.h>


class PwmModule {
static HardwarePWM* hw_pwm;
static Vector<int> duty;
static uint8 pinNum;
static Timer timer;
static uint8* pins;

public:
static bool initialize();
static bool start();
static bool shutdown();
static void commandCallback(String message);
};

The implementation is as follows:

#include "pwm_module.h"

HardwarePWM* PwmModule::hw_pwm = 0;
uint8 PwmModule::pinNum = 0;
Timer PwmModule::timer;
uint8* PwmModule::pins = 0;


enum {
PWM_START = 0x01,
PWM_STOP = 0x02,
PWM_SET_DUTY = 0x04,
PWM_DUTY = 0x08,
PWM_ACTIVE = 0x10
};

We define the commands that will be available with the PWM module here as an enumeration:


bool PwmModule::initialize() {
BaseModule::registerModule(MOD_IDX_PWM, PwmModule::start, PwmModule::shutdown);
}


bool PwmModule::start() {
OtaCore::registerTopic(MQTT_PREFIX + String("pwm/") + OtaCore::getLocation(), PwmModule::commandCallback);

return true;
}


bool PwmModule::shutdown() {
OtaCore::deregisterTopic(MQTT_PREFIX + String("pwm/") + OtaCore::getLocation());

if (hw_pwm) {
delete hw_pwm;
hw_pwm = 0;
}

return true;
}

When we start this module, we register the MQTT topic on which the module will be able to receive commands. When shutting down, we deregister this topic again. We use the HardwarePWM class from Sming to enable PWM on individual pins.

The rest of the module is simply the command processor:


void PwmModule::commandCallback(String message) {
OtaCore::log(LOG_DEBUG, "PWM command: " + message);
if (message.length() < 1) { return; }
int index = 0;
uint8 cmd = *((uint8*) &message[index++]);

if (cmd == PWM_START) {
if (message.length() < 2) { return; }
uint8 num = *((uint8*) &message[index++]);

OtaCore::log(LOG_DEBUG, "Pins to add: " + String(num));

if (message.length() != (2 + num)) { return; }

pins = new uint8[num];
for (int i = 0; i < num; ++i) {
pins[i] = *((uint8*) &message[index++]);
if (!OtaCore::claimPin(pins[i])) {
OtaCore::log(LOG_ERROR, "Pin is already in use: " + String(pins[i]));

OtaCore::publish("pwm/response", OtaCore::getLocation() + ";0", 1);

return;
}

OtaCore::log(LOG_INFO, "Adding GPIO pin " + String(pins[i]));
}

hw_pwm = new HardwarePWM(pins, num);
pinNum = num;

OtaCore::log(LOG_INFO, "Added pins to PWM: " + String(pinNum));

OtaCore::publish("pwm/response", OtaCore::getLocation() + ";1", 1);
}
else if (cmd == PWM_STOP) {
delete hw_pwm;
hw_pwm = 0;

for (int i = 0; i < pinNum; ++i) {
if (!OtaCore::releasePin(pins[i])) {
OtaCore::log(LOG_ERROR, "Pin cannot be released: " + String(pins[i]));

OtaCore::publish("pwm/response", OtaCore::getLocation() + ";0", 1);

return;
}

OtaCore::log(LOG_INFO, "Removing GPIO pin " + String(pins[i]));
}

delete[] pins;
pins = 0;

OtaCore::publish("pwm/response", OtaCore::getLocation() + ";1");
}
else if (cmd == PWM_SET_DUTY) {
if (message.length() < 3) { return; }

uint8 pin = *((uint8*) &message[index++]);
uint8 duty = *((uint8*) &message[index++]);
bool ret = hw_pwm->setDuty(pin, ((uint32) 222.22 * duty));
if (!ret) {
OtaCore::publish("pwm/response", OtaCore::getLocation() + ";0");

return;
}

OtaCore::publish("pwm/response", OtaCore::getLocation() + ";1");
}
else if (cmd == PWM_DUTY) {
if (message.length() < 2) { return; }

uint8 pin = *((uint8*) &message[index++]);
uint32 duty = hw_pwm->getDuty(pin);

uint8 dutyp = (duty / 222.22) + 1;
String res = "";
res += (char) pin;
res += (char) dutyp;
OtaCore::publish("pwm/response", OtaCore::getLocation() + ";" + res);
}
else if (cmd == PWM_ACTIVE) {
String res;
if (pins && pinNum > 0) {
res = String((char*) pins, pinNum);
}

OtaCore::publish("pwm/response", OtaCore::getLocation() + ";" + res);
}
}

The protocol implemented by the preceding method is the following:

Command

Meaning

Payload

Return value

0x01

Start the module

uint8 (number of pins)

uint8* (one byte per pin number)

0x00/0x01

0x02

Stop the module

-

0x00/0x01

0x04

Set the PWM duty level

uint8 (pin number)

uint8 (duty cycle, 0 - 100)

0x00/0x01

0x08

Get the PWM duty level

uint8 (pin number).

uint8 (duty level)

0x10

Returns the active pins

-

uint8* (one pin number per byte)

For each command, we parse the string of bytes we receive, checking the number of bytes to see whether we get the expected number, and then interpreting them as commands and their payload. We either return a 0 (failure) or a 1 (success), or a payload with the desired information.

One obvious addition that could be made here would be to add some kind of checksum to the received command, along with sanity checks on the received data. While code like this will work great in a secure environment with encrypted MQTT links and a reliable network connection, other environments may be less forgiving, with corrupted data and false data being injected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.5.68