ESP32 / ESP8266 & BME280 / SHT2x sensor with InfluxDB support
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

lora.cpp 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. * lora.cpp
  3. *
  4. * ESP8266 / ESP32 Environmental Sensor
  5. *
  6. * ----------------------------------------------------------------------------
  7. * "THE BEER-WARE LICENSE" (Revision 42):
  8. * <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  9. * you can do whatever you want with this stuff. If we meet some day, and you
  10. * think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  11. * ----------------------------------------------------------------------------
  12. */
  13. #ifdef FEATURE_LORA
  14. #include <Arduino.h>
  15. #ifdef FEATURE_SML
  16. #define HELTEC_NO_DISPLAY
  17. #else // FEATURE_SML
  18. #define HELTEC_POWER_BUTTON
  19. #endif // FEATURE_SML
  20. #include <heltec_unofficial.h>
  21. #include "config.h"
  22. #include "DebugLog.h"
  23. #include "influx.h"
  24. #include "smart_meter.h"
  25. #include "lora.h"
  26. //#define DEBUG_LORA_RX_HEXDUMP
  27. #ifdef FEATURE_SML
  28. #define LORA_LED_BRIGHTNESS 0 // in percent, 50% brightness is plenty for this LED
  29. #define DEEP_SLEEP_DURATION_S (60 - (millis() / 1000)) // 60s cycle time
  30. #define DEEP_SLEEP_TIMEOUT_MS (30UL * 1000UL) // fallback sleep timeout when sml data was received
  31. #define DEEP_SLEEP_ABORT_NO_DATA_MS DEEP_SLEEP_TIMEOUT_MS // sleep timeout when no sml data received
  32. #else // FEATURE_SML
  33. #define LORA_LED_BRIGHTNESS 25 // in percent, 50% brightness is plenty for this LED
  34. #endif // FEATURE_SML
  35. // Frequency in MHz. Keep the decimal point to designate float.
  36. // Check your own rules and regulations to see what is legal where you are.
  37. #define FREQUENCY 866.3 // for Europe
  38. // #define FREQUENCY 905.2 // for US
  39. // LoRa bandwidth. Keep the decimal point to designate float.
  40. // Allowed values are 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0 and 500.0 kHz.
  41. #define BANDWIDTH 250.0
  42. // Number from 5 to 12. Higher means slower but higher "processor gain",
  43. // meaning (in nutshell) longer range and more robust against interference.
  44. #define SPREADING_FACTOR 9
  45. // Transmit power in dBm. 0 dBm = 1 mW, enough for tabletop-testing. This value can be
  46. // set anywhere between -9 dBm (0.125 mW) to 22 dBm (158 mW). Note that the maximum ERP
  47. // (which is what your antenna maximally radiates) on the EU ISM band is 25 mW, and that
  48. // transmissting without an antenna can damage your hardware.
  49. // 25mW = 14dBm
  50. #define MAX_TX_POWER 14
  51. #define ANTENNA_GAIN (5 - 3) // 3dB for the coax extensions and their SMA connectors
  52. #define TRANSMIT_POWER (MAX_TX_POWER - ANTENNA_GAIN)
  53. #define RADIOLIB_xy(action) \
  54. debug.print(#action); \
  55. debug.print(" = "); \
  56. debug.print(state); \
  57. debug.print(" ("); \
  58. debug.print(radiolib_result_string(state)); \
  59. debug.println(")");
  60. #define RADIOLIB_CHECK(action) do { \
  61. int state = action; \
  62. if (state != RADIOLIB_ERR_NONE) { \
  63. RADIOLIB_xy(action); \
  64. success = false; \
  65. } \
  66. } while (false);
  67. static unsigned long last_bat_time = 0;
  68. static bool use_lora = true;
  69. static unsigned long last_tx = 0, last_rx = 0;
  70. static unsigned long tx_time = 0, minimum_pause = 0;
  71. static volatile bool rx_flag = false;
  72. #ifndef LORA_KEEP_SENDING_CACHE
  73. #define BAT_MSG_EVERY_NTH 5
  74. RTC_DATA_ATTR static uint8_t last_tx_msg = LORA_SML_HELLO;
  75. RTC_DATA_ATTR static uint8_t bat_msg_cnt = 0;
  76. #endif // ! LORA_KEEP_SENDING_CACHE
  77. #ifdef FEATURE_SML
  78. struct sml_cache {
  79. double value, next_value;
  80. bool ready, has_next;
  81. unsigned long counter, next_counter;
  82. };
  83. static struct sml_cache cache[LORA_SML_NUM_MESSAGES];
  84. #endif // FEATURE_SML
  85. void lora_oled_init(void) {
  86. heltec_setup();
  87. }
  88. void lora_oled_print(String s) {
  89. #ifndef FEATURE_SML
  90. display.print(s);
  91. #endif // ! FEATURE_SML
  92. }
  93. static void print_bat(void) {
  94. float vbat = heltec_vbat();
  95. debug.printf("Vbat: %.2fV (%d%%)\n", vbat, heltec_battery_percent(vbat));
  96. }
  97. double lora_get_mangled_bat(void) {
  98. uint8_t data[sizeof(double)];
  99. float vbat = heltec_vbat();
  100. int percent = heltec_battery_percent(vbat);
  101. memcpy(data, &vbat, sizeof(float));
  102. memcpy(data + sizeof(float), &percent, sizeof(int));
  103. return *((double *)data);
  104. }
  105. #ifdef LORA_CLIENT_CHECKSUM
  106. // adapted from "Hacker's Delight"
  107. static uint32_t calc_checksum(const uint8_t *data, size_t len) {
  108. uint32_t c = 0xFFFFFFFF;
  109. for (size_t i = 0; i < len; i++) {
  110. c ^= data[i];
  111. for (size_t j = 0; j < 8; j++) {
  112. uint32_t mask = -(c & 1);
  113. c = (c >> 1) ^ (0xEDB88320 & mask);
  114. }
  115. }
  116. return ~c;
  117. }
  118. #endif // LORA_CLIENT_CHECKSUM
  119. static void lora_rx(void) {
  120. rx_flag = true;
  121. }
  122. static bool lora_tx(enum lora_sml_type type, double value) {
  123. bool tx_legal = millis() > (last_tx + minimum_pause);
  124. if (!tx_legal) {
  125. return false;
  126. }
  127. struct lora_sml_msg msg;
  128. msg.type = type;
  129. msg.value = value;
  130. #ifdef LORA_CLIENT_CHECKSUM
  131. msg.checksum = calc_checksum((uint8_t *)&msg, offsetof(struct lora_sml_msg, checksum));
  132. #endif // LORA_CLIENT_CHECKSUM
  133. uint8_t *data = (uint8_t *)&msg;
  134. const size_t len = sizeof(struct lora_sml_msg);
  135. debug.printf("TX [%d] (%lu) ", data[0], len);
  136. #ifdef LORA_XOR_KEY
  137. for (size_t i = 0; i < len; i++) {
  138. data[i] ^= LORA_XOR_KEY[i];
  139. }
  140. #endif // LORA_XOR_KEY
  141. radio.clearDio1Action();
  142. heltec_led(LORA_LED_BRIGHTNESS);
  143. bool success = true;
  144. tx_time = millis();
  145. RADIOLIB_CHECK(radio.transmit(data, len));
  146. tx_time = millis() - tx_time;
  147. heltec_led(0);
  148. bool r = true;
  149. if (success) {
  150. debug.printf("OK (%i ms)\n", (int)tx_time);
  151. } else {
  152. debug.println("fail");
  153. r = false;
  154. }
  155. // Maximum 1% duty cycle
  156. minimum_pause = tx_time * 100;
  157. last_tx = millis();
  158. radio.setDio1Action(lora_rx);
  159. #ifndef FEATURE_SML
  160. success = true;
  161. RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
  162. if (!success) {
  163. use_lora = false;
  164. }
  165. #endif // ! FEATURE_SML
  166. return r;
  167. }
  168. #ifdef FEATURE_SML
  169. #ifdef LORA_KEEP_SENDING_CACHE
  170. static void lora_sml_handle_cache(void) {
  171. // find smallest message counter that is ready
  172. unsigned long min_counter = ULONG_MAX;
  173. for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
  174. if (cache[i].ready && (cache[i].counter < min_counter)) {
  175. min_counter = cache[i].counter;
  176. }
  177. }
  178. // try to transmit next value with lowest counter
  179. for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
  180. if (cache[i].ready && (cache[i].counter == min_counter)) {
  181. enum lora_sml_type msg = (enum lora_sml_type)i;
  182. if (lora_tx(msg, cache[msg].value)) {
  183. if (cache[i].has_next) {
  184. cache[i].has_next = false;
  185. cache[i].value = cache[i].next_value;
  186. cache[i].counter = cache[i].next_counter;
  187. } else {
  188. cache[i].ready = false;
  189. }
  190. }
  191. }
  192. }
  193. }
  194. #endif // LORA_KEEP_SENDING_CACHE
  195. void lora_sml_send(enum lora_sml_type msg, double value, unsigned long counter) {
  196. if (cache[msg].ready) {
  197. // still waiting to be transmitted, so cache for next cycle
  198. cache[msg].has_next = true;
  199. cache[msg].next_value = value;
  200. cache[msg].next_counter = counter;
  201. } else {
  202. // cache as current value, for transmission in this cycle
  203. cache[msg].ready = true;
  204. cache[msg].value = value;
  205. cache[msg].counter = counter;
  206. }
  207. }
  208. void lora_sml_done(void) {
  209. #ifndef LORA_KEEP_SENDING_CACHE
  210. // turn off Ve external 3.3V to Smart Meter reader
  211. heltec_ve(false);
  212. // select next message from cache
  213. uint8_t n = 0;
  214. do {
  215. last_tx_msg++;
  216. n++;
  217. if (last_tx_msg == LORA_SML_BAT_V) {
  218. bat_msg_cnt++;
  219. if (bat_msg_cnt < BAT_MSG_EVERY_NTH) {
  220. continue; // skip bat message this time
  221. }
  222. bat_msg_cnt = 0;
  223. }
  224. if (last_tx_msg >= LORA_SML_NUM_MESSAGES) {
  225. last_tx_msg = 0;
  226. }
  227. } while ((!cache[last_tx_msg].ready) && (n < LORA_SML_NUM_MESSAGES + 1));
  228. // transmit it
  229. if (cache[last_tx_msg].ready) {
  230. enum lora_sml_type msg = (enum lora_sml_type)last_tx_msg;
  231. lora_tx(msg, cache[msg].value);
  232. }
  233. debug.println("sleep");
  234. heltec_deep_sleep(DEEP_SLEEP_DURATION_S < (minimum_pause / 1000) ? (minimum_pause / 1000) : DEEP_SLEEP_DURATION_S);
  235. #endif // ! LORA_KEEP_SENDING_CACHE
  236. }
  237. #endif // FEATURE_SML
  238. void lora_init(void) {
  239. #ifdef FEATURE_SML
  240. for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
  241. cache[i].value = NAN;
  242. cache[i].next_value = NAN;
  243. cache[i].ready = false;
  244. cache[i].has_next = false;
  245. cache[i].counter = 0;
  246. cache[i].next_counter = 0;
  247. }
  248. //print_bat();
  249. #endif // FEATURE_SML
  250. bool success = true;
  251. RADIOLIB_CHECK(radio.begin());
  252. if (!success) {
  253. use_lora = false;
  254. return;
  255. }
  256. radio.setDio1Action(lora_rx);
  257. debug.printf("Frequency: %.2f MHz\n", FREQUENCY);
  258. RADIOLIB_CHECK(radio.setFrequency(FREQUENCY));
  259. if (!success) {
  260. use_lora = false;
  261. return;
  262. }
  263. debug.printf("Bandwidth: %.1f kHz\n", BANDWIDTH);
  264. RADIOLIB_CHECK(radio.setBandwidth(BANDWIDTH));
  265. if (!success) {
  266. use_lora = false;
  267. return;
  268. }
  269. debug.printf("Spreading Factor: %i\n", SPREADING_FACTOR);
  270. RADIOLIB_CHECK(radio.setSpreadingFactor(SPREADING_FACTOR));
  271. if (!success) {
  272. use_lora = false;
  273. return;
  274. }
  275. debug.printf("TX power: %i dBm\n", TRANSMIT_POWER);
  276. RADIOLIB_CHECK(radio.setOutputPower(TRANSMIT_POWER));
  277. if (!success) {
  278. use_lora = false;
  279. return;
  280. }
  281. #ifndef FEATURE_SML
  282. // Start receiving
  283. RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
  284. if (!success) {
  285. use_lora = false;
  286. return;
  287. }
  288. #endif // ! FEATURE_SML
  289. #ifdef FEATURE_SML
  290. // turn on Ve external 3.3V to power Smart Meter reader
  291. heltec_ve(true);
  292. #endif // FEATURE_SML
  293. }
  294. void lora_run(void) {
  295. heltec_loop();
  296. unsigned long time = millis();
  297. #ifdef OLED_BAT_INTERVAL
  298. if (((time - last_bat_time) >= OLED_BAT_INTERVAL) || (last_bat_time == 0)) {
  299. last_bat_time = time;
  300. print_bat();
  301. #ifdef FORCE_BAT_SEND_AT_OLED_INTERVAL
  302. lora_sml_send(LORA_SML_BAT_V, lora_get_mangled_bat(), 0);
  303. #endif // FORCE_BAT_SEND_AT_OLED_INTERVAL
  304. }
  305. #endif // OLED_BAT_INTERVAL
  306. #if defined(DEEP_SLEEP_TIMEOUT_MS) && defined(DEEP_SLEEP_DURATION_S)
  307. bool got_sml = sml_data_received();
  308. if ((got_sml && (time >= DEEP_SLEEP_TIMEOUT_MS))
  309. || ((!got_sml) && (time >= DEEP_SLEEP_ABORT_NO_DATA_MS))) {
  310. debug.printf("falllback sleep %d %lu\n", got_sml, time);
  311. heltec_deep_sleep(DEEP_SLEEP_DURATION_S < (minimum_pause / 1000) ? (minimum_pause / 1000) : DEEP_SLEEP_DURATION_S);
  312. }
  313. #endif // DEEP_SLEEP_TIMEOUT_MS && DEEP_SLEEP_DURATION_S
  314. #ifndef FEATURE_SML
  315. if ((time >= (6UL * 60UL * 60UL * 1000UL) // running for at least 6h
  316. && ((time - last_rx) >= (30UL * 1000UL))) // and last lora rx at least 30s ago
  317. || ((time - last_rx) >= (4UL * 60UL * 1000UL))) { // or last message longer than 4min ago
  318. debug.println("hang sleep");
  319. heltec_deep_sleep(5); // attempt reset to avoid lorarx hanging
  320. }
  321. #endif // ! FEATURE_SML
  322. if (!use_lora) {
  323. return;
  324. }
  325. if (rx_flag) {
  326. rx_flag = false;
  327. last_rx = time;
  328. bool success = true;
  329. uint8_t data[sizeof(struct lora_sml_msg)];
  330. RADIOLIB_CHECK(radio.readData(data, sizeof(data)));
  331. if (success) {
  332. #ifdef LORA_XOR_KEY
  333. for (size_t i = 0; i < sizeof(data); i++) {
  334. data[i] ^= LORA_XOR_KEY[i];
  335. }
  336. #endif
  337. debug.printf("RX [%i]\n", data[0]);
  338. debug.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
  339. debug.printf(" SNR: %.2f dB\n", radio.getSNR());
  340. #if defined(DEBUG_LORA_RX_HEXDUMP) || (!defined(ENABLE_INFLUXDB_LOGGING))
  341. for (int i = 0; i < sizeof(data); i++) {
  342. debug.printf(" %02X", data[i]);
  343. if (i < (sizeof(data) - 1)) {
  344. debug.print(" ");
  345. } else {
  346. debug.println();
  347. }
  348. }
  349. #endif
  350. struct lora_sml_msg *msg = (struct lora_sml_msg *)data;
  351. #ifdef LORA_CLIENT_CHECKSUM
  352. uint32_t checksum = calc_checksum(data, offsetof(struct lora_sml_msg, checksum));
  353. if (checksum != msg->checksum) {
  354. debug.printf(" CRC: 0x%08X != 0x%08X\n", msg->checksum, checksum);
  355. } else {
  356. debug.printf(" CRC: OK 0x%08X\n", checksum);
  357. #endif // LORA_CLIENT_CHECKSUM
  358. #ifdef ENABLE_INFLUXDB_LOGGING
  359. if (data[0] == LORA_SML_BAT_V) {
  360. // extract mangled float and int from double
  361. float vbat = NAN;
  362. int percent = -1;
  363. memcpy(&vbat, data + offsetof(struct lora_sml_msg, value), sizeof(float));
  364. memcpy(&percent, data + offsetof(struct lora_sml_msg, value) + sizeof(float), sizeof(int));
  365. debug.printf(" Vbat: %.2f (%d%%)\n", vbat, percent);
  366. writeSensorDatum("environment", "sml", SENSOR_LOCATION, "vbat", vbat);
  367. writeSensorDatum("environment", "sml", SENSOR_LOCATION, "percent", percent);
  368. } else {
  369. debug.printf(" Value: %.2f\n", msg->value);
  370. String key;
  371. switch (data[0]) {
  372. case LORA_SML_HELLO:
  373. key = "hello";
  374. break;
  375. case LORA_SML_SUM_WH:
  376. key = "Sum_Wh";
  377. break;
  378. case LORA_SML_T1_WH:
  379. key = "T1_Wh";
  380. break;
  381. case LORA_SML_T2_WH:
  382. key = "T2_Wh";
  383. break;
  384. case LORA_SML_SUM_W:
  385. key = "Sum_W";
  386. break;
  387. case LORA_SML_L1_W:
  388. key = "L1_W";
  389. break;
  390. case LORA_SML_L2_W:
  391. key = "L2_W";
  392. break;
  393. case LORA_SML_L3_W:
  394. key = "L3_W";
  395. break;
  396. default:
  397. key = "unknown";
  398. break;
  399. }
  400. writeSensorDatum("environment", "sml", SENSOR_LOCATION, key, msg->value);
  401. }
  402. #endif // ENABLE_INFLUXDB_LOGGING
  403. #ifdef LORA_CLIENT_CHECKSUM
  404. }
  405. #endif // LORA_CLIENT_CHECKSUM
  406. }
  407. #ifndef FEATURE_SML
  408. success = true;
  409. RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
  410. if (!success) {
  411. use_lora = false;
  412. return;
  413. }
  414. #endif // ! FEATURE_SML
  415. }
  416. #ifdef LORA_KEEP_SENDING_CACHE
  417. lora_sml_handle_cache();
  418. #endif // LORA_KEEP_SENDING_CACHE
  419. #ifndef FEATURE_SML
  420. if (button.isSingleClick()) {
  421. // In case of button click, tell user to wait
  422. bool tx_legal = millis() > last_tx + minimum_pause;
  423. if (!tx_legal) {
  424. debug.printf("Legal limit, wait %i sec.\n", (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
  425. return;
  426. }
  427. debug.println("click");
  428. lora_tx(LORA_SML_HELLO, heltec_temperature());
  429. }
  430. #endif // ! FEATURE_SML
  431. }
  432. #endif // FEATURE_LORA