Browse Source

simple protocol on top of lora phy to transmit sml data

Thomas Buck 4 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
 void lora_run(void);
23
 void lora_run(void);
24
 
24
 
25
 #ifdef FEATURE_SML
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
 #endif // FEATURE_SML
43
 #endif // FEATURE_SML
29
 
44
 
30
 #endif // FEATURE_LORA
45
 #endif // FEATURE_LORA

+ 165
- 51
src/lora.cpp View File

27
 // define LORA_TEST_TX to periodically transmit a test message
27
 // define LORA_TEST_TX to periodically transmit a test message
28
 //#define LORA_TEST_TX
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
 #ifdef FEATURE_SML
32
 #ifdef FEATURE_SML
33
 #define LORA_LED_BRIGHTNESS 1 // in percent, 50% brightness is plenty for this LED
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
 #else // FEATURE_SML
35
 #else // FEATURE_SML
35
 #define LORA_LED_BRIGHTNESS 25 // in percent, 50% brightness is plenty for this LED
36
 #define LORA_LED_BRIGHTNESS 25 // in percent, 50% brightness is plenty for this LED
36
 #endif // FEATURE_SML
37
 #endif // FEATURE_SML
82
 
83
 
83
 static unsigned long last_bat_time = 0;
84
 static unsigned long last_bat_time = 0;
84
 static bool use_lora = true;
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
 static volatile bool rx_flag = false;
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
 void lora_oled_init(void) {
105
 void lora_oled_init(void) {
89
     heltec_setup();
106
     heltec_setup();
90
 }
107
 }
102
     rx_flag = true;
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
 void lora_init(void) {
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
     print_bat();
225
     print_bat();
107
 
226
 
108
     bool success = true;
227
     bool success = true;
150
         return;
269
         return;
151
     }
270
     }
152
 
271
 
272
+#ifdef FEATURE_SML
153
     // turn on Ve external 3.3V to power Smart Meter reader
273
     // turn on Ve external 3.3V to power Smart Meter reader
154
     heltec_ve(true);
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
 #endif // FEATURE_SML
278
 #endif // FEATURE_SML
279
+}
201
 
280
 
202
 void lora_run(void) {
281
 void lora_run(void) {
203
     heltec_loop();
282
     heltec_loop();
219
         rx_flag = false;
298
         rx_flag = false;
220
 
299
 
221
         bool success = true;
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
         if (success) {
303
         if (success) {
225
-            debug.printf("RX [%i]\n", data.length());
304
+            debug.printf("RX [%i]\n", data[0]);
226
             debug.printf("  RSSI: %.2f dBm\n", radio.getRSSI());
305
             debug.printf("  RSSI: %.2f dBm\n", radio.getRSSI());
227
             debug.printf("  SNR: %.2f dB\n", radio.getSNR());
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
         success = true;
326
         success = true;
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
 #ifdef LORA_TEST_TX
340
 #ifdef LORA_TEST_TX
239
     // Transmit a packet every PAUSE seconds or when the button is pressed
341
     // Transmit a packet every PAUSE seconds or when the button is pressed
240
-    bool tx_legal = millis() > last_tx + minimum_pause;
241
     if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE * 1000)) || button.isSingleClick()) {
342
     if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE * 1000)) || button.isSingleClick()) {
242
         // In case of button click, tell user to wait
343
         // In case of button click, tell user to wait
243
         if (!tx_legal) {
344
         if (!tx_legal) {
245
             return;
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
 #endif // LORA_TEST_TX
364
 #endif // LORA_TEST_TX
251
 }
365
 }
252
 
366
 

+ 43
- 15
src/smart_meter.cpp View File

22
 #include "lora.h"
22
 #include "lora.h"
23
 #include "smart_meter.h"
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
 static EspSoftwareSerial::UART port1, port2;
30
 static EspSoftwareSerial::UART port1, port2;
31
+static unsigned long counter = 0;
29
 
32
 
30
 static double SumWh = NAN, T1Wh = NAN, T2Wh = NAN;
33
 static double SumWh = NAN, T1Wh = NAN, T2Wh = NAN;
31
 static double SumW = NAN, L1W = NAN, L2W = NAN, L3W = NAN;
34
 static double SumW = NAN, L1W = NAN, L2W = NAN, L3W = NAN;
86
 void sml_init(void) {
89
 void sml_init(void) {
87
     init_vars();
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
 void sml_run(void) {
96
 void sml_run(void) {
100
 
103
 
101
     if (s == SML_START) {
104
     if (s == SML_START) {
102
         init_vars();
105
         init_vars();
106
+        counter++;
103
     } else if (s == SML_LISTEND) {
107
     } else if (s == SML_LISTEND) {
104
         for (unsigned int i = 0; i < (sizeof(handlers) / sizeof(handlers[0])); i++) {
108
         for (unsigned int i = 0; i < (sizeof(handlers) / sizeof(handlers[0])); i++) {
105
             if (smlOBISCheck(handlers[i].OBIS)) {
109
             if (smlOBISCheck(handlers[i].OBIS)) {
109
     } else if (s == SML_FINAL) {
113
     } else if (s == SML_FINAL) {
110
         debug.println("SML Reading:");
114
         debug.println("SML Reading:");
111
 
115
 
112
-        if (!isnan(SumWh))
116
+        if (!isnan(SumWh)) {
113
             debug.printf("Sum: %14.3lf Wh\n", SumWh);
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
             debug.printf(" T1: %14.3lf Wh\n", T1Wh);
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
             debug.printf(" T2: %14.3lf Wh\n", T2Wh);
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
             debug.printf("Sum: %14.3lf W\n", SumW);
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
             debug.printf(" L1: %14.3lf W\n", L1W);
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
             debug.printf(" L2: %14.3lf W\n", L2W);
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
             debug.printf(" L3: %14.3lf W\n", L3W);
159
             debug.printf(" L3: %14.3lf W\n", L3W);
132
-
133
-        debug.println();
134
-
135
 #ifdef FEATURE_LORA
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
 #endif // FEATURE_LORA
162
 #endif // FEATURE_LORA
163
+        }
164
+
165
+        debug.println();
138
     }
166
     }
139
 }
167
 }
140
 
168
 

Loading…
Cancel
Save