ESP32 / ESP8266 & BME280 / SHT2x sensor with InfluxDB support
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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