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