Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MHZ19 CO2 sensor library #2415

Merged
merged 7 commits into from
Nov 15, 2021
Merged

Add MHZ19 CO2 sensor library #2415

merged 7 commits into from
Nov 15, 2021

Conversation

mikee47
Copy link
Contributor

@mikee47 mikee47 commented Nov 5, 2021

New asynchronous library for Sming

  • Verify UART operation using Host emulator
  • Verify PWM decoding on ESP8266 with random GPIO wiggling
  • Add callback and suspend/resume capability to PwmReader
  • Verify on real hardware

@slaff slaff added this to the 4.5.0 milestone Nov 5, 2021
@mikee47
Copy link
Contributor Author

mikee47 commented Nov 5, 2021

@Vity01 Could you try this out on your hardware?

@Vity01
Copy link

Vity01 commented Nov 6, 2021

You are the boss! Thank you. I will try it tomorrow.

@slaff
Copy link
Contributor

slaff commented Nov 8, 2021

@Vity01 were you able to test this PR?

@Vity01
Copy link

Vity01 commented Nov 8, 2021

@slaff I spent several several hours on it to get it work, I made some progress of understanding that thing, but I need more time...

@Vity01
Copy link

Vity01 commented Nov 8, 2021

I am confused with the wiring from the example.
The header file defines:

#define PWM_PIN 14

#define SERIAL_TX_PIN 2
#define MHZ19_TX_PIN 15
#define MHZ19_RX_PIN 13

but I don't see usage for the pins MHZ19_TX_PIN and MHZ19_RX_PIN
There is only Serial.begin(SERIAL_BAUD_RATE, SERIAL_8N1, SERIAL_TX_ONLY, SERIAL_TX_PIN);
It's weird the RX pin is not used at all. Any ideas?
(https://github.com/mikee47/Sming-MHZ19/blob/master/src/include/MHZ19/SampleConfig.h)

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 8, 2021

I am confused with the wiring from the example. The header file defines:

#define PWM_PIN 14

#define SERIAL_TX_PIN 2
#define MHZ19_TX_PIN 15
#define MHZ19_RX_PIN 13

but I don't see usage for the pins MHZ19_TX_PIN and MHZ19_RX_PIN.

That's correct, the definitions aren't used so they're purely for information - I'll add a comment: The call to serial0.begin() sets UART0 up with the default pins (1 & 3). The call to seria0.swap() then exchanges pin 1 with 15 and 3 with 13. This is confusing but it's just how the hardware is set up!

There is only Serial.begin(SERIAL_BAUD_RATE, SERIAL_8N1, SERIAL_TX_ONLY, SERIAL_TX_PIN); It's weird the RX pin is not used at all.

This is for debug output using UART1. The RX pin isn't accessible - it conflicts with one of the flash data lines.

@Vity01
Copy link

Vity01 commented Nov 10, 2021

This is a whole sketch I use:

#include <SmingCore.h>
#include <MHZ19.h>

#define PWM_PIN 14

#define SERIAL_TX_PIN 2
#define MHZ19_TX_PIN 15
#define MHZ19_RX_PIN 13

namespace
{
HardwareSerial serial0(0);
MHZ19::Uart mhz19(serial0);
MHZ19::PwmReader pwmReader;

SimpleTimer timer;


void initHardware()
{
	Serial.begin();
	Serial.println("Init HW");
	serial0.begin();	
	serial0.swap();
	Serial.println("Swap done");

	Serial.setPort(1);
	Serial.println("Begin second serial");
	Serial.begin(SERIAL_BAUD_RATE, SERIAL_8N1, SERIAL_TX_ONLY, SERIAL_TX_PIN);
	Serial.systemDebugOutput(true);

	Serial.println("Pwm Reader PIN");
	pwmReader.begin(PWM_PIN);
}

void takeMeasurement()
{
	Serial.println("Take Measurement");

	mhz19.getMeasurement([](MHZ19::Measurement& m) {
		Serial.print("Measurement result: ");
		Serial.println(toString(m.error));
		if(m.error != MHZ19::Error::success) {
			return;
		}
	
		Serial.print("co2 ");
		Serial.print(m.co2_ppm);
		Serial.print(", temp ");
		Serial.print(m.temperature);
		Serial.print(", status ");
		Serial.println(m.status);
	});

	/* This method of PWM reading is not recommended */
	// int co2ppm = MHZ19::pwmRead(PWM_PIN);
	// Serial.print("co2 via PWM: ");
	// Serial.println(co2ppm);
}

} // namespace

void init()
{
	initHardware();

	mhz19.setAutoCalibration(false);
	timer.initializeMs<2000>(takeMeasurement).start();

	pwmReader.setCallback([](uint16_t ppm) {
		Serial.print(_F("PWM reader says "));
		Serial.print(ppm);
		Serial.println(_F("ppm"));
	});

	Serial.println("Note: Sensor requires 3 minutes to warm up.");
}

I tried this combination of wiring

  • GND, 3V3

  • MHZ19.PWM->GPI014 (D5)

  • MHZ19.RX -> GPI02 (D4)
    as well as

  • MHZ19.RX -> GPI13 (D7)

  • MHZ19.TX -> GPI15 (D8)

In both cases there is no output to the console and the sketch is crashing probably during takeMeasurement invocation (the LED is blinking when the ESP is restarting).
Any ideas what am I doing wrong? I admit I still don't understand how to combine it. It's different against tutorials I found (because of the HWserial vs Software Serial) https://www.zive.cz/Client.Gallery/show.aspx?id_file=145800494&article=192963 (image #11)

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 10, 2021

Connections should be:

MHZ19 PWM -> GPIO14 (D5)
MHZ19 TX -> GPIO13 (D7)
MHZ19 RX -> GPIO15 (D8)
GPIO2 (D4) -> GPIO1 (TX)

Note that UART1 outputs on GPIO2, but the USB/serial chip on your nodeMCU is connected to GPIO1.
Hence we need to bridge these two pins.

I ran your code unmodified and has no crashing issues on my nodeMCU (similar to yours).
However, I'd suggest working with the MHZ19_getppm sample initially.
All the samples build, run and appear to do what I'd expect, just lacking actual hardware to test against.

Also note the blue LED on the ESP12F module is connected to GPIO2 so should flash when there's debug output being printed,
which would be every 2 seconds for this code.

If the chip is rebooting, you'd see some serial output at 74880 baud, which is what the ROM code defaults to.
You can verify with make terminal COM_SPEED=74880. (Remember to switch it back again with make terminal COM_SPEED=115200)

The serial port initialisation order is important:

void initHardware()
{
// Use UART0 for MHZ19 connection
serial0.begin(); // Starts UART0
serial0.swap(); // Switches to pins 13,15

// Use UART1 for debug output
Serial.setPort(1); // Close port (if open) and select UART1 when begin() is next called
Serial.begin(SERIAL_BAUD_RATE, SERIAL_8N1, SERIAL_TX_ONLY, SERIAL_TX_PIN);
Serial.systemDebugOutput(true);

pwmReader.begin(PWM_PIN);

}

PS. Software serial is something I'd only consider as a last resort as it's less robust than using hardware, even at relatively low speeds. Anything which relies on disabling interrupts for extended periods raises a red flag.

PPS: I messed up the PWM reader a bit in the last couple of commits, but that won't affect serial operation.

@Vity01
Copy link

Vity01 commented Nov 10, 2021

Thank you Mike for the info. We are getting close :-). I made some progress.
I pulled your code (+ clean&build) and updated the wiring.
I can confirm the diode is blinking when timer is invoked.

Now I see the output:

512067084 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1488ppm
PWM reader says 1488ppm
Take Measurement
514067004 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1488ppm
PWM reader says 1488ppm
Take Measurement
516066924 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1488ppm
PWM reader says 1488ppm
Take Measurement
518066844 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1488ppm
PWM reader says 1486ppm
Take Measurement
520066764 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1486ppm
PWM reader says 1486ppm
Take Measurement
522066684 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1486ppm
PWM reader says 1486ppm
Take Measurement
524066604 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1484ppm
PWM reader says 1484ppm
Take Measurement
526066524 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1484ppm
PWM reader says 1484ppm
Take Measurement
528066444 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1497ppm
PWM reader says 1534ppm
Take Measurement
530066364 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1534ppm
PWM reader says 1534ppm
Take Measurement
532067117 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1534ppm
PWM reader says 1570ppm
Take Measurement
534067037 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1703ppm
PWM reader says 1703ppm
Take Measurement
536066957 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1703ppm
PWM reader says 1703ppm
Take Measurement
538066877 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1733ppm
PWM reader says 1926ppm
Take Measurement
540066797 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1926ppm
PWM reader says 1926ppm
Take Measurement
542066717 [MHZ19] Checksum failed
Measurement result: Invalid Response
PWM reader says 1929ppm
Take Measurement
544066637 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
546066557 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
548066477 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
550066397 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
552066317 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
554066237 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
556066990 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
558066910 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
560066830 [MHZ19] Checksum failed
Measurement result: Invalid Response
Take Measurement
562066750 [MHZ19] Checksum failed
Measurement result: Invalid Response

The PWM value seems to be correct. When I blow at the sensor, ppm grows. But it stops to show measurement once it reaches > 2000. I am not sure if it's a bug or feature :-) (there is 2000ppm range). And as you noticed, the measurement does not work. Anything I can try?

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 10, 2021

Checksum failed means data is all wrong! Try building with verbose debugging to see what's happening:

make clean MHZ19-clean
make -j DEBUG_VERBOSE_LEVEL=3

@Vity01
Copy link

Vity01 commented Nov 10, 2021

> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e5 3d 40 42 cf fa                       ....=@B..
2066998 [MHZ19] Checksum failed
2067316 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e4 3d 40 42 cf fb                       ....=@B..
4066622 [MHZ19] Checksum failed
4066685 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e4 3d 40 42 cf fb                       ....=@B..
6066542 [MHZ19] Checksum failed
6066605 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e4 3d 40 42 cf fb                       ....=@B..
8066462 [MHZ19] Checksum failed
8066526 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e2 3d 40 42 cf fd                       ....=@B..
10066382 [MHZ19] Checksum failed
10066448 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e2 3d 40 42 cf fd                       ....=@B..
12066302 [MHZ19] Checksum failed
12066369 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e1 3d 40 42 cf fe                       ....=@B..
14066222 [MHZ19] Checksum failed
14066289 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e1 3d 40 42 cf fe                       ....=@B..
16066142 [MHZ19] Checksum failed
16066209 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d e1 3d 40 42 cf fe                       ....=@B..
18066062 [MHZ19] Checksum failed
18066130 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1420ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d de 3d 40 42 cf 01                       ....=@B..
20066816 [MHZ19] Checksum failed
20066883 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1420ppm
PWM reader says 1418ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d de 3d 40 42 cf 01                       ....=@B..
22066736 [MHZ19] Checksum failed
22066803 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1418ppm
PWM reader says 1418ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d dc 3d 40 42 cf 03                       ....=@B..
24067489 [MHZ19] Checksum failed
24067557 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1418ppm
PWM reader says 1418ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d dc 3d 40 42 cf 03                       ....=@B..
26067409 [MHZ19] Checksum failed
26067477 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1418ppm
PWM reader says 1418ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d dc 3d 40 42 cf 03                       ....=@B..
28067329 [MHZ19] Checksum failed
28067397 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1418ppm
PWM reader says 1418ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d d9 3d 40 42 cf 06                       ....=@B..
30067249 [MHZ19] Checksum failed
30067317 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1418ppm
PWM reader says 1416ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d d9 3d 40 42 cf 06                       ....=@B..
32067169 [MHZ19] Checksum failed
32067237 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1416ppm
PWM reader says 1416ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 0d d8 3d 40 42 cf 07                       ....=@B..
34067089 [MHZ19] Checksum failed
34067158 [MHZ19] Returning Invalid Response
Measurement result: Invalid Response
PWM reader says 1416ppm
PWM reader says 1416ppm

Update: I am studying protocol doc, the command is correct.
Update2:

  • first 2 bytes from the response are correct - ff 86 = response header
  • next 2 bytes should be a value of CO2 -0x0dd8 = 3544 dec , quite far from 1416
  • next 4 bytes should be zero
  • 8th byte should be a checksum = 0xFF - (1st + 2nd + 3d byte ) + 0x01

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 10, 2021

Bug in checksum calculation now fixed (missed a digit!) Didn't cause problem in testing because of zero packet bytes - just shows how ineffective that method is!

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 10, 2021

Datasheet not particular clear about range setting, but I'm sure you can figure that bit out!

@Vity01
Copy link

Vity01 commented Nov 10, 2021

We are almost there :-). I am aware of your blind programming and you are doing great job!
When I added
mhz19.setDetectionRange(DetectionRange::PPM_2000); before mhz19.setAutoCalibration(false); the values started to match.
Value from the UART is a bit slower (needs more time to get sync), but it seems to work now. I am not sure what are the CO2 numbers when this range is not set.
Idea - maybe mhz19.setAutoCalibration(true); ?

This is the output when the detectionRange is 2000. Looks correct 👍 (I have to investigate what the status value means)

Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 01 73 3e 04 5c cc 9c                       ...s>.\..
118068664 [MHZ19] Returning Success
Measurement result: Success
co2 371, temp 22, status 4
PWM reader says 373ppm
PWM reader says 373ppm
Take Measurement
> MHZ19: ff 01 86 00 00 00 00 00 79                       ........y
< MHZ19: ff 86 01 73 3e 04 5c cc 9c                       ...s>.\..
120068584 [MHZ19] Returning Success
Measurement result: Success
co2 371, temp 22, status 4
PWM reader says 373ppm
PWM reader says 360ppm
  • When the PWM reaches the limit (>= 2000), the PWM callback is not called. Is it limited by PwmReader? I have to study it how it's implemented.
    Update: I suppose it's called on value change, right?
  • Is it possible to block diode blinking?

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 10, 2021

We are almost there :-). I am aware of your blind programming and you are doing great job!

Thanks :-)

When I added mhz19.setDetectionRange(DetectionRange::PPM_2000); before mhz19.setAutoCalibration(false); the values started to match. Value from the UART is a bit slower (needs more time to get sync), but it seems to work now. I am not sure what are the CO2 numbers when this range is not set. Idea - maybe mhz19.setAutoCalibration(true); ?

All this will vary depending on the variant being used - I've seen MH-Z19, MH-Z19B and MH-Z19C so far...

If you can procure some reliable information for configuring your particular device we can add that to the documentation.

* When the PWM reaches the limit (>= 2000), the PWM callback is not called. Is it limited by PwmReader? I have to study it how it's implemented.
  Update: I suppose it's called on value change, right?

More likely the values fall out of spec. as per the PwmReader::Pulse::isValid() method. You could return true to catch everything (but watch out for possible divide by zeroes!)

Also see CYCLE_MS and CYCLE_TOLERANCE values. For wire-waggle-testing I set CYCLE_TOLERANCE to 1000 but again try widening the ranges to see what happens.

* Is it possible to block diode blinking?

Not in software. Get your soldering iron out! https://www.electrodragon.com/w/images/f/f4/Schematic_esp-12-q.png

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 10, 2021

Actually yes of course when PWM input is at maximum the pulse will be on those limits so should have a check for that and return maximum value.

@Vity01
Copy link

Vity01 commented Nov 10, 2021

Hmmms, after cca 2 hours of measurement and writing to the console it started to print just a mess (checked also with another terminal in Putty). The diode was blinking (measurement is probably working OK), but the output to the terminal/console was broken. No idea what could be wrong...

@slaff
Copy link
Contributor

slaff commented Nov 11, 2021

@mikee47 is this PR ready for merging?

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 11, 2021

@Vity01 How would you like to proceed with this PR? Do you have any further suggestions before merging it?

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 11, 2021

Hmmms, after cca 2 hours of measurement and writing to the console it started to print just a mess (checked also with another terminal in Putty). The diode was blinking (measurement is probably working OK), but the output to the terminal/console was broken. No idea what could be wrong...

Have you checked with different baud rates? Re. above comment try 74880 to see if it's rebooting. Maybe a snippet of your console dump will help diagnose what's happening.

@Vity01
Copy link

Vity01 commented Nov 11, 2021

I am trying to reproduce the "output mess" problem. So far it's working. I don't think the problem was in rebooting, just the output to the terminal.

Suggestion:
Please add mhz19.setDetectionRange(DetectionRange::PPM_2000); into init() in the sample so the result value from the Serial and PWM matches, otherwise it shows different values. Maybe a better solution is also to set pwmReader.begin(PWM_PIN, rangeSet); with const DetectionRange rangeSet = DetectionRange::PPM_2000); and to use same const with setDetectionRange()

@mikee47
Copy link
Contributor Author

mikee47 commented Nov 12, 2021

So much of this is undocumented. Have a look at https://github.com/WifWaf/MH-Z19 it handles various undocumented commands.

@slaff slaff merged commit 2350aff into SmingHub:develop Nov 15, 2021
@mikee47 mikee47 deleted the feature/mzh19 branch November 16, 2021 12:13
@slaff slaff mentioned this pull request Nov 16, 2021
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants