Project ideas that are currently work in progress:
- a door lock
- a furnace (central heating) controller
Thank you for all the help that I received on
Better C++
Discord server!
00001H#8295
alex88#9252
Alvin121#2883
Arno#7285
Doctor Bazooka#2461
Droid33#7264
DXPower#0133
Felix#6325
Fluffles#6709
Jérôme#1807
Kev#3440
Pickleas Cage#4209
SlyBach#4412
The Dreams Wind#1949
Thank you for all the help that I received on
PlatformIO Community
forum!
- Building environments:
nodemcu
to upload toNodeMCU DevKit 1.0
,nodemcu_ota
for WiFi uploadsnative
to run unit tests for pure C++,native_arduino
to run unit tests with Arduino framework mocked.
- In general,
pio run
buildssrc
dir, whilepio test
buildstest
dir. rm -rf .pio
– a quick way to get rid of thePIO
's generated files.pio init --ide clion
-
pio run -e nodemcu
andpio run -e native
both buildsrc
directory, but with a given environment config. -
pio run -t upload
builds and uploads to the board. -
pio run -t monitor
– serial monitor (over USB cable) -
pio run -t upload -t monitor
– upload and monitor combinedWhen serial monitor is active, the code cannot be uploaded!
That is why we need to combine this into one command, instead of having the monitor always in the background.
-
pio run -e native
builds native (desktop / command line) env. -
pio run -e native -t exec
builds and runs the native "app".
-
pio test -e native
builds and runs tests intest/native/
directory. -
pio test -e nodemcu
basically does nothing, as it would build tests do deploy on the embedded device. -
TDD workflow: watches for file changes and runs test(s) immediately, in a continuous loop.
./scripts/tdd.sh native/Foo/test_Foo
– pass path to the test as the first argument.- It is best to configure
External Tools
andKeymap
inClion
to runscripts/tdd.sh
. - See more info inside the file.
pio check
- door lock open/close by website
- automatically reconnects to Wi-Fi after lost connection
- indicates lost Wi-Fi connection by blinking led
- physical buttons to open/close
- servo cutoff power circuit for energy saving
Use one pin with the servo library to control the position and use another the other pin with a MOSFET (/(npn switching) transistor (like 2N4401) / relay) (or other higher current driver) to turn the power to each servo on and off as you need to.
- door lock should work without Wi-Fi (i.e. being closed by default)
- configure
pio check
so it does not analyse external libraries https://docs.platformio.org/en/latest/core/userguide/cmd_check.html - serial monitor output colors
- To be used eventually?
std::unordered_map<key type, value type>
- Configure GH Workflows CI
- configure simulator(s)
- update and test fix
- NodeMCU has 2 built–in LEDs -> I'm only using one for now, maybe I could use both.
- https://lowvoltage.github.io/2017/07/09/Onboard-LEDs-NodeMCU-Got-Two
- https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
- https://rabbithole.wwwdotorg.org/2017/03/28/esp8266-gpio.html
D0
/GPIO16
- on board
high
at boot, falls tolow
after ~110msUSER
: whatever that meansWAKE
: used to wake up from deep sleep- input: no interrupt
- output: no PWM or I2C support
- low output level isn’t the expected
0V
, but rather about1V
- it is "inverted" – LED is bright on
low
, not onhigh
D4
/GPIO2
- on chip
high
at boot, according to 1; varies at boot and stabilizes athigh
~70ms after boot, while can be set tolow
~110ms after boot, according to 2TXD1
: whatever that meansU1TXD
: it is probably the reason it flashes during chip programming- boot fails if pulled LOW
- input: pulled up
- output: OK
- it is "inverted" – LED is bright on
low
, not onhigh
- it is the
LED_BUILTIN
in the source code - I'm using this one currently to indicate app's status.
- LED brightness can be regulated with
analogWrite
, instead of just turning it on withdigitalWrite
- PWM must be supported on the PIN
- For the loop indicator, I could use a soft heartbeat blinking, while using hard–bright blinking for error indication.
- Must remember:
digitalRead
andanalogRead
give "wrong" results afteranalogWrite
– if I want to track the state, I have to do it with a code var.
Most of the stuff, that could be done, takes precious resources: time and hardware.
This project's focus is on learning and experimenting. It is more important to me that I learn stuff doing it, than that I achieve some incredible, polished result.
Another thing is that microcontroller hardware is very limited. There is always a tradeoff between memory and speed. AND code quality.
- Super pretty web design
- It is not a commercial project, that I'd want to advertise as having a shiny interface.
- It has to work and be easy to use, not necessarily be pretty.
- To spend more time on the looks, means to spend less time on the workings.
- Support for old web browsers
- I don't use old web browsers, you shouldn't either (security reasons for starters).
- Support for disabled Javascript
- It would be nice to have, but it would de facto mean doing and testing yet another UI, that I would not actually use, after all.
- Support for microcontroller boards I don't have or plan to use.
- i18n – English only (and maybe Polish)
- 4 spaces for indents
- Better have more lines, but keep them short
- What can be tested (and I know how), shall be tested (I mean automated testing ofc)
OOP
,SOLID
explicit
constructors (almost?) everywhere- Always using
{}
for initialization, not()
- Marking classes as
final
, unless I actually need to extend over them - One class definition per file
- With the exception for internal, NOT reusable classes: so class in a class is allowed.
- Header files only, where possible
.h
are enough most of the time to what I do.- Potential slower compilation does not bother me (it is still quick enough).
- It sucks to maintain two files (
.h
and.cpp
) for each class I make. - Readability is not a problem when you keep your classes short,
and doing one thing only, as required by
SRP
.
- Using
PROGMEM
everywhere I can (keeping strings in Flash, instead of RAM)- Everywhere I can, meaning the Arduino–related code, skipping for generic C++ code
F
macro is not needed,PSTR()
macro is enough.- Even for the code that is not Flash storage aware.
- At least on ESP-8266 it works.
- It is Von Neumann architecture
- there is one address space for both instructions and data.
- It is Von Neumann architecture
- If I'd ever want to make it compatible with the "real" Arduinos, therefore having Harvard architecture, where instructions and data are separate, the code would have to be strongly redesigned anyway.
- There are a few places that
PSTR()
does not work, hence usingF()
or nothing.- They have a todo to find out why it does not work.
ArduinoJson
lib crashes onPSTR()
, but works withF()
.
- At least on ESP-8266 it works.
- Even for the code that is not Flash storage aware.
- A space between closure's context and body:
auto closure = [] () {};
- A comment, explaining why the header is being included, when it isn't obvious from the header's name:
#include <memory> // std::make_unique
ArduinoJson
StaticJsonDocument
when recommended (<1KB and "predictable") -> with version6
I'm forced to hardcode/calculate the size anyway, soDynamicJsonDocument
does not really ease the usage- It crashes on
PSTR()
, so using the "proper"F()
instead -> todo: maybe I should get rid of it anyway? just use CStrings? - Serialization error handling policy: checking for overflow (
jsonDocument.overflowed()
)
const
methods vsmutable
- https://isocpp.org/wiki/faq/const-correctness#logical-vs-physical-const
DnApp::Logger::Logger
Logger
implementations obviously can have (some of them do have) internal state to manage- -> to stay truthful with their nature, their methods are not marked
const
. - -> to allow that, I don't specify
const
for the abstract base class methods as well.
- -> to stay truthful with their nature, their methods are not marked
- On the other hand, the userland objects (services) might log or not
-> whether they do or not, it does not logically change the service's state,
-> therefore the internal
Logger logger
field might be marked asmutable
. - https://discord.com/channels/583251190591258624/1088209923302768691/1089911377151397918
- AAA: Almost Always Auto
- Integer types
- Prefer the (size) explicit options (like
uint8_t
), over the unspecified (likeint
). - Use Arduino specific types (like
byte
) only for code directly dealing with such requirements from the Arduino framework. - Watch out for sizes on different platforms!
- Can be tested in this way:
// compiles in the `nodemcu` env, does not on _(my)_ `native` env static_assert(sizeof(unsigned long) == sizeof(uint32_t));
byte
/unsigned byte
:nodemcu
:int8_t
/uint8_t
(-128
–127
/0
–255
)native
: ~
short
/unsigned short
:nodemcu
:int16_t
/uint16_t
(-32,768
–32,767
/0
–65,535
)native
:int16_t
/uint16_t
(-32,768
–32,767
/0
–65,535
)
int
/unsigned int
:nodemcu
:int32_t
/uint32_t
(-2,147,483,648
–2,147,483,647
/0
–4,294,967,295
)native
:int32_t
/uint32_t
(-2,147,483,648
–2,147,483,647
/0
–4,294,967,295
)
long
/unsigned long
:nodemcu
:int32_t
/uint32_t
(-2,147,483,648
–2,147,483,647
/0
–4,294,967,295
)native
:int64_t
/uint64_t
(large^^)
- Can be tested in this way:
- Prefer the (size) explicit options (like
// todo:[2137] Description of what is to be done
– the number is for easier grep, when the same kind of issue appears in multiple places.static_cast
tovoid
when function arguments are not being used. I know of 3 possible solutions:- A.)
static_cast<void>(unusedParameter);
– the preferred C++ way, that we're doing, - B.)
(void) unusedParameter;
– the "old" C–style casting, butA.)
is more precise, - C.) remove
unusedParameter
var from the function arguments – but keeping the name better explains what it represents.
- A.)