Skip to content

Commit f3f32f8

Browse files
committed
feature/lora: Add LoraWAN communications on 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 * Needs gui changes too.. however holding on that until better tested
1 parent 751d837 commit f3f32f8

File tree

6 files changed

+303
-1
lines changed

6 files changed

+303
-1
lines changed

platformio.ini

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ build_flags =
9797
build_partitions = min_spiffs.csv
9898
build_partitions_debug = min_spiffs_debug.csv
9999

100+
# Used for LoRaWAN builds
101+
# https://github.com/mcci-catena/arduino-lmic#selecting-the-lorawan-region-configuration
102+
lora_lib =
103+
MCCI LoRaWAN LMIC library
104+
aparcar/CayenneLPP@^1.3.0
105+
lora_build_flags =
106+
-D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS
107+
-D hal_init=LMICHAL_init ; Workaround mcci arduino-lmic bug 714 on esp32
108+
-D CFG_us915 ; USA 915Mhz
109+
100110
neopixel_lib = adafruit/Adafruit [email protected]
101111

102112

@@ -316,9 +326,13 @@ upload_speed = 921600
316326

317327
[env:openevse_esp32-heltec-wifi-lora-v2]
318328
board = heltec_wifi_lora_32_V2
329+
lib_deps =
330+
${common.lib_deps}
331+
${common.lora_lib}
319332
build_flags =
320333
${common.build_flags}
321334
${common.src_build_flags}
335+
${common.lora_build_flags}
322336
${common.version}.dev
323337
-D DEBUG_PORT=Serial
324338
-D WIFI_LED=25
@@ -328,3 +342,10 @@ build_flags =
328342
-D RAPI_PORT=Serial1
329343
-D RX1=25
330344
-D TX1=27
345+
-D CFG_sx1276_radio ; SX1275 radio
346+
-D ENABLE_LORA=1
347+
-D LORA_NSS=18
348+
-D LORA_RST=14
349+
-D LORA_DIO0=26
350+
-D LORA_DIO1=35
351+
-D LORA_DIO2=34

src/app_config.cpp

Lines changed: 23 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;
@@ -181,6 +186,11 @@ ConfigOpt *opts[] =
181186
// RFID storage
182187
new ConfigOptDefenition<String>(rfid_storage, "", "rfid_storage", "rs"),
183188

189+
// Lora settings
190+
new ConfigOptDefenition<String>(lora_deveui, "", "lora_deveui", "lde"),
191+
new ConfigOptDefenition<String>(lora_appeui, "", "lora_appeui", "lae"),
192+
new ConfigOptDefenition<String>(lora_appeui, "", "lora_appkey", "lak"),
193+
184194
#if RGB_LED
185195
// LED brightness
186196
new ConfigOptDefenition<uint8_t>(led_brightness, LED_DEFAULT_BRIGHTNESS, "led_brightness", "lb"),
@@ -439,6 +449,19 @@ config_save_ohm(bool enable, String qohm)
439449
user_config.commit();
440450
}
441451

452+
void
453+
config_save_lora(bool enable, String devEui, String appEui, String appKey)
454+
{
455+
uint32_t newflags = flags & ~CONFIG_LORA;
456+
if(enable)
457+
newflags |= CONFIG_LORA;
458+
459+
user_config.set("flags", newflags);
460+
user_config.set("lora_deveui", devEui);
461+
user_config.set("lora_appeui", appEui);
462+
user_config.set("lora_appkey", appKey);
463+
}
464+
442465
void
443466
config_save_rfid(bool enable, String storage){
444467
uint32_t newflags = flags & ~CONFIG_RFID;

src/app_config.h

Lines changed: 15 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_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_LORA == (flags & CONFIG_LORA);
138+
}
139+
130140
inline bool config_ocpp_enabled() {
131141
return CONFIG_SERVICE_OCPP == (flags & CONFIG_SERVICE_OCPP);
132142
}
@@ -232,6 +242,11 @@ extern void config_save_ohm(bool enable, String qohm);
232242
// -------------------------------------------------------------------
233243
extern void config_save_rfid(bool enable, String storage);
234244

245+
// -------------------------------------------------------------------
246+
// Save Lora settings
247+
// -------------------------------------------------------------------
248+
extern void config_save_lora(bool enable, String devEui, String appEui, String appKey);
249+
235250
// -------------------------------------------------------------------
236251
// Save the flags
237252
// -------------------------------------------------------------------

src/lora.cpp

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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 "input.h"
34+
#include "lora.h"
35+
36+
#include "app_config.h"
37+
38+
39+
#define LORA_HTOI(c) ((c<='9')?(c-'0'):((c<='F')?(c-'A'+10):((c<='f')?(c-'a'+10):(0))))
40+
#define LORA_TWO_HTOI(h, l) ((LORA_HTOI(h) << 4) + LORA_HTOI(l))
41+
#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]); }
42+
#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)
43+
44+
#define ANNOUNCE_INTERVAL 30 * 1000 // (In Milliseconds)
45+
46+
47+
// LoRa module pin mapping
48+
const lmic_pinmap lmic_pins = {
49+
.nss = LORA_NSS,
50+
.rxtx = LMIC_UNUSED_PIN,
51+
.rst = LORA_RST,
52+
.dio = {LORA_DIO0, LORA_DIO1, LORA_DIO2},
53+
};
54+
55+
// Used for OTAA, not used (yet)
56+
void os_getArtEui (u1_t* buf) { }
57+
void os_getDevEui (u1_t* buf) { }
58+
void os_getDevKey (u1_t* buf) { }
59+
60+
void
61+
create_rapi_cayennelpp(EvseManager* _evse, CayenneLPP* lpp)
62+
{
63+
if (_evse == NULL) {
64+
DBUGF("Corrupt EvseManager!")
65+
return;
66+
}
67+
if (lpp == NULL) {
68+
DBUGF("Corrupt CayenneLPP buffer!")
69+
return;
70+
}
71+
72+
lpp->reset();
73+
lpp->addDigitalInput(0, _evse->getEvseState());
74+
lpp->addAnalogInput(1, _evse->getVoltage());
75+
lpp->addAnalogInput(2, _evse->getAmps());
76+
lpp->addAnalogInput(3, _evse->getChargeCurrent());
77+
lpp->addDigitalInput(4, _evse->getSessionElapsed() / 60);
78+
if(evse.isTemperatureValid(EVSE_MONITOR_TEMP_MONITOR))
79+
lpp->addTemperature(5, _evse->getTemperature(EVSE_MONITOR_TEMP_MONITOR) * TEMP_SCALE_FACTOR);
80+
if(evse.isTemperatureValid(EVSE_MONITOR_TEMP_MAX))
81+
lpp->addTemperature(6, _evse->getTemperature(EVSE_MONITOR_TEMP_MAX) * TEMP_SCALE_FACTOR);
82+
}
83+
84+
/// Reset LoRa modem. Reload LoRaWAN keys
85+
void onEvent(ev_t ev) {
86+
switch (ev) {
87+
case EV_TXCOMPLETE:
88+
DBUGF("LoRa: TX Complete.");
89+
// LoRaWAN transmission complete
90+
if (LMIC.txrxFlags & TXRX_ACK) {
91+
// Received ack
92+
DBUGF("LoRa: TX ack.");
93+
}
94+
break;
95+
case EV_TXSTART:
96+
DBUGF("LoRa: TX Begin.");
97+
break;
98+
default:
99+
// Ignore anything else for now
100+
break;
101+
}
102+
}
103+
104+
LoraTask::LoraTask()
105+
:
106+
MicroTasks::Task()
107+
{
108+
}
109+
110+
void
111+
LoraTask::begin(EvseManager &evse)
112+
{
113+
_evse = &evse;
114+
MicroTask.startTask(this);
115+
}
116+
117+
/// Initial setup of LoRa modem.
118+
void
119+
LoraTask::setup()
120+
{
121+
Profile_Start(LoraTask::setup);
122+
123+
os_init();
124+
modem_reset();
125+
126+
Profile_End(LoraTask::setup, 1);
127+
}
128+
129+
/// Reset LoRa modem. Reload LoRaWAN keys
130+
void
131+
LoraTask::modem_reset()
132+
{
133+
Profile_Start(LoraTask::modem_reset);
134+
// LoRaWAN credentials to use
135+
uint8_t DEVADDR[4];
136+
uint8_t NWKSKEY[16];
137+
uint8_t APPSKEY[16];
138+
139+
LORA_HEX_TO_BYTE(DEVADDR, lora_deveui.c_str(), 4);
140+
LORA_HEX_TO_BYTE(NWKSKEY, lora_appeui.c_str(), 16);
141+
LORA_HEX_TO_BYTE(APPSKEY, lora_appkey.c_str(), 16);
142+
143+
LMIC_reset();
144+
LMIC_setSession (0x13, LORA_DEVADDR(DEVADDR), NWKSKEY, APPSKEY);
145+
LMIC_setAdrMode(0);
146+
LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100);
147+
LMIC_selectSubBand(1);
148+
LMIC_setLinkCheckMode(0);
149+
LMIC.dn2Dr = DR_SF7;
150+
Profile_End(LoraTask::modem_reset, 1);
151+
}
152+
153+
/// Announce our status to LoraWAN if it's time
154+
void
155+
LoraTask::publish(CayenneLPP* lpp)
156+
{
157+
Profile_Start(LoraTask::publish);
158+
DBUGF("LoRa: Starting LoRaWAN broadcast...");
159+
// Check if there is not a current TX/RX job running
160+
if (LMIC.opmode & OP_TXRXPEND) {
161+
DBUGF("LoRa: Modem busy. Retry later");
162+
return;
163+
}
164+
LMIC_setTxData2(1, lpp->getBuffer(), lpp->getSize(), false);
165+
Profile_End(LoraTask::publish, 1);
166+
}
167+
168+
unsigned long
169+
LoraTask::loop(MicroTasks::WakeReason reason)
170+
{
171+
if (!config_lora_enabled())
172+
return 1000;
173+
174+
CayenneLPP lpp(24);
175+
create_rapi_cayennelpp(_evse, &lpp);
176+
lora.publish(&lpp);
177+
return ANNOUNCE_INTERVAL;
178+
}
179+
180+
LoraTask lora;
181+
182+
#endif /* ENABLE_LORA */

src/lora.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2019-2023 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+
#ifndef _LORA_H
28+
#define _LORA_H
29+
30+
#include <CayenneLPP.h>
31+
#include <MicroTasks.h>
32+
33+
#include "evse_man.h"
34+
35+
void create_rapi_cayennelpp(EvseManager* _evse, CayenneLPP* lpp);
36+
37+
class LoraTask : public MicroTasks::Task {
38+
private:
39+
EvseManager* _evse;
40+
41+
protected:
42+
void setup();
43+
unsigned long loop(MicroTasks::WakeReason reason);
44+
void publish(CayenneLPP* lpp);
45+
void modem_reset();
46+
47+
public:
48+
LoraTask();
49+
void begin(EvseManager &evse);
50+
};
51+
52+
extern LoraTask lora;
53+
54+
#endif // _LORA_H
55+
#endif /* ENABLE_LORA */

src/main.cpp

Lines changed: 7 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"
@@ -156,6 +157,11 @@ void setup()
156157

157158
ocpp.begin(evse, lcd, eventLog, rfid);
158159

160+
#ifdef ENABLE_LORA
161+
// initialise LoRA if supported
162+
lora.begin(evse);
163+
#endif
164+
159165
shaper.begin(evse);
160166

161167
lcd.display(F("OpenEVSE WiFI"), 0, 0, 0, LCD_CLEAR_LINE);
@@ -335,4 +341,4 @@ void handle_serial()
335341
DEBUG_PORT.printf("{\"code\":200,\"msg\":\"%s\"}\n", config_modified ? "done" : "no change");
336342
}
337343
}
338-
}
344+
}

0 commit comments

Comments
 (0)