supla-device
Loading...
Searching...
No Matches
aqi.eco.h
1// SPDX-FileCopyrightText: malarz
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Dependencies:
5// https://github.com/arduino-libraries/Arduino_JSON
6
7// It use ca. 20kB RAM, so ESP32 is highly recomended
8
9#ifdef ARDUINO_ARCH_AVR
10#error "aqi.eco is not supported on AVR"
11#endif // ARDUINO_ARCH_AVR
12
13#ifndef SRC_SUPLA_PROTOCOL_AQI_ECO_H_
14#define SRC_SUPLA_PROTOCOL_AQI_ECO_H_
15
16// Allow insecure external TLS (not recommended)
17// #define SUPLA_ALLOW_INSECURE_EXTERNAL_TLS
18
19#include <supla/version.h>
20#include <Arduino.h>
21#include <ArduinoJson.h>
22#include <supla/network/client.h>
23#include <supla/protocol/weathersender.h>
24
25// Certificate for https://aqi.eco (LetsEncrypt)
26// Valid until 2035-06-04
27static const char LETS_ENCRYPT_CA_CERT[] PROGMEM = R"EOF(
28-----BEGIN CERTIFICATE-----
29MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
30TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
31cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
32WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
33ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
34MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
35h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
360TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
37A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
38T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
39B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
40B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
41KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
42OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
43jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
44qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
45rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
46HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
47hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
48ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
493BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
50NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
51ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
52TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
53jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
54oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
554RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
56mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
57emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
58-----END CERTIFICATE-----
59)EOF";
60
61namespace Supla {
62namespace Protocol {
64 public:
65 explicit AQIECO(Supla::Network* _network, char token[], int refresh = 180,
66 char server[] = "api.aqi.eco", int id = 0)
68 client = Supla::ClientBuilder();
69 client->setSSLEnabled(true);
70#if !defined(SUPLA_ALLOW_INSECURE_EXTERNAL_TLS)
71 client->setCACert(LETS_ENCRYPT_CA_CERT);
72#endif
73
74 // serverAddress
75 strncpy(serverAddress, server, 32);
76 serverAddress[32] = 0;
77
78 // apiToken
79 if (strlen(token) == 32) {
80 strncpy(apiToken, token, 32);
81 apiToken[32] = 0;
82 } else {
83 apiToken[0] = 0;
84 }
85 // refreshTime
86 if (refresh < 120) {
87 refreshTime = 120;
88 } else {
89 refreshTime = refresh;
90 }
91 SUPLA_LOG_DEBUG("aqi.eco: refresh time: %d", refreshTime);
92
93 // sensorId
94 if (id == 0) {
95 uint8_t mac[6] = {};
96 _network->getMacAddr(mac);
97 sensorId = ((mac[2]*256+mac[3])*256+mac[4])*256+mac[5];
98 } else {
99 sensorId = id;
100 }
101 }
102
103 ~AQIECO() {
104 delete client;
105 client = nullptr;
106 }
107
108 bool sendData() override {
109 if (strlen(apiToken) != 32) {
110 SUPLA_LOG_DEBUG("aqi.eco: expected token length 32, got %d",
111 static_cast<int>(strlen(apiToken)));
112 return false;
113 }
114
115 StaticJsonDocument<768> jsonBuffer;
116 JsonObject json = jsonBuffer.to<JsonObject>();
117
118 json["esp8266id"] = sensorId;
119 json["software_version"] = "Supla_" SUPLA_SHORT_VERSION;
120 JsonArray sensordatavalues = json.createNestedArray("sensordatavalues");
121
122 for (int i=0; i < MAXSENSORS; i++) {
123 if (sensors[i]) {
124 double value = getSensorValue(i);
125 String type = "unknown";
126 switch (i) {
127 case Supla::SenorType::PM1:
128 type = "SPS30_P0";
129 break;
130 case Supla::SenorType::PM2_5:
131 type = "SPS30_P2";
132 break;
133 case Supla::SenorType::PM4:
134 type = "SPS30_P4";
135 break;
136 case Supla::SenorType::PM10:
137 type = "SPS30_P1";
138 break;
139 case Supla::SenorType::TEMP:
140 type = "BME280_temperature";
141 break;
142 case Supla::SenorType::HUMI:
143 type = "BME280_humidity";
144 break;
145 case Supla::SenorType::PRESS:
146 type = "BME280_pressure";
147 value *= 100;
148 break;
149 case Supla::SenorType::LIGHT:
150 type = "ambient_light";
151 break;
152 case Supla::SenorType::WIND:
153 type = "wind_speed";
154 break;
155 case Supla::SenorType::RAIN:
156 type = "rainfall";
157 break;
158 case Supla::SenorType::CO2:
159 type = "conc_co2_ppm";
160 break;
161 }
162
163 if (!isnan(value)) {
164 JsonObject jo = sensordatavalues.createNestedObject();
165 jo["value_type"] = type;
166 jo["value"] = value;
167 } else {
168 return false;
169 }
170 }
171 }
172 char output[768];
173 serializeJson(json, output, 768);
174 SUPLA_LOG_DEBUG("aqi.eco: JSON: %s", output);
175
176 if (client->connect(serverAddress, 443)) {
177 client->print("POST /update/");
178 client->print(apiToken);
179 client->println(" HTTP/1.1");
180 client->print("Host: ");
181 client->println(serverAddress);
182 client->println("Content-Type: application/json");
183 client->print("Content-Length: ");
184 client->println(strlen(output));
185 client->println();
186 client->println(output);
187
188 SUPLA_LOG_DEBUG("aqi.eco: sended %d bytes to %s/update/%s",
189 strlen(output), serverAddress, apiToken);
190
191 // waiting for response
192 delay(100);
193 if (!client->available()) {
194 SUPLA_LOG_DEBUG("aqi.eco: no bytes to read from %s", serverAddress);
195 return false;
196 }
197 int responseLength = client->available();
198 SUPLA_LOG_DEBUG("aqi.eco: reading from %s: %d bytes",
199 serverAddress, responseLength);
200
201 if (responseLength >= static_cast<int>(sizeof(output))) {
202 responseLength = sizeof(output) - 1;
203 }
204
205 for (int i = 0; i < responseLength; i++) {
206 int responseChar = client->read();
207 if (responseChar < 0) {
208 output[i] = 0;
209 break;
210 }
211 output[i] = static_cast<char>(responseChar);
212 if (output[i] == '\n') {
213 output[i] = 0;
214 }
215 }
216 output[responseLength] = 0;
217 SUPLA_LOG_DEBUG("aqi.eco: response from %s: %s", serverAddress, output);
218 return true;
219 }
220 return false;
221 }
222
223 private:
224 ::Supla::Client *client = nullptr;
225 char apiToken[33];
226 char serverAddress[33];
227 uint32_t sensorId = 0;
228};
229} // namespace Protocol
230} // namespace Supla
231
232#endif // SRC_SUPLA_PROTOCOL_AQI_ECO_H_
Definition client.h:24
Definition network.h:21
Definition aqi.eco.h:63
Definition weathersender.h:30