Browse Source

simple protocol on top of lora phy to transmit sml data

Thomas Buck 5 months ago
parent
commit
0294b07280
3 changed files with 225 additions and 68 deletions
  1. 17
    2
      include/lora.h
  2. 165
    51
      src/lora.cpp
  3. 43
    15
      src/smart_meter.cpp

+ 17
- 2
include/lora.h View File

@@ -23,8 +23,23 @@ void lora_init(void);
23 23
 void lora_run(void);
24 24
 
25 25
 #ifdef FEATURE_SML
26
-void lora_sml_send(double SumWh, double T1Wh, double T2Wh,
27
-                   double SumW, double L1W, double L2W, double L3W);
26
+
27
+enum lora_sml_type {
28
+    LORA_SML_HELLO = 0,
29
+    LORA_SML_SUM_WH,
30
+    LORA_SML_T1_WH,
31
+    LORA_SML_T2_WH,
32
+    LORA_SML_SUM_W,
33
+    LORA_SML_L1_W,
34
+    LORA_SML_L2_W,
35
+    LORA_SML_L3_W,
36
+    LORA_SML_BAT_V,
37
+
38
+    LORA_SML_NUM_MESSAGES
39
+};
40
+
41
+void lora_sml_send(enum lora_sml_type msg, double value, unsigned long counter);
42
+
28 43
 #endif // FEATURE_SML
29 44
 
30 45
 #endif // FEATURE_LORA

+ 165
- 51
src/lora.cpp View File

@@ -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
 

+ 43
- 15
src/smart_meter.cpp View File

@@ -22,10 +22,13 @@
22 22
 #include "lora.h"
23 23
 #include "smart_meter.h"
24 24
 
25
-#define SML_TX 47
26
-#define SML_RX 48
25
+#define SML_TX 33
26
+#define SML_RX 34
27
+#define SML_BAUD 9600
28
+#define SML_PARAM SWSERIAL_8N1
27 29
 
28 30
 static EspSoftwareSerial::UART port1, port2;
31
+static unsigned long counter = 0;
29 32
 
30 33
 static double SumWh = NAN, T1Wh = NAN, T2Wh = NAN;
31 34
 static double SumW = NAN, L1W = NAN, L2W = NAN, L3W = NAN;
@@ -86,8 +89,8 @@ static OBISHandler handlers[] = {
86 89
 void sml_init(void) {
87 90
     init_vars();
88 91
 
89
-    port1.begin(9600, SWSERIAL_8N1, SML_RX, -1, false);
90
-    port2.begin(9600, SWSERIAL_8N1, SML_TX, -1, false);
92
+    port1.begin(SML_BAUD, SML_PARAM, SML_RX, -1, false);
93
+    port2.begin(SML_BAUD, SML_PARAM, SML_TX, -1, false);
91 94
 }
92 95
 
93 96
 void sml_run(void) {
@@ -100,6 +103,7 @@ void sml_run(void) {
100 103
 
101 104
     if (s == SML_START) {
102 105
         init_vars();
106
+        counter++;
103 107
     } else if (s == SML_LISTEND) {
104 108
         for (unsigned int i = 0; i < (sizeof(handlers) / sizeof(handlers[0])); i++) {
105 109
             if (smlOBISCheck(handlers[i].OBIS)) {
@@ -109,32 +113,56 @@ void sml_run(void) {
109 113
     } else if (s == SML_FINAL) {
110 114
         debug.println("SML Reading:");
111 115
 
112
-        if (!isnan(SumWh))
116
+        if (!isnan(SumWh)) {
113 117
             debug.printf("Sum: %14.3lf Wh\n", SumWh);
118
+#ifdef FEATURE_LORA
119
+            lora_sml_send(LORA_SML_SUM_WH, SumWh, counter);
120
+#endif // FEATURE_LORA
121
+        }
114 122
 
115
-        if (!isnan(T1Wh))
123
+        if (!isnan(T1Wh)) {
116 124
             debug.printf(" T1: %14.3lf Wh\n", T1Wh);
125
+#ifdef FEATURE_LORA
126
+            lora_sml_send(LORA_SML_T1_WH, T1Wh, counter);
127
+#endif // FEATURE_LORA
128
+        }
117 129
 
118
-        if (!isnan(T2Wh))
130
+        if (!isnan(T2Wh)) {
119 131
             debug.printf(" T2: %14.3lf Wh\n", T2Wh);
132
+#ifdef FEATURE_LORA
133
+            lora_sml_send(LORA_SML_T2_WH, T2Wh, counter);
134
+#endif // FEATURE_LORA
135
+        }
120 136
 
121
-        if (!isnan(SumW))
137
+        if (!isnan(SumW)) {
122 138
             debug.printf("Sum: %14.3lf W\n", SumW);
139
+#ifdef FEATURE_LORA
140
+            lora_sml_send(LORA_SML_SUM_W, SumW, counter);
141
+#endif // FEATURE_LORA
142
+        }
123 143
 
124
-        if (!isnan(L1W))
144
+        if (!isnan(L1W)) {
125 145
             debug.printf(" L1: %14.3lf W\n", L1W);
146
+#ifdef FEATURE_LORA
147
+            lora_sml_send(LORA_SML_L1_W, L1W, counter);
148
+#endif // FEATURE_LORA
149
+        }
126 150
 
127
-        if (!isnan(L2W))
151
+        if (!isnan(L2W)) {
128 152
             debug.printf(" L2: %14.3lf W\n", L2W);
153
+#ifdef FEATURE_LORA
154
+            lora_sml_send(LORA_SML_L2_W, L2W, counter);
155
+#endif // FEATURE_LORA
156
+        }
129 157
 
130
-        if (!isnan(L3W))
158
+        if (!isnan(L3W)) {
131 159
             debug.printf(" L3: %14.3lf W\n", L3W);
132
-
133
-        debug.println();
134
-
135 160
 #ifdef FEATURE_LORA
136
-        lora_sml_send(SumWh, T1Wh, T2Wh, SumW, L1W, L2W, L3W);
161
+            lora_sml_send(LORA_SML_L3_W, L3W, counter);
137 162
 #endif // FEATURE_LORA
163
+        }
164
+
165
+        debug.println();
138 166
     }
139 167
 }
140 168
 

Loading…
Cancel
Save