Ik zie dat er een aantal verkeerde aannames voorbij komen omtrent de UART bus.
Op het moment dat je de geintegreerde UART controller van je microcontroller gebruikt, is deze
altijd data aan het lezen. Zodra de controller data ontvangt wordt dit in een buffer gestopt. Vervolgens is het aan de gebruiker om deze data uit de buffer te halen. Dit is ook wat de cm1106_sensor.h driver van @
hsmade doet. De polling interval die je in de .yaml instelt geeft eigenlijk alleen maar aan hoevaak je die buffer gaat uitlezen. En dit is waar het mis gaat.
De driver die eerder gedeeld is checked niet of er daadwerkelijk data in de buffer klaar staat om gelezen te worden. Als de driver een read van de buffer doet, terwijl er geen data in zit, zal de onderliggende UART driver de volgende error geven:
code:
1
| [E][uart:015]: Reading from UART timed out at byte 0! |
Dit gebeurd dus als de polling interval kleiner is dan het interval waarmee de co2 sensor berichten uitstuurt.
Op het moment dat je de polling interval groter maakt krijg je ook een probleem. Uit mijn bevindingen stuurt de co2 sensor elke 2 seconde een bericht op de UART bus. Stel je leest de buffer elke 30 seconden uit omdat je dat genoeg lijkt. Dan wordt er voor elk bericht dat je uit de buffer leest, er 15 in geschreven. Je krijgt dus een delay op je meting die snel oploopt. Op een gegeven moment zit de buffer vol en heb je de maximale delay bereikt. Bij esphome staat de buffer grootte standaard op 256 bytes ingesteld. Een bericht van de Co2 sensor is 8 bytes groot wat betekend dat je er 32 van kan opslaan. Dan loop je op een gegeven moment 32*30=960 seconde = 16 minuten achter op de huidige waarde. Hoe groter de polling interval, hoe groter dit probleem wordt. Dit is niet wat je wil.
Gelukkig is er tegen beide problemen iets te doen. De UART driver heeft ondersteuning voor de volgende functie:
Hiermee kun je zien hoeveel bytes er zijn opgeslagen in de buffer. Hiermee kan een conditie gemaakt worden dat de CM1106 driver de buffer niet moet uitlezen als hij leeg is. Ook is het mogelijk om erachter te komen hoeveel berichten er in de buffer zitten, zodat de laatste waarde is uit te lezen.
Verder is het mogelijk om in de .yaml config de UART buffer grootte aan te passen. Zoals eerder gezegd is deze 256 bytes groot. Dit betekent dat er 64 seconde aan berichten in past. Wil je een polling interval die groter is dan dat, moet je de buffer grootte zo instellen dat het past. Stel je wilt een polling interval van 15 minuten (900 seconde), dan heb je minimaal een buffer nodig van: (900 / 2) * 8 bytes = 3600 bytes.
Ik heb de CM1106 driver opgeschoond om deze features toe te voegen. Zie de source code hieronder. Ik ben nog van plan het netjes in github in te checken. Doe ik vanavond misschien wel.
(EDIT 23-03-2024): Toekomstige driver updates zullen voortaan in
deze github repository geplaatst worden. De onderstaande driver laat ik hier staan voor het geval de link ooit dood gaat, maar het is mogelijk dat er een nieuwere versie van de driver beschikbaar is!
C++: cm1106_sensor.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
| // put this file in your esphome folder
// protocol implemented as described in https://en.gassensor.com.cn/Product_files/Specifications/CM1106-C%20Single%20Beam%20NDIR%20CO2%20Sensor%20Module%20Specification.pdf
#include "esphome.h"
class CM1106 : public UARTDevice {
public:
CM1106(UARTComponent *parent) : UARTDevice(parent) {}
int16_t getCo2PPM() {
// The expected response consists of 8 bytes
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
// | HEAD | LEN | CMD | DATA1 | DATA2 | DATA3 | DATA4 | CS |
uint8_t response[NUM_MSG_BYTES] = {0};
// All read responses start with 0x16
// The payload length for the Co2 message is 0x05
// The command for the Co2 message is 0x01
uint8_t expectedHeader[] = {0x16, 0x05, 0x01};
int currentPos = 0;
int availableBytes = readUartFillLevel();
if (availableBytes < NUM_MSG_BYTES) {
return -1;
}
// We are only interested in the last message, drop all others
while (availableBytes >= (2*NUM_MSG_BYTES)) {
readUartResponse(response, NUM_MSG_BYTES);
availableBytes = readUartFillLevel();
}
// Find the expected header
while (currentPos < sizeof(expectedHeader)) {
if (readUartFillLevel()) {
readUartResponse(response+currentPos, 1);
} else {
return -1;
}
if (response[currentPos] == expectedHeader[currentPos]) {
currentPos++;
}
}
// If present, read the data and checksum
if (readUartFillLevel() >= NUM_MSG_BYTES - sizeof(expectedHeader)) {
readUartResponse(response+currentPos, NUM_MSG_BYTES - sizeof(expectedHeader));
} else {
ESP_LOGW(TAG, "The last message in the buffer was not complete");
return -1;
}
// Process the Co2 value and checksum
uint8_t checksum = calcCRC(response, sizeof(response));
int16_t ppm = response[3] << 8 | response[4];
if (response[7] == checksum) {
ESP_LOGD(TAG, "CM1106 Received CO₂=%uppm DF3=%02X DF4=%02X", ppm, response[5], response[6]);
return ppm;
} else {
ESP_LOGW(TAG, "Got wrong UART checksum: 0x%02X - Calculated: 0x%02X, ppm data: %u", response[7], checksum, ppm);
return -1;
}
return -1;
}
private:
const char *TAG = "cm1106";
const uint8_t NUM_MSG_BYTES = 8;
// Checksum: 256-(HEAD+LEN+CMD+DATA)%256
uint8_t calcCRC(uint8_t* response, size_t len) {
uint8_t crc = 0;
// last byte of response is checksum, don't calculate it
for (int i = 0; i < len - 1; i++) {
crc -= response[i];
}
return crc;
}
void readUartResponse(uint8_t *response, size_t responseLen) {
read_array(response, responseLen);
}
// Returns the number of bytes currently in the UART RX Buffer
int readUartFillLevel() {
return available();
}
};
class CM1106Sensor : public PollingComponent, public Sensor {
private:
CM1106 *cm1106;
public:
CM1106Sensor(UARTComponent *parent, uint32_t update_interval) : PollingComponent(update_interval) {
cm1106 = new CM1106(parent);
}
float get_setup_priority() const { return setup_priority::DATA; }
void setup() override {
}
void update() override {
int16_t ppm = cm1106->getCo2PPM();
if (ppm > -1) {
publish_state(ppm);
}
}
virtual ~CM1106Sensor() { delete cm1106; }
}; |
En hieronder het stukje .yaml dat je toe moet voegen om de buffer groter te maken
YAML:
resideo.yaml
1
2
3
4
5
| uart:
rx_pin: 20
baud_rate: 9600
rx_buffer_size: 256
id: cm1106_uart |
Hopelijk hebben @
The Fatal, @
_CrookClaw_ en andere er iets aan
EDIT:
- Bug in code opgelost die er voor kon zorgen dat er nooit een bericht werd gelezen
- Link naar Github repository toegevoegd waar alle toekomstige code changes voortaan geplaatst zullen worden
[
Voor 12% gewijzigd door
Skix_Aces op 23-03-2024 17:55
]