SuperHouse #40: DIY air quality sensor part 3 - software
Vložit
- čas přidán 25. 08. 2024
- * www.superhouse...
* www.superhouse...
It's surprisingly easy to make your own simple air quality sensor. All you need is a cheap laser-scattering particulate matter sensor, a Wemos D1 Mini, a soldering iron, and Tasmota.
Part 1 showed how to make the simplest possible air quality sensor. Part 2 extended the project to add a 128x32 pixel OLED display and a mode button, and to install custom firmware to make the sensor last longer. Part 3 is a detailed walkthrough of the source code.
See the link above for a step by step guide.
SuperHouse:
- www.superhouse.tv
- / superhousetv
- / superhousetv
Jonathan Oxer:
- / jonoxer
- / jonoxer
Support me on Patreon to receive exclusive discount codes, access to Patron-only sections of the SuperHouse Discord, and early access to new episodes: / superhouse
Oops, I mis-spoke when I was complaining about Hungarian notation: technically C isn't a "strongly typed" language, but I think my basic point still stands and the type system will catch many problems related to variable mis-assignment.
C most certainly *IS* strongly typed, but this is obscured by it providing access to the underlying "raw" or "machine hardware" types. It is the over-use of these "generic" types that weaken type use in C. The best way around it is to make heavy use of typedef to create separate, specific types for each need. See my comment further below for details why this is important (and free) to do.
I agree about Hungarian Notation, but not for your reasons Every modern IDE will display the type of a variable by placing the cursor on/in/next-to the name: Let the IDE do its job! If your IDE lacks this feature, stop using it! I heartily recommend vscode (Visual Studio Code, code.visualstudio.com/, which confusingly is not "Microsoft Visual Studio") for pretty much every language on every development platform for every target system. Yes, vscode also speaks Arduino: github.com/Microsoft/vscode-arduino
Don't shorten this kind of instruction videos just because you feel it takes "Forever". The length is perfect !!
Yea it's a good amount of info per minute. It isn't like a lot of teachers who will sit on a function for 15 minutes. Like at 33:00 he cruises through several things. a lot of teachers would sit and tell you what String() does for the 300th time
Using a config.h make much more sense to me now. Thank you.
Thank you for the detailed code structure explanations. I've trying to learn how to modify sketches that I discover.
Finally, the multiple tab variety is now clear!
OMG, this is exactly what I want to do with that sensor, thank you so much!
Outstanding!!! Definitely keep up doing these videos! Not only closes the loop on the project but also tell the choices/decisions made! Appreciate the hard work and percerverance necessary for putting all this together nicely.
The world's best teacher
Hey, good Sir! Loved this tutorial!
Thanks for that reminder at around 2/3. Found myself rapt with an empty ipa in hand following your code@ 0.75x, multiple rewinds. The reverse constant code style: It's weakly typed., so isn't there a risk of redefinition of the constant? or is the compiler our prude and catching that so well explained almost at the end. Want to build a biometric that senses alcohol consumption, net ppm in blood and count of ipa's openned .
I've built it, and with some tweaks it's working great now 👍
I've added a button to be able to change the update interval on the fly.
Also, I noted a bug in your code; The MQTT messages are spammed with dozens of messages per second.
Reason;
there is a check "if (true == g_pms_ppd_readings_taken)" which is neat, but after reporting, this variable has to be set to false again, which does not happen.
The variable will become true again during the next reading, and then it's again one mqtt messages, etc..
I've altered lots of this in the source, but I think the mqtt message spamming is solved by adding these two lines at the end of the loop() function:
g_pms_ppd_readings_taken = false;
g_pms_ae_readings_taken = false;
Thanks..! very well explained..!
Very thorough, well done!
Lots of good C/C++ pointers!
I see what you did there!
Interesting code explanation and really one of the most easy to read codes I’ve seen so far. I plan on making one of those but using a bigger oled display (one of those 2.42 inch I think they were) because my wife has a bad eyesight. Given your code this should not be to difficult I think. And of course I need to modify the case to fit that screen.
I look forward to seeing the result, that sounds like a great idea :-) I have a version with a 240x240 pixel colour TFT coming up in part 5.
Thank you, this project is awesome! I have had a PMS5003 sitting around for a while however just haven't had the time to write the code. Well, you kind if did the exact thing I was planning on doing. :-) I'm going to port this code to Atmega and use Ethernet. Two minor suggestions though. For serial debugging, use the F("some text here") macro to store all those strings in flash, not RAM. It will save most of the RAM you use. Additionally avoid the String class and instead use cstrings. String had a tendency to fragment RAM and is a major source of memory leaks in the Arduino framework.
feedback for code (code compacted):
----
For local variable in global scope prefix with static
----
I would prefer to use an enum for the states:
typedef enum {state1, state2, state3} states_t;
static states_t mystate;
----
For use struct for grouping:
typedef struct {
uint16_t pm1p0;
uint16_t pm2p5;
uint16_t pm10p0;
} stdParticle_t;
static stdParticle_t stdParticle; //is per default initialized to 0
You should minimize the number of global variables. The stdParticle_t could be moved a "particle-sensor.h"
----
g_mqtt_message_buffer[150]; Its normaly bad practice to use constants like 150 in the code , define the constants
-----
g_current_mode_button_state should not be global, is only used in a local function
----
Separate the functionality i more files: example one for the particle-sensor "particle-sensor.cpp"
make the updatePmsReadings() return a boolean to indicate if all readings been taken.
Great suggestions from someone who obviously knows what they're talking about. Which I don't.
@@SuperHouseTV I do not agree, you actually know what you are doing. Not many are following some code guideline like you do, in stead of throwing some code together. All code can be improved by having someone inspecting it and make feedback, and I thought that your code deserved it. So keep on improving and learning and follow some guidelines. You have succeeded if its easy to read your own in a year.
Actually I am following your sensor project and waiting for my sensor to arrive in the near future. Looking forward to your next video.
Super interesting! Thanks for this video! I am currently finding my way with domoticz and want to use a few of these.
Nice code description...cheers.
With the case statement and default, the same can be said of the 'if' or 'else if' statement should always be completed by an 'ELSE'. Have you looked at Mirsa C coding standard. Great code review.
Good point. I haven't seen that coding standard, thanks for the tip. I'll check it out :-)
Can't you define MQTT_MAX_PACKET_SIZE in your file above when you include the library and have it will override the PubSubClient value? The PubSubClient library looks to only use the default 128 value if MQTT_MAX_PACKET_SIZE hasn't been already defined.
Great series of videos.
Wow, you may be right! I'm going to check that for sure. I thought it was impossible to overwrite because the Arduino pre-processor does the libraries first, but I really hope that's not the case.
Was gonna say exactly the same thing. Have done that in the past. Use "--DMQTT_MAX_PACKET_SIZE=1024" to define it when running the compiler. Very easy when using Platform.io to compile/build. Not sure about the Arduino IDE
Is there any chance to use ESPHome to bring this software into the processor ?
Does your "events" status topic get messages from every device on wakeup including waking from DEEPSLEEP? I did something similar (for my temperature sensors that go to sleep) but was looking at it sending messages every time from deepsleep and was curious if there's a way around that. As far as I can see there isn't since it's basically just rebooting it with the pin
Great question, and yes, that's what happens. If you have an RTC attached you could have that message happen conditionally, but the internal millis() counter is reset along with everything else during wakeup so you can't use that to check how long it's been. One nasty option is to ready millis() right before going to sleep, and add it to a runtime accumulator stored in flash. On wakeup, check how much runtime has been accumulated. Or instead of runtime, it could be a simple counter of how many reboots have been seen since the last report was sent. Clear and start the counter again when required. If anyone has a nice solution to this, I'd love to hear it.
@@SuperHouseTV Ah I see, yea I think I read through something of a "hacky retain data through deepsleep" like that but it gave me nightmares so I just went with the msg every reset instead.. for now. RTC method is more understandable to me at least I might try it
Very well done, love this video & thank you for all the information, definitely going to help my coding. I do have one question about "Serial.begin(xxxx)". When you insert all these Serial.println in your code does it slow down the loop even if the board is not plugged in to the USB. Does it make any difference?
It would make a tiny difference having those in there, but nothing that you'd care about. It pushes the message into the serial buffer, which is never processed so the message doesn't go anywhere. It should run at the same speed whether USB is connected or not.
Is it possible to have measurements in two decimal points instead of only start from 1? For example 0.01 micro gram/m3
*Super interesting. Really kicked me to do my own. Thank you!*
Question: What needs to be changed, or can I use PMS7003 sensor without any changes in source please?
Just another thought, main program has a loop that never ends, at 17:51 you call a client.loop(). in Arduino terms a loop never breaks it's the main running code. So calling a loop would appear to call a routine that will never come back to the calling process. Would it have given more clarity to the user if were to call a routing called something like, client.process_message_data().
That method is defined in the PubSubClient library, so it's not something that I named.
In you video, you said that you would leave a link to the Embedded C programming guide, however, I don't see it anywhere. Perhaps I have missed it
In the description is a link to the SuperHouse page for this video. On that page are links to resources, including the source code and the programming guide.
@@SuperHouseTV Got it, thanks
Great video, I picked up a few useful tips. Any reason why you used ESP.getChipId() and not WiFi.macAddress()
?
I don't have a strong preference, that would work fine too :-)
@@SuperHouseTV The ESP MAC address can be set to any value desired! I would not assume anything about the uniqueness of it's value.
Being able to set the MAC address in IoT devices greatly simplifies a couple of niche networking issues that most end-users don't care about, but that every single IoT device manufacturer on the planet most certainly DOES care about! For example, many vendor programs are able to discover their devices on your network even if they don't yet have IP addresses assigned. They do this by knowing the MAC address range they use for their devices, then using ICMP/ARP to scan through that MAC address space.
In my own HA system, I move all MAC addresses **away** from the factory assigned value for that exact reason: I want to "own" my devices and limit their discovery by others. Any device lacking a modifiable MAC address gets returned. Still, I do record and preserve the original MAC address in case I return, sell or give the device away. This is far more work than most will want to do for themselves, but I'm paranoid about security, to the point that it's more of an addictive game for me.
As an example, my router sends me an email whenever a new MAC address shows up on my network. If it is on my wired network, that MAC address is immediately blocked at the switch until I explicitly add it to the "permitted" list. There is no need for this on WiFi, which has decent security (when used correctly).
How are you mate, I really appreciate the information you post and the way you do it. thank you very much, I hope you can give some insight on an idea that I want to make, my garage door is fully sonoffed using sonoff sv flashed with ESPhome with an addition of an ultrasonic sensor to detect if the car is parked or not. all work great and as expected but I need to add a very important safety measure, I want the door to stop functioning if an object is in its way, like my pet crosiing the garage door while it is operating or my car is parked under the door but not fully inside the garage, I want to prevent the door hitting the car if the remote is activated by accident, can I do that with what I have already, like some configuration inside the sonoff sv bearing in mind I have already used GPIO 4, 5 and 14. Or do I need to add a sonoff basic so its relay stops the garage motor from working based on another ultrasonic sensor reading that measures the door horizontal gap? and if so how, please? Your insight would be greatly appreciated. Have a nice day
Just curious. What is the difference between your "Hello World" alive message and using a LWT?
Good question. The LWT is intended for a very different purpose: it's to determine what happens when a device disconnects from the broker, not when it first connects. I'm using the "status" topic kind of like a system log.
@@SuperHouseTV Thanks for that. Makes sense.
Sneaky inside out shirt change
I once wore pants inside out for like 20 hours in my shop before noticing lmao.
lol got me, didn't even pick up on it. I was too busy admiring the Rembrandt on the wall.
Why you didn’t add the user and password for MQTT? In my home assistant I have it with user and password. Thank you for the video, very nice!!!
No particular reason, I just didn't do it in this example. I should add them as options. It's a good idea.
Hey I notice your shirt... :-)
Great video anyway.
Thank you.
I hope someone can help me out.......when i try to compile in arduino IDE i get a lot of error messages, the first being line 122 and the error is: 'WifiClient' does not name a type
Does anyone know how i can fix this?
Curious why you use #defines for your state machine states rather than an enum (or my preferred an enum class and a switch case)? Achieves the same thing of course, just interested!
No specific reason. I've used enum for this purpose other times. I'm just fickle. I'm also not a programmer, so I'm fudging my way through things.
Some minor nits:
1. Having uint32_t and similar "naked" types everywhere makes it seem all such variables are "the same thing". They are not the same thing! They only share a common underlying representation, with very different meanings. That is, the difference between syntax and semantics. Liberal use of typedefs can make code not only more readable (especially improved function signatures), but also give the compiler more hints to help catch incorrect assignments, and also force the programmer to use explicit casts when intentionally breaking type boundaries, even when the underlying machine type may be the same. And it comes with no cost to the final code speed or size (it's free!).
2. #defines are EVIL! Especially when used for machine states. Enums are much more practical and type-safe, and again it lets the compiler help keep things correct, keeping like combined with like. Again, at no cost to the final code speed or size (it's free!).
3. Avoid using naked 'if' tests ("if (x)..."), where the reader of the code must remember what the underlying representation of true and false are in the current context. This becomes very problematic when multiple languages are used. For example, I use C for tiny embedded systems, and Python for large-scale data analysis from suites of tiny systems. A better approach is to ALWAYS explicitly use a logical comparison operator (>, =,
we are migrating from Arduino to VSCode with platform.io
Whoever smelt it delt it