Skip to content

Commit 41eb837

Browse files
committed
feature/lora: Enable LoraWAN supported hardware
* Allows OpenEVSE units (with a LoraWAN compatible modem) to announce their status up to several miles to TheThingsNetwork or Helium IoT gateways. * Must be using a ESP32 with a built-in LoRA modem, or by manually attaching a LoRA modem to a stock ESP32
1 parent 751d837 commit 41eb837

File tree

8 files changed

+229
-1
lines changed

8 files changed

+229
-1
lines changed

platformio.ini

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ build_flags =
8787
-D AO_DBG_LEVEL=AO_DL_INFO
8888
-D AO_MG_VERSION_614 ; use MG v6.14
8989
-D AO_CA_CERT_LOCAL ; manage certificate locally
90+
-D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS
91+
-D hal_init=LMICHAL_init ; Workaround mcci arduino-lmic bug 714 on esp32
9092
#-D ENABLE_DEBUG
9193
#-D ENABLE_DEBUG_MONGOOSE_HTTP_CLIENT
9294
-D RAPI_MAX_COMMANDS=20
@@ -316,6 +318,10 @@ upload_speed = 921600
316318

317319
[env:openevse_esp32-heltec-wifi-lora-v2]
318320
board = heltec_wifi_lora_32_V2
321+
lib_deps =
322+
${common.lib_deps}
323+
MCCI LoRaWAN LMIC library
324+
# https://github.com/mcci-catena/arduino-lmic#selecting-the-lorawan-region-configuration
319325
build_flags =
320326
${common.build_flags}
321327
${common.src_build_flags}
@@ -328,3 +334,11 @@ build_flags =
328334
-D RAPI_PORT=Serial1
329335
-D RX1=25
330336
-D TX1=27
337+
-D CFG_us915 ; USA 915Mhz
338+
-D CFG_sx1276_radio ; SX1275 radio
339+
-D ENABLE_LORA=1
340+
-D LORA_NSS=18
341+
-D LORA_RST=14
342+
-D LORA_DIO0=26
343+
-D LORA_DIO1=35
344+
-D LORA_DIO2=34

src/app_config.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ String mqtt_vehicle_range;
6262
String mqtt_vehicle_eta;
6363
String mqtt_announce_topic;
6464

65+
// LoraWAN network settings
66+
String lora_deveui;
67+
String lora_appeui;
68+
String lora_appkey;
69+
6570
// OCPP 1.6 Settings
6671
String ocpp_server;
6772
String ocpp_chargeBoxId;

src/app_config.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ extern String mqtt_vehicle_range;
4949
extern String mqtt_vehicle_eta;
5050
extern String mqtt_announce_topic;
5151

52+
// LoraWAN Settings
53+
extern String lora_deveui;
54+
extern String lora_appeui;
55+
extern String lora_appkey;
56+
5257
// OCPP 1.6 Settings
5358
extern String ocpp_server;
5459
extern String ocpp_chargeBoxId;
@@ -97,6 +102,7 @@ extern uint32_t flags;
97102
#define CONFIG_OCPP_AUTO_AUTH (1 << 22)
98103
#define CONFIG_OCPP_OFFLINE_AUTH (1 << 23)
99104
#define CONFIG_THREEPHASE (1 << 24)
105+
#define CONFIG_SERVICE_LORA (1 << 25)
100106

101107

102108
inline bool config_emoncms_enabled() {
@@ -127,6 +133,10 @@ inline bool config_mqtt_reject_unauthorized() {
127133
return 0 == (flags & CONFIG_MQTT_ALLOW_ANY_CERT);
128134
}
129135

136+
inline bool config_lora_enabled() {
137+
return CONFIG_SERVICE_LORA == (flags & CONFIG_SERVICE_LORA);
138+
}
139+
130140
inline bool config_ocpp_enabled() {
131141
return CONFIG_SERVICE_OCPP == (flags & CONFIG_SERVICE_OCPP);
132142
}

src/input.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,29 @@ void create_rapi_json(JsonDocument &doc)
155155
doc["elapsed"] = evse.getSessionElapsed();
156156
}
157157

158+
/// A small packed report of unit's status
159+
void create_rapi_packed(uint8_t *data)
160+
{
161+
if (sizeof(data[0]) / sizeof(data) != 8) {
162+
DBUGF("create_rapi_packed: Incorrect data size passed!");
163+
return;
164+
}
165+
// Values with potential > 255 are reduced via a
166+
// conversion factor.
167+
data[0] = evse.getEvseState();
168+
data[1] = evse.getVoltage() / 2; // CF * 2
169+
data[2] = evse.getAmps();
170+
data[3] = evse.getChargeCurrent();
171+
data[4] = evse.getSessionElapsed() / 60; // CF * 60
172+
173+
if(evse.isTemperatureValid(EVSE_MONITOR_TEMP_MONITOR))
174+
data[5] = evse.getTemperature(EVSE_MONITOR_TEMP_MONITOR) * TEMP_SCALE_FACTOR;
175+
if(evse.isTemperatureValid(EVSE_MONITOR_TEMP_MAX))
176+
data[6] = evse.getTemperature(EVSE_MONITOR_TEMP_MAX) * TEMP_SCALE_FACTOR;
177+
178+
// data[7] what?
179+
}
180+
158181
void
159182
handleRapiRead()
160183
{

src/input.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ extern String ohm_hour;
1717

1818
extern void handleRapiRead();
1919
extern void create_rapi_json(JsonDocument &data);
20+
extern void create_rapi_packed(uint8_t* data);
2021

2122
extern void input_setup();
2223

src/lora.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright (c) 2019-2020 Alexander von Gluck IV for OpenEVSE
3+
*
4+
* -------------------------------------------------------------------
5+
*
6+
* Additional Adaptation of OpenEVSE ESP Wifi
7+
* by Trystan Lea, Glyn Hudson, OpenEnergyMonitor
8+
* All adaptation GNU General Public License as below.
9+
*
10+
* -------------------------------------------------------------------
11+
*
12+
* This file is part of Open EVSE.
13+
* Open EVSE is free software; you can redistribute it and/or modify
14+
* it under the terms of the GNU General Public License as published by
15+
* the Free Software Foundation; either version 3, or (at your option)
16+
* any later version.
17+
* Open EVSE is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU General Public License for more details.
21+
* You should have received a copy of the GNU General Public License
22+
* along with Open EVSE; see the file COPYING. If not, write to the
23+
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24+
* Boston, MA 02111-1307, USA.
25+
*/
26+
#ifdef ENABLE_LORA
27+
28+
#include <lmic.h>
29+
#include <hal/hal.h>
30+
#include <SPI.h>
31+
32+
#include "emonesp.h"
33+
#include "lora.h"
34+
35+
#include "app_config.h"
36+
37+
38+
#define LORA_HTOI(c) ((c<='9')?(c-'0'):((c<='F')?(c-'A'+10):((c<='f')?(c-'a'+10):(0))))
39+
#define LORA_TWO_HTOI(h, l) ((LORA_HTOI(h) << 4) + LORA_HTOI(l))
40+
#define LORA_HEX_TO_BYTE(a, h, n) { for (int i = 0; i < n; i++) (a)[i] = LORA_TWO_HTOI(h[2*i], h[2*i + 1]); }
41+
#define LORA_DEVADDR(a) (uint32_t) ((uint32_t) (a)[3] | (uint32_t) (a)[2] << 8 | (uint32_t) (a)[1] << 16 | (uint32_t) (a)[0] << 24)
42+
43+
#define ANNOUNCE_INTERVAL 30 * 1000 // (In Milliseconds)
44+
45+
46+
// TODO: Store these via WebUI? We're doing (simple) ABP activation for now
47+
//const char *devAddr = "00000000";
48+
//const char *nwkSKey = "00000000000000000000000000000000";
49+
//const char *appSKey = "00000000000000000000000000000000";
50+
51+
52+
// LoRaWAN credentials to use
53+
static uint8_t DEVADDR[4];
54+
static uint8_t NWKSKEY[16];
55+
static uint8_t APPSKEY[16];
56+
57+
// Next LoRaWAN announcement
58+
unsigned long nextAnnounce;
59+
60+
// LoRa module pin mapping
61+
const lmic_pinmap lmic_pins = {
62+
.nss = LORA_NSS,
63+
.rxtx = LMIC_UNUSED_PIN,
64+
.rst = LORA_RST,
65+
.dio = {LORA_DIO0, LORA_DIO1, LORA_DIO2},
66+
};
67+
68+
// Used for OTAA, not used (yet)
69+
void os_getArtEui (u1_t* buf) { }
70+
void os_getDevEui (u1_t* buf) { }
71+
void os_getDevKey (u1_t* buf) { }
72+
73+
74+
void onEvent(ev_t ev) {
75+
switch (ev) {
76+
case EV_TXCOMPLETE:
77+
DBUGF("LoRa: TX Complete.");
78+
// LoRaWAN transmission complete
79+
if (LMIC.txrxFlags & TXRX_ACK) {
80+
// Received ack
81+
DBUGF("LoRa: TX ack.");
82+
}
83+
break;
84+
case EV_TXSTART:
85+
DBUGF("LoRa: TX Begin.");
86+
break;
87+
default:
88+
// Ignore anything else for now
89+
break;
90+
}
91+
}
92+
93+
/// Reset LoRa modem. Reload LoRaWAN keys
94+
void lora_reset()
95+
{
96+
LORA_HEX_TO_BYTE(DEVADDR, lora_deveui.c_str(), 4);
97+
LORA_HEX_TO_BYTE(NWKSKEY, lora_appeui.c_str(), 16);
98+
LORA_HEX_TO_BYTE(APPSKEY, lora_appkey.c_str(), 16);
99+
100+
LMIC_reset();
101+
LMIC_setSession (0x13, LORA_DEVADDR(DEVADDR), NWKSKEY, APPSKEY);
102+
LMIC_setAdrMode(0);
103+
LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100);
104+
LMIC_selectSubBand(1);
105+
LMIC_setLinkCheckMode(0);
106+
LMIC.dn2Dr = DR_SF7;
107+
}
108+
109+
/// Initial setup of LoRa modem.
110+
void lora_setup()
111+
{
112+
Profile_Start(lora_setup);
113+
114+
os_init();
115+
lora_reset();
116+
117+
// Set us up for an immeadiate announcement
118+
nextAnnounce = millis();
119+
120+
Profile_End(lora_setup, 1);
121+
}
122+
123+
/// Announce our status to LoraWAN if it's time
124+
void lora_publish(uint8_t *dataPacket)
125+
{
126+
if (millis() < nextAnnounce)
127+
return;
128+
129+
Profile_Start(lora_loop);
130+
DBUGF("LoRa: Starting LoRaWAN broadcast...");
131+
// Check if there is not a current TX/RX job running
132+
if (LMIC.opmode & OP_TXRXPEND) {
133+
DBUGF("LoRa: Modem busy. Retry later");
134+
return;
135+
}
136+
137+
LMIC_setTxData2(1, dataPacket, sizeof(dataPacket), true);
138+
nextAnnounce = millis() + ANNOUNCE_INTERVAL;
139+
140+
Profile_End(lora_loop, 1);
141+
}
142+
143+
#else /* !ENABLE_LORA */
144+
145+
#include "emonesp.h"
146+
#include "app_config.h"
147+
148+
void lora_setup() { /*NOP*/ }
149+
void lora_reset() { /*NOP*/ }
150+
void lora_publish(uint8_t *dataPacket) { /*NOP*/ }
151+
152+
#endif

src/lora.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef _LORA_H
2+
#define _LORA_H
3+
4+
5+
void lora_setup();
6+
void lora_reset();
7+
void lora_publish(uint8_t *dataPacket);
8+
9+
10+
#endif // _LORA_H

src/main.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "divert.h"
4343
#include "ota.h"
4444
#include "lcd.h"
45+
#include "lora.h"
4546
#include "openevse.h"
4647
#include "root_ca.h"
4748
#include "espal.h"
@@ -139,6 +140,12 @@ void setup()
139140
net_setup();
140141
DBUGF("After net_setup: %d", ESPAL.getFreeHeap());
141142

143+
#ifdef ENABLE_LORA
144+
// initialise LoRA if supported
145+
lora_setup();
146+
DBUGF("After lora_setup: %d" ESPAL.getFreeHeap());
147+
#endif
148+
142149
// Initialise Mongoose networking library
143150
Mongoose.begin();
144151
Mongoose.setRootCa(root_ca);
@@ -248,6 +255,12 @@ loop() {
248255
}
249256
} // end WiFi connected
250257

258+
#ifdef ENABLE_LORA
259+
uint8_t loraPacket[8];
260+
create_rapi_packed(loraPacket);
261+
lora_publish(loraPacket);
262+
#endif
263+
251264
if(DEBUG_PORT.available()) {
252265
handle_serial();
253266
}
@@ -335,4 +348,4 @@ void handle_serial()
335348
DEBUG_PORT.printf("{\"code\":200,\"msg\":\"%s\"}\n", config_modified ? "done" : "no change");
336349
}
337350
}
338-
}
351+
}

0 commit comments

Comments
 (0)