1
\$\begingroup\$

I enhanced the my daughter's bed light to IoT. Specifically I can turn the light on/off remotely and there is a timer & alarm function. All of this is implemented with a Particle Photon.

This review is split in two parts, where this part discusses the C++ firmware application and this question the html/js gui. As I'm not seasoned in either language I mainly look for some general feedback about code quality.

A few comments about the source:

  • The language itself is C++11 without a few features (for example tryCatch)
  • The code flow is as follows call setup once and then loop repeatedly.
  • StringStream should mimik std::stringstream with lower memory footprint.
  • String is probably best described as char[] with bells and whistles.
  • Parsing: I'm fine with setting default 0 in case of wrong input - the only thing I cannot accept is a crash of the application.

wolke.ino

#include "Particle.h"
#include "application.h"
#include "stream.h"
#include <cstdint>

/*****************************************************************
 *                        LED                                    *
 *****************************************************************/
struct LED {
    pin_t pin = TX;
    bool active = false;
    int32_t brightness = 255;

    void on() {
        active = true;
        analogWrite(pin, brightness);
    };

    void off() {
        active = false;
        analogWrite(pin, 0);
    }
} led;

StringStream& operator<<(StringStream &ss, const LED &led) {
    ss << "{\"name\":\"led\",";
    ss << "\"active\":" << led.active;
    ss << ",\"read\":" << led.brightness << "}";
    return ss;
}

int led_toggle(String cmd) {
    if (led.active) {
        led.off();
    } else {
        led.on();
    }
    return 0;
}

int set_brightness(String cmd) {
    led.brightness = cmd.toInt();
    led.on();
    return 0;
}

/*****************************************************************
 *                        Timer                                  *
 *****************************************************************/
struct TimerClass {
    int32_t initial_value = 3600;
    int32_t t0 = 0;
    bool active = 0;

    void start() {
        t0 = Time.now();
        active = true;
    };

    int32_t remainder() const {
        return initial_value + t0 - Time.now();
    };

    void check() {
        if (active && (remainder() < 0)) {
            active = false;
            led.on();
        }
    };
} timer;

StringStream& operator<<(StringStream &ss, const TimerClass &timer) {
    ss << "{\"name\":\"timer\",";
    ss << "\"active\":" << timer.active;
    ss << ",\"read\":";

    if (timer.active) {
        ss << timer.remainder();
    } else {
        ss << timer.initial_value;
    }

    ss << "}";
    return ss;
}

int timer_toggle(String cmd) {
    if (timer.active) {
        timer.active = false;
    } else {
        timer.start();
    }
    return 0;
}

int set_timer(String cmd) {
    timer.initial_value = cmd.toInt();
    timer.start();
    return 0;
}

/****************************************************************
 *                        Time                                  *
 ****************************************************************/
struct MyTimeClass {
    int32_t shift = -7;
    bool active = 1;

    int32_t hour() const {
        int32_t hour = Time.hour() + shift * active;
        if (hour >= 24) {
            hour -= 24;
        }

        if (hour <= 0) {
            hour += 24;
        }

        return hour;
    };
} local_time;

StringStream& operator<<(StringStream &ss, const MyTimeClass &local_time) {
    ss << "{\"name\":\"time\",";
    ss << "\"active\":" << local_time.active;
    ss << ",\"read\":\"" << local_time.hour() << ":";
    minute_print(ss, Time.minute());
    ss << "\"}";
    return ss;
}

int time_toggle(String cmd) {
    local_time.active ^= true;
    return 0;
}

int set_shift(String cmd) {
    local_time.shift = cmd.toInt();
    return 0;
}

void minute_print(StringStream &ss, int32_t min) {
    // Complicated form to print %02d
    if (min < 10) {
        ss << int32_t(0);
    } 
    ss << min;
}

/*****************************************************************
 *                        Alarm                                  *
 *****************************************************************/
struct Alarm {
    int32_t min = 0;
    int32_t hour = 0;
    bool active = false;

    void check() {
        if (active &&
            (Time.minute() == min) &&
            (local_time.hour() == hour)) {
            led.on();
        }
    };
} alarm;

StringStream& operator<<(StringStream &ss, const Alarm &alarm) {
    ss << "{\"name\":\"alarm\",";
    ss << "\"active\":" << alarm.active;
    ss << ",\"read\":\"" << alarm.hour << ":";
    minute_print(ss, alarm.min);
    ss << "\"}";
    return ss;
}

int alarm_toggle(String cmd) {
    alarm.active ^= true;
    return 0;
}

String CMD;
int set_alarm(String cmd) {
    CMD = cmd;
    int pos = cmd.indexOf(":");

    if (pos == -1) {
        return 1;
    } else {
        alarm.hour = cmd.substring(0, pos).toInt();
        alarm.min = cmd.substring(pos + 1).toInt();
        alarm.active = true;
    }

    return 0;
}

/****************************************************************
 *                        Main                                  *
 ****************************************************************/
String STATUS;
StringStream ss;

void setup() {
    pinMode(led.pin, OUTPUT);
    Particle.function("led_toggle", led_toggle);
    Particle.function("led_value", set_brightness);
    Particle.function("timer_toggle", timer_toggle);
    Particle.function("timer_value", set_timer);
    Particle.function("time_toggle", time_toggle);
    Particle.function("time_value", set_shift);
    Particle.function("alarm_toggle", alarm_toggle);
    Particle.function("alarm_value", set_alarm);

    Particle.variable("status", STATUS);
    Particle.variable("cmd", CMD);
}

void loop() {
    delay(10);

    timer.check();
    alarm.check();

    ss.str.clear();
    ss << "[" << led;
    ss << "," << alarm;
    ss << "," << local_time;
    ss << "," << timer << "]";
    STATUS = ss.str.c_str();
}

stream.h

#ifndef STREAM_H
#define STREAM_H

#include <string>
#include <stdio.h>
#include <cstdint>
#include <cstring>
#include <cinttypes>

/*****************************************************************
 * StringStream is a poor mans std::stringstream which can be used
 * for easy string concation.
 *****************************************************************/


struct StringStream {
    static const size_t MIN_CAPACITY = 100;
    static const size_t N_BUFFER = 20;

    std::string str;
    char buffer[N_BUFFER];

    StringStream();
    void clear();
    StringStream& operator<<(std::string txt);
    StringStream& operator<<(const char *txt);
    StringStream& operator<<(char value);
    StringStream& operator<<(double value);
    StringStream& operator<<(int32_t value);
    StringStream& operator<<(bool value);
};

#endif

stream.cpp

#include "stream.h"


StringStream::StringStream() {
    str.reserve(MIN_CAPACITY);
}


StringStream& StringStream::operator<<(std::string txt) {
    str.append(txt);
    return *this;
}


StringStream& StringStream::operator<<(const char* txt) {
    str.append(txt);
    return *this;
}


StringStream& StringStream::operator<<(char value) {
    str.append(1, value);
    return *this;
}


StringStream& StringStream::operator<<(double value) {
    memset(buffer, '\0', N_BUFFER);
    snprintf(buffer, N_BUFFER, "%e", value);
    str.append(buffer, std::char_traits<char>::length(buffer));
    return *this;
}


StringStream& StringStream::operator<<(int32_t value) {
    memset(buffer, '\0', N_BUFFER);
    snprintf(buffer, N_BUFFER, "%" PRId32, value);
    str.append(buffer, std::char_traits<char>::length(buffer));
    return *this;
}


StringStream& StringStream::operator<<(bool value) {
    str.append(1, value ? '1' : '0');
    return *this;
}
\$\endgroup\$
  • \$\begingroup\$ I don't have time to do proper review, but I can suggest you to split your first file into multiple smaller ones instead of doing "sections" - it will make it easier to find stuff. \$\endgroup\$ – Marqin Oct 14 '18 at 8:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.