ESP32 / ESP8266 & BME280 / SHT2x sensor with InfluxDB support
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

lora.cpp 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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. // Turns the 'PRG' button into the power button, long press is off
  16. #define HELTEC_POWER_BUTTON
  17. #include <heltec_unofficial.h>
  18. #include "config.h"
  19. #include "DebugLog.h"
  20. #include "influx.h"
  21. #include "lora.h"
  22. //#define DEBUG_LORA_RX_HEXDUMP
  23. #ifdef FEATURE_SML
  24. #define LORA_LED_BRIGHTNESS 1 // in percent, 50% brightness is plenty for this LED
  25. #define OLED_BAT_INTERVAL (2UL * 60UL * 1000UL) // in ms
  26. #define FORCE_BAT_SEND_AT_OLED_INTERVAL
  27. #else // FEATURE_SML
  28. #define LORA_LED_BRIGHTNESS 25 // in percent, 50% brightness is plenty for this LED
  29. #endif // FEATURE_SML
  30. // Frequency in MHz. Keep the decimal point to designate float.
  31. // Check your own rules and regulations to see what is legal where you are.
  32. #define FREQUENCY 866.3 // for Europe
  33. // #define FREQUENCY 905.2 // for US
  34. // LoRa bandwidth. Keep the decimal point to designate float.
  35. // 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.
  36. #define BANDWIDTH 250.0
  37. // Number from 5 to 12. Higher means slower but higher "processor gain",
  38. // meaning (in nutshell) longer range and more robust against interference.
  39. #define SPREADING_FACTOR 9
  40. // Transmit power in dBm. 0 dBm = 1 mW, enough for tabletop-testing. This value can be
  41. // set anywhere between -9 dBm (0.125 mW) to 22 dBm (158 mW). Note that the maximum ERP
  42. // (which is what your antenna maximally radiates) on the EU ISM band is 25 mW, and that
  43. // transmissting without an antenna can damage your hardware.
  44. // 25mW = 14dBm
  45. #define MAX_TX_POWER 14
  46. #define ANTENNA_GAIN 5
  47. #define TRANSMIT_POWER (MAX_TX_POWER - ANTENNA_GAIN)
  48. #define RADIOLIB_xy(action) \
  49. debug.print(#action); \
  50. debug.print(" = "); \
  51. debug.print(state); \
  52. debug.print(" ("); \
  53. debug.print(radiolib_result_string(state)); \
  54. debug.println(")");
  55. #define RADIOLIB_CHECK(action) do { \
  56. int state = action; \
  57. if (state != RADIOLIB_ERR_NONE) { \
  58. RADIOLIB_xy(action); \
  59. success = false; \
  60. } \
  61. } while (false);
  62. static unsigned long last_bat_time = 0;
  63. static bool use_lora = true;
  64. static unsigned long last_tx = 0, tx_time = 0, minimum_pause = 0;
  65. static volatile bool rx_flag = false;
  66. #ifdef FEATURE_SML
  67. struct sml_cache {
  68. double value, next_value;
  69. bool ready, has_next;
  70. unsigned long counter, next_counter;
  71. };
  72. static struct sml_cache cache[LORA_SML_NUM_MESSAGES];
  73. #endif // FEATURE_SML
  74. void lora_oled_init(void) {
  75. heltec_setup();
  76. }
  77. void lora_oled_print(String s) {
  78. display.print(s);
  79. }
  80. static void print_bat(void) {
  81. float vbat = heltec_vbat();
  82. debug.printf("Vbat: %.2fV (%d%%)\n", vbat, heltec_battery_percent(vbat));
  83. }
  84. double lora_get_mangled_bat(void) {
  85. uint8_t data[sizeof(double)];
  86. float vbat = heltec_vbat();
  87. int percent = heltec_battery_percent(vbat);
  88. memcpy(data, &vbat, sizeof(float));
  89. memcpy(data + sizeof(float), &percent, sizeof(int));
  90. return *((double *)data);
  91. }
  92. // adapted from "Hacker's Delight"
  93. static uint32_t calc_checksum(const uint8_t *data, size_t len) {
  94. uint32_t c = 0xFFFFFFFF;
  95. for (size_t i = 0; i < len; i++) {
  96. c ^= data[i];
  97. for (size_t j = 0; j < 8; j++) {
  98. uint32_t mask = -(c & 1);
  99. c = (c >> 1) ^ (0xEDB88320 & mask);
  100. }
  101. }
  102. return ~c;
  103. }
  104. static void lora_rx(void) {
  105. rx_flag = true;
  106. }
  107. static bool lora_tx(enum lora_sml_type type, double value) {
  108. bool tx_legal = millis() > (last_tx + minimum_pause);
  109. if (!tx_legal) {
  110. //debug.printf("Legal limit, wait %i sec.\n", (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
  111. return false;
  112. }
  113. struct lora_sml_msg msg;
  114. msg.type = type;
  115. msg.value = value;
  116. msg.checksum = calc_checksum((uint8_t *)&msg, offsetof(struct lora_sml_msg, checksum));
  117. uint8_t *data = (uint8_t *)&msg;
  118. const size_t len = sizeof(struct lora_sml_msg);
  119. #ifdef LORA_XOR_KEY
  120. for (size_t i = 0; i < len; i++) {
  121. data[i] ^= LORA_XOR_KEY[i];
  122. }
  123. #endif
  124. debug.printf("TX [%d] (%lu) ", data[0], len);
  125. radio.clearDio1Action();
  126. heltec_led(LORA_LED_BRIGHTNESS);
  127. bool success = true;
  128. tx_time = millis();
  129. RADIOLIB_CHECK(radio.transmit(data, len));
  130. tx_time = millis() - tx_time;
  131. heltec_led(0);
  132. bool r = true;
  133. if (success) {
  134. debug.printf("OK (%i ms)\n", (int)tx_time);
  135. } else {
  136. debug.println("fail");
  137. r = false;
  138. }
  139. // Maximum 1% duty cycle
  140. minimum_pause = tx_time * 100;
  141. last_tx = millis();
  142. radio.setDio1Action(lora_rx);
  143. success = true;
  144. RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
  145. if (!success) {
  146. use_lora = false;
  147. }
  148. return r;
  149. }
  150. #ifdef FEATURE_SML
  151. static bool lora_sml_cache_send(enum lora_sml_type msg) {
  152. return lora_tx(msg, cache[msg].value);
  153. }
  154. static void lora_sml_handle_cache(void) {
  155. // find smallest message counter that is ready
  156. unsigned long min_counter = ULONG_MAX;
  157. for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
  158. if (cache[i].ready && (cache[i].counter < min_counter)) {
  159. min_counter = cache[i].counter;
  160. }
  161. }
  162. // try to transmit next value with lowest counter
  163. for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
  164. if (cache[i].ready && (cache[i].counter == min_counter)) {
  165. if (lora_sml_cache_send((enum lora_sml_type)i)) {
  166. if (cache[i].has_next) {
  167. cache[i].has_next = false;
  168. cache[i].value = cache[i].next_value;
  169. cache[i].counter = cache[i].next_counter;
  170. } else {
  171. cache[i].ready = false;
  172. }
  173. }
  174. }
  175. }
  176. }
  177. void lora_sml_send(enum lora_sml_type msg, double value, unsigned long counter) {
  178. if (cache[msg].ready) {
  179. // still waiting to be transmitted, so cache for next cycle
  180. cache[msg].has_next = true;
  181. cache[msg].next_value = value;
  182. cache[msg].next_counter = counter;
  183. } else {
  184. // cache as current value, for transmission in this cycle
  185. cache[msg].ready = true;
  186. cache[msg].value = value;
  187. cache[msg].counter = counter;
  188. }
  189. }
  190. #endif // FEATURE_SML
  191. void lora_init(void) {
  192. #ifdef FEATURE_SML
  193. for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
  194. cache[i].value = NAN;
  195. cache[i].next_value = NAN;
  196. cache[i].ready = false;
  197. cache[i].has_next = false;
  198. cache[i].counter = 0;
  199. cache[i].next_counter = 0;
  200. }
  201. #endif // FEATURE_SML
  202. print_bat();
  203. bool success = true;
  204. RADIOLIB_CHECK(radio.begin());
  205. if (!success) {
  206. use_lora = false;
  207. return;
  208. }
  209. radio.setDio1Action(lora_rx);
  210. debug.printf("Frequency: %.2f MHz\n", FREQUENCY);
  211. RADIOLIB_CHECK(radio.setFrequency(FREQUENCY));
  212. if (!success) {
  213. use_lora = false;
  214. return;
  215. }
  216. debug.printf("Bandwidth: %.1f kHz\n", BANDWIDTH);
  217. RADIOLIB_CHECK(radio.setBandwidth(BANDWIDTH));
  218. if (!success) {
  219. use_lora = false;
  220. return;
  221. }
  222. debug.printf("Spreading Factor: %i\n", SPREADING_FACTOR);
  223. RADIOLIB_CHECK(radio.setSpreadingFactor(SPREADING_FACTOR));
  224. if (!success) {
  225. use_lora = false;
  226. return;
  227. }
  228. debug.printf("TX power: %i dBm\n", TRANSMIT_POWER);
  229. RADIOLIB_CHECK(radio.setOutputPower(TRANSMIT_POWER));
  230. if (!success) {
  231. use_lora = false;
  232. return;
  233. }
  234. // Start receiving
  235. RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
  236. if (!success) {
  237. use_lora = false;
  238. return;
  239. }
  240. #ifdef FEATURE_SML
  241. // turn on Ve external 3.3V to power Smart Meter reader
  242. heltec_ve(true);
  243. // send hello msg after boot
  244. lora_sml_send(LORA_SML_HELLO, -42.23, 0);
  245. #endif // FEATURE_SML
  246. }
  247. void lora_run(void) {
  248. heltec_loop();
  249. #ifdef OLED_BAT_INTERVAL
  250. unsigned long time = millis();
  251. if (((time - last_bat_time) >= OLED_BAT_INTERVAL) || (last_bat_time == 0)) {
  252. last_bat_time = time;
  253. print_bat();
  254. #ifdef FORCE_BAT_SEND_AT_OLED_INTERVAL
  255. lora_sml_send(LORA_SML_BAT_V, lora_get_mangled_bat(), 0);
  256. #endif // FORCE_BAT_SEND_AT_OLED_INTERVAL
  257. }
  258. #endif
  259. if (!use_lora) {
  260. return;
  261. }
  262. // If a packet was received, display it and the RSSI and SNR
  263. if (rx_flag) {
  264. rx_flag = false;
  265. bool success = true;
  266. uint8_t data[sizeof(struct lora_sml_msg)];
  267. RADIOLIB_CHECK(radio.readData(data, sizeof(data)));
  268. if (success) {
  269. #ifdef LORA_XOR_KEY
  270. for (size_t i = 0; i < sizeof(data); i++) {
  271. data[i] ^= LORA_XOR_KEY[i];
  272. }
  273. #endif
  274. debug.printf("RX [%i]\n", data[0]);
  275. debug.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
  276. debug.printf(" SNR: %.2f dB\n", radio.getSNR());
  277. #if defined(DEBUG_LORA_RX_HEXDUMP) || (!defined(ENABLE_INFLUXDB_LOGGING))
  278. for (int i = 0; i < sizeof(data); i++) {
  279. debug.printf(" %02X", data[i]);
  280. if (i < (sizeof(data) - 1)) {
  281. debug.print(" ");
  282. } else {
  283. debug.println();
  284. }
  285. }
  286. #endif
  287. struct lora_sml_msg *msg = (struct lora_sml_msg *)data;
  288. uint32_t checksum = calc_checksum(data, offsetof(struct lora_sml_msg, checksum));
  289. if (checksum != msg->checksum) {
  290. debug.printf(" CRC: 0x%08X != 0x%08X\n", msg->checksum, checksum);
  291. } else {
  292. debug.printf(" CRC: OK 0x%08X\n", checksum);
  293. #ifdef ENABLE_INFLUXDB_LOGGING
  294. if (data[0] == LORA_SML_BAT_V) {
  295. // extract mangled float and int from double
  296. float vbat = NAN;
  297. int percent = -1;
  298. memcpy(&vbat, data + offsetof(struct lora_sml_msg, value), sizeof(float));
  299. memcpy(&percent, data + offsetof(struct lora_sml_msg, value) + sizeof(float), sizeof(int));
  300. debug.printf(" Vbat: %.2f (%d%%)\n", vbat, percent);
  301. writeSensorDatum("environment", "sml", SENSOR_LOCATION, "vbat", vbat);
  302. writeSensorDatum("environment", "sml", SENSOR_LOCATION, "percent", percent);
  303. } else {
  304. debug.printf(" Value: %.2f\n", msg->value);
  305. String key;
  306. switch (data[0]) {
  307. case LORA_SML_HELLO:
  308. key = "hello";
  309. break;
  310. case LORA_SML_SUM_WH:
  311. key = "Sum_Wh";
  312. break;
  313. case LORA_SML_T1_WH:
  314. key = "T1_Wh";
  315. break;
  316. case LORA_SML_T2_WH:
  317. key = "T2_Wh";
  318. break;
  319. case LORA_SML_SUM_W:
  320. key = "Sum_W";
  321. break;
  322. case LORA_SML_L1_W:
  323. key = "L1_W";
  324. break;
  325. case LORA_SML_L2_W:
  326. key = "L2_W";
  327. break;
  328. case LORA_SML_L3_W:
  329. key = "L3_W";
  330. break;
  331. default:
  332. key = "unknown";
  333. break;
  334. }
  335. writeSensorDatum("environment", "sml", SENSOR_LOCATION, key, msg->value);
  336. }
  337. #endif // ENABLE_INFLUXDB_LOGGING
  338. }
  339. }
  340. success = true;
  341. RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
  342. if (!success) {
  343. use_lora = false;
  344. return;
  345. }
  346. }
  347. #ifdef FEATURE_SML
  348. lora_sml_handle_cache();
  349. #endif // FEATURE_SML
  350. if (button.isSingleClick()) {
  351. // In case of button click, tell user to wait
  352. bool tx_legal = millis() > last_tx + minimum_pause;
  353. if (!tx_legal) {
  354. debug.printf("Legal limit, wait %i sec.\n", (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
  355. return;
  356. }
  357. // send test hello message on lorarx target, or battery state on loratx target
  358. #ifdef FEATURE_SML
  359. lora_sml_send(LORA_SML_BAT_V, lora_get_mangled_bat(), 0);
  360. #else // FEATURE_SML
  361. lora_tx(LORA_SML_HELLO, -23.42);
  362. #endif // FEATURE_SML
  363. }
  364. }
  365. #endif // FEATURE_LORA