|
@@ -27,10 +27,11 @@
|
27
|
27
|
// define LORA_TEST_TX to periodically transmit a test message
|
28
|
28
|
//#define LORA_TEST_TX
|
29
|
29
|
|
30
|
|
-#define OLED_BAT_INTERVAL (10UL * 1000UL) // in ms
|
|
30
|
+//#define DEBUG_LORA_RX_HEXDUMP
|
31
|
31
|
|
32
|
32
|
#ifdef FEATURE_SML
|
33
|
33
|
#define LORA_LED_BRIGHTNESS 1 // in percent, 50% brightness is plenty for this LED
|
|
34
|
+#define OLED_BAT_INTERVAL (10UL * 1000UL) // in ms
|
34
|
35
|
#else // FEATURE_SML
|
35
|
36
|
#define LORA_LED_BRIGHTNESS 25 // in percent, 50% brightness is plenty for this LED
|
36
|
37
|
#endif // FEATURE_SML
|
|
@@ -82,9 +83,25 @@
|
82
|
83
|
|
83
|
84
|
static unsigned long last_bat_time = 0;
|
84
|
85
|
static bool use_lora = true;
|
85
|
|
-static unsigned long last_tx = 0, counter = 0, tx_time = 0, minimum_pause = 0;
|
|
86
|
+static unsigned long last_tx = 0, tx_time = 0, minimum_pause = 0;
|
86
|
87
|
static volatile bool rx_flag = false;
|
87
|
88
|
|
|
89
|
+#ifdef FEATURE_SML
|
|
90
|
+
|
|
91
|
+struct sml_cache {
|
|
92
|
+ double value, next_value;
|
|
93
|
+ bool ready, has_next;
|
|
94
|
+ unsigned long counter, next_counter;
|
|
95
|
+};
|
|
96
|
+
|
|
97
|
+static struct sml_cache cache[LORA_SML_NUM_MESSAGES];
|
|
98
|
+
|
|
99
|
+#endif // FEATURE_SML
|
|
100
|
+
|
|
101
|
+#ifdef LORA_TEST_TX
|
|
102
|
+static unsigned long test_counter = 0;
|
|
103
|
+#endif // LORA_TEST_TX
|
|
104
|
+
|
88
|
105
|
void lora_oled_init(void) {
|
89
|
106
|
heltec_setup();
|
90
|
107
|
}
|
|
@@ -102,7 +119,109 @@ static void lora_rx(void) {
|
102
|
119
|
rx_flag = true;
|
103
|
120
|
}
|
104
|
121
|
|
|
122
|
+static bool lora_tx(uint8_t *data, size_t len) {
|
|
123
|
+ bool tx_legal = millis() > (last_tx + minimum_pause);
|
|
124
|
+ if (!tx_legal) {
|
|
125
|
+ //debug.printf("Legal limit, wait %i sec.\n", (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
|
|
126
|
+ return false;
|
|
127
|
+ }
|
|
128
|
+
|
|
129
|
+ debug.printf("TX [%lu] ", len);
|
|
130
|
+ radio.clearDio1Action();
|
|
131
|
+
|
|
132
|
+ heltec_led(LORA_LED_BRIGHTNESS);
|
|
133
|
+
|
|
134
|
+ bool success = true;
|
|
135
|
+ tx_time = millis();
|
|
136
|
+ RADIOLIB_CHECK(radio.transmit(data, len));
|
|
137
|
+ tx_time = millis() - tx_time;
|
|
138
|
+
|
|
139
|
+ heltec_led(0);
|
|
140
|
+
|
|
141
|
+ bool r = true;
|
|
142
|
+ if (success) {
|
|
143
|
+ debug.printf("OK (%i ms)\n", (int)tx_time);
|
|
144
|
+ } else {
|
|
145
|
+ debug.println("fail");
|
|
146
|
+ r = false;
|
|
147
|
+ }
|
|
148
|
+
|
|
149
|
+ // Maximum 1% duty cycle
|
|
150
|
+ minimum_pause = tx_time * 100;
|
|
151
|
+ last_tx = millis();
|
|
152
|
+
|
|
153
|
+ radio.setDio1Action(lora_rx);
|
|
154
|
+
|
|
155
|
+ success = true;
|
|
156
|
+ RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
|
|
157
|
+ if (!success) {
|
|
158
|
+ use_lora = false;
|
|
159
|
+ }
|
|
160
|
+
|
|
161
|
+ return r;
|
|
162
|
+}
|
|
163
|
+
|
|
164
|
+#ifdef FEATURE_SML
|
|
165
|
+static bool lora_sml_cache_send(enum lora_sml_type msg) {
|
|
166
|
+ const size_t len = sizeof(double) + 1;
|
|
167
|
+ uint8_t data[len];
|
|
168
|
+ data[0] = (uint8_t)msg;
|
|
169
|
+ memcpy(data + 1, &cache[msg].value, sizeof(double));
|
|
170
|
+ return lora_tx(data, len);
|
|
171
|
+}
|
|
172
|
+
|
|
173
|
+static void lora_sml_handle_cache(void) {
|
|
174
|
+ // find smallest message counter that is ready
|
|
175
|
+ unsigned long min_counter = ULONG_MAX;
|
|
176
|
+ for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
|
|
177
|
+ if (cache[i].ready && (cache[i].counter < min_counter)) {
|
|
178
|
+ min_counter = cache[i].counter;
|
|
179
|
+ }
|
|
180
|
+ }
|
|
181
|
+
|
|
182
|
+ // try to transmit next value with lowest counter
|
|
183
|
+ for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
|
|
184
|
+ if (cache[i].ready && (cache[i].counter == min_counter)) {
|
|
185
|
+ if (lora_sml_cache_send((enum lora_sml_type)i)) {
|
|
186
|
+ if (cache[i].has_next) {
|
|
187
|
+ cache[i].has_next = false;
|
|
188
|
+ cache[i].value = cache[i].next_value;
|
|
189
|
+ cache[i].counter = cache[i].next_counter;
|
|
190
|
+ } else {
|
|
191
|
+ cache[i].ready = false;
|
|
192
|
+ }
|
|
193
|
+ }
|
|
194
|
+ }
|
|
195
|
+ }
|
|
196
|
+}
|
|
197
|
+
|
|
198
|
+void lora_sml_send(enum lora_sml_type msg, double value, unsigned long counter) {
|
|
199
|
+ if (cache[msg].ready) {
|
|
200
|
+ // still waiting to be transmitted, so cache for next cycle
|
|
201
|
+ cache[msg].has_next = true;
|
|
202
|
+ cache[msg].next_value = value;
|
|
203
|
+ cache[msg].next_counter = counter;
|
|
204
|
+ } else {
|
|
205
|
+ // cache as current value, for transmission in this cycle
|
|
206
|
+ cache[msg].ready = true;
|
|
207
|
+ cache[msg].value = value;
|
|
208
|
+ cache[msg].counter = counter;
|
|
209
|
+ }
|
|
210
|
+}
|
|
211
|
+#endif // FEATURE_SML
|
|
212
|
+
|
105
|
213
|
void lora_init(void) {
|
|
214
|
+#ifdef FEATURE_SML
|
|
215
|
+ for (int i = 0; i < LORA_SML_NUM_MESSAGES; i++) {
|
|
216
|
+ cache[i].value = NAN;
|
|
217
|
+ cache[i].next_value = NAN;
|
|
218
|
+ cache[i].ready = false;
|
|
219
|
+ cache[i].has_next = false;
|
|
220
|
+ cache[i].counter = 0;
|
|
221
|
+ cache[i].next_counter = 0;
|
|
222
|
+ }
|
|
223
|
+#endif // FEATURE_SML
|
|
224
|
+
|
106
|
225
|
print_bat();
|
107
|
226
|
|
108
|
227
|
bool success = true;
|
|
@@ -150,54 +269,14 @@ void lora_init(void) {
|
150
|
269
|
return;
|
151
|
270
|
}
|
152
|
271
|
|
|
272
|
+#ifdef FEATURE_SML
|
153
|
273
|
// turn on Ve external 3.3V to power Smart Meter reader
|
154
|
274
|
heltec_ve(true);
|
155
|
|
-}
|
156
|
|
-
|
157
|
|
-static void lora_tx(String data) {
|
158
|
|
- bool tx_legal = millis() > (last_tx + minimum_pause);
|
159
|
|
- if (!tx_legal) {
|
160
|
|
- //debug.printf("Legal limit, wait %i sec.\n", (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
|
161
|
|
- return;
|
162
|
|
- }
|
163
|
|
-
|
164
|
|
- debug.printf("TX [%s] ", String(counter).c_str());
|
165
|
|
- radio.clearDio1Action();
|
166
|
|
-
|
167
|
|
- heltec_led(LORA_LED_BRIGHTNESS);
|
168
|
|
-
|
169
|
|
- bool success = true;
|
170
|
|
- tx_time = millis();
|
171
|
|
- RADIOLIB_CHECK(radio.transmit(data));
|
172
|
|
- tx_time = millis() - tx_time;
|
173
|
|
-
|
174
|
|
- heltec_led(0);
|
175
|
|
-
|
176
|
|
- if (success) {
|
177
|
|
- debug.printf("OK (%i ms)\n", (int)tx_time);
|
178
|
|
- } else {
|
179
|
|
- debug.println("fail");
|
180
|
|
- }
|
181
|
|
-
|
182
|
|
- // Maximum 1% duty cycle
|
183
|
|
- minimum_pause = tx_time * 100;
|
184
|
|
- last_tx = millis();
|
185
|
275
|
|
186
|
|
- radio.setDio1Action(lora_rx);
|
187
|
|
-
|
188
|
|
- success = true;
|
189
|
|
- RADIOLIB_CHECK(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
|
190
|
|
- if (!success) {
|
191
|
|
- use_lora = false;
|
192
|
|
- }
|
193
|
|
-}
|
194
|
|
-
|
195
|
|
-#ifdef FEATURE_SML
|
196
|
|
-void lora_sml_send(double SumWh, double T1Wh, double T2Wh,
|
197
|
|
- double SumW, double L1W, double L2W, double L3W) {
|
198
|
|
-
|
199
|
|
-}
|
|
276
|
+ // send hello msg after boot
|
|
277
|
+ lora_sml_send(LORA_SML_HELLO, -42.23, 0);
|
200
|
278
|
#endif // FEATURE_SML
|
|
279
|
+}
|
201
|
280
|
|
202
|
281
|
void lora_run(void) {
|
203
|
282
|
heltec_loop();
|
|
@@ -219,12 +298,29 @@ void lora_run(void) {
|
219
|
298
|
rx_flag = false;
|
220
|
299
|
|
221
|
300
|
bool success = true;
|
222
|
|
- String data;
|
223
|
|
- RADIOLIB_CHECK(radio.readData(data));
|
|
301
|
+ uint8_t data[sizeof(double) + 1];
|
|
302
|
+ RADIOLIB_CHECK(radio.readData(data, sizeof(data)));
|
224
|
303
|
if (success) {
|
225
|
|
- debug.printf("RX [%i]\n", data.length());
|
|
304
|
+ debug.printf("RX [%i]\n", data[0]);
|
226
|
305
|
debug.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
|
227
|
306
|
debug.printf(" SNR: %.2f dB\n", radio.getSNR());
|
|
307
|
+
|
|
308
|
+ double val = NAN;
|
|
309
|
+ memcpy(&val, data + 1, sizeof(double));
|
|
310
|
+ debug.printf(" Value: %.2f\n", val);
|
|
311
|
+
|
|
312
|
+#ifdef DEBUG_LORA_RX_HEXDUMP
|
|
313
|
+ for (int i = 0; i < sizeof(data); i++) {
|
|
314
|
+ debug.printf("%02X", data[i]);
|
|
315
|
+ if (i < (sizeof(data) - 1)) {
|
|
316
|
+ debug.print(" ");
|
|
317
|
+ } else {
|
|
318
|
+ debug.println();
|
|
319
|
+ }
|
|
320
|
+ }
|
|
321
|
+#endif
|
|
322
|
+
|
|
323
|
+ // TODO payload to influxdb
|
228
|
324
|
}
|
229
|
325
|
|
230
|
326
|
success = true;
|
|
@@ -235,9 +331,14 @@ void lora_run(void) {
|
235
|
331
|
}
|
236
|
332
|
}
|
237
|
333
|
|
|
334
|
+#ifdef FEATURE_SML
|
|
335
|
+ lora_sml_handle_cache();
|
|
336
|
+#endif // FEATURE_SML
|
|
337
|
+
|
|
338
|
+ bool tx_legal = millis() > last_tx + minimum_pause;
|
|
339
|
+
|
238
|
340
|
#ifdef LORA_TEST_TX
|
239
|
341
|
// Transmit a packet every PAUSE seconds or when the button is pressed
|
240
|
|
- bool tx_legal = millis() > last_tx + minimum_pause;
|
241
|
342
|
if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE * 1000)) || button.isSingleClick()) {
|
242
|
343
|
// In case of button click, tell user to wait
|
243
|
344
|
if (!tx_legal) {
|
|
@@ -245,8 +346,21 @@ void lora_run(void) {
|
245
|
346
|
return;
|
246
|
347
|
}
|
247
|
348
|
|
248
|
|
- lora_tx(String(counter++).c_str());
|
|
349
|
+ String s = String(test_counter++);
|
|
350
|
+ lora_tx(s.c_str(), s.length());
|
249
|
351
|
}
|
|
352
|
+#else // LORA_TEST_TX
|
|
353
|
+#ifdef FEATURE_SML
|
|
354
|
+ if (button.isSingleClick()) {
|
|
355
|
+ // In case of button click, tell user to wait
|
|
356
|
+ if (!tx_legal) {
|
|
357
|
+ debug.printf("Legal limit, wait %i sec.\n", (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
|
|
358
|
+ return;
|
|
359
|
+ }
|
|
360
|
+
|
|
361
|
+ lora_sml_send(LORA_SML_HELLO, -23.42, 0);
|
|
362
|
+ }
|
|
363
|
+#endif // FEATURE_SML
|
250
|
364
|
#endif // LORA_TEST_TX
|
251
|
365
|
}
|
252
|
366
|
|