ESP32 DIY Smarter Home – Collect Measurements

Before we add the BME280 environmental sensor I wanted to report basic information from the processor without having to connect anything else. The ESP32 actually has a hall effect sensor, as explained here. This sensor allows the processor to sense the presense of a magnetic field.

Project changes

I made a few changes to the project:

  • Renamed the project as esp32-sensor
  • Added a test directory for unit testing
  • Added a measurements library (created as a library to allow for unit testing the library)
  • After finding this post by Pierre Vernaeckt, I went ahead and added CI testing with Gitlab. Thanks!
  • Added .gitlab-ci.yml to allow for automated continuous integration with Gitlab.
  • Added a ci folder to host cppcheck.sh from the aff3ct library.

meas.cpp is fairly simple (cpplint does not know that platformio can find meas.h):

#include <Arduino.h>
#include "meas.h" //NOLINT(build/include_subdir)

Meas::Meas() {
    //String
}

String Meas::get_hello() {
    data = "Hello World!";
    return data;
}

int Meas::get_hall() {
    return hallRead();
}

meas.h:

#ifndef LIB_MEASUREMENTS_MEAS_H_
#define LIB_MEASUREMENTS_MEAS_H_

#include <Arduino.h>

class Meas {
    public:
        Meas();
        String get_hello();
        int get_hall();
    private:
        String data;
};

#endif  // LIB_MEASUREMENTS_MEAS_H_

main.cpp:

#include <Arduino.h>
#include "meas.h"  //NOLINT(build/include_subdir)

Meas meas;

void setup() {
  // put your setup code here, to run once:
  meas = Meas();
  String greeting = meas.get_hello() + "\n";
  printf("%s", greeting.c_str());
}

void loop() {
  // put your main code here, to run repeatedly:
  printf("Mag: %d, time: %lu\n", meas.get_hall(), millis());
  delay(1000);
}

And test_main.cpp, which is mostly a copy and paste from here. The only exception is that I’ve added a unit test for the hall effect sensor measurement function.

#include <Arduino.h>
#include <unity.h>
#include "meas.h"

String STR_TO_TEST;

void setUp(void) {
    // set stuff up here
    STR_TO_TEST = "Hello, world!";
}

void tearDown(void) {
    // clean stuff up here
    STR_TO_TEST = "";
}

void test_string_concat(void) {
    String hello = "Hello, ";
    String world = "world!";
    TEST_ASSERT_EQUAL_STRING(STR_TO_TEST.c_str(), (hello + world).c_str());
}

void test_string_substring(void) {
    TEST_ASSERT_EQUAL_STRING("Hello", STR_TO_TEST.substring(0, 5).c_str());
}

void test_string_index_of(void) {
    TEST_ASSERT_EQUAL(7, STR_TO_TEST.indexOf('w'));
}

void test_string_equal_ignore_case(void) {
    TEST_ASSERT_TRUE(STR_TO_TEST.equalsIgnoreCase("HELLO, WORLD!"));
}

void test_string_to_upper_case(void) {
    STR_TO_TEST.toUpperCase();
    TEST_ASSERT_EQUAL_STRING("HELLO, WORLD!", STR_TO_TEST.c_str());
}

void test_string_replace(void) {
    STR_TO_TEST.replace('!', '?');
    TEST_ASSERT_EQUAL_STRING("Hello, world?", STR_TO_TEST.c_str());
}

void test_string_get_hello(void) {
    String hello = "Hello World!";
    Meas meas = Meas();
    String output = meas.get_hello();
    TEST_ASSERT_EQUAL_STRING(output.c_str(), (hello).c_str());
}

void test_int_get_hall(void) {
    Meas meas = Meas();
    int hall = meas.get_hall();
    TEST_ASSERT_INT16_WITHIN_MESSAGE(10, -20, hall, "Magnetic field detected during testing!");
}

void setup()
{
    delay(2000); // service delay
    UNITY_BEGIN();

    RUN_TEST(test_string_concat);
    RUN_TEST(test_string_substring);
    RUN_TEST(test_string_index_of);
    RUN_TEST(test_string_equal_ignore_case);
    RUN_TEST(test_string_to_upper_case);
    RUN_TEST(test_string_replace);
    RUN_TEST(test_string_get_hello);
    RUN_TEST(test_int_get_hall);

    UNITY_END(); // stop unit testing
}

void loop()
{
}

Adding Hall Effect measurements

To add Hall effect measurements all that you need to do is to call hallRead() to get the current hall effect measurement.

Unit Testing

With the test_main.cpp added to the test folder, we can now run unit tests on the actual hardware by running “Test” within the PlatformIO menu. Eventually I would like to integrate CI testing with hardware in the loop, but for now we can run it locally.

Gitlab Continuous Integration

With CI enabled, this project is now built and tested against cpplint, flawfinder, shellcheck, and cppcheck. If all of those tests pass then the binary is built using PlatformIO and a build artifact is created. Eventually I would like to get this integrated with an OTA update server so that the latest artifact is deployed on all of the sensors.

.gitlab-ci.yml:

# Different stages the CI/CD pipeline will go through, in order.
# If more than one job is located

# References:
# - https://github.com/SonarOpenCommunity/sonar-cxx/blob/master/sonar-cxx-plugin/src/samples/SampleProject2/Makefile

stages:
  - test
  - unit_test
  - build

#documentation:
#  stage: document
#  image: ubuntu:latest
#  script:
#    - apt-get update
#    - apt-get install -y doxygen
#    - doxygen documentation/doxyfile
#    - tar czf documentation.tar.gz documentation/doxygen/html/
#  artifacts:
#    paths:
#      - documentation.tar

cpplint:
  stage: test
  image: python:latest
  script: 
    - python -m pip install cpplint
    - pwd
    - cpplint --filter=-whitespace,-legal/copyright,-readability/multiline_comment --linelength=180 --recursive src/* include/* lib/* > cpplint.txt
  artifacts:
    paths:
      - cpplint.txt

flawfinder:
  stage: test
  image: python:latest
  script: 
    - python -m pip install flawfinder
    - flawfinder --html . > flawfinder.html
  artifacts:
    paths:
      - flawfinder.html
  allow_failure: true

shellcheck:
  stage: test
  image: koalaman/shellcheck-alpine
  script: 
    - shellcheck --version
    - for file in $(find . -type f | grep *.sh); do shellcheck --format=gcc $file; done;


cppcheck:
  stage: test
  image: ubuntu:latest
  script:
    - apt-get update
    - apt-get install -y cppcheck
    - chmod +x ci/cppcheck.sh
    - ./ci/cppcheck.sh
  artifacts:
    paths:
      - cppcheck

build:
  stage: build
  image: python:latest
  script: 
    - pip install -U platformio
    - platformio run -e ezsbc
    - mv .pio/build/ezsbc/firmware.bin firmware_ezsbc.bin
  variables: {PLATFORMIO_CI_SRC: "src/main.cpp"}
  artifacts:
    paths:
      - firmware_ezsbc.bin

To build it in the cloud, I rearranged the repo and changed the project name to ezsbc in platformio.ini:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:ezsbc]
platform = espressif32
board = esp-wrover-kit
framework = arduino
monitor_speed = 115200
build_flags = -DCORE_DEBUG_LEVEL=5

That’s all for now! The full project can be found here.