diff --git a/platformio.ini b/platformio.ini index dd04a868..59ea7e72 100644 --- a/platformio.ini +++ b/platformio.ini @@ -97,6 +97,16 @@ build_flags = build_partitions = min_spiffs.csv build_partitions_debug = min_spiffs_debug.csv +# Used for LoRaWAN builds +# https://github.com/mcci-catena/arduino-lmic#selecting-the-lorawan-region-configuration +lora_lib = + MCCI LoRaWAN LMIC library + aparcar/CayenneLPP@^1.3.0 +lora_build_flags = + -D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS + -D hal_init=LMICHAL_init ; Workaround mcci arduino-lmic bug 714 on esp32 + -D CFG_us915 ; USA 915Mhz + neopixel_lib = adafruit/Adafruit NeoPixel@1.7.0 @@ -316,9 +326,13 @@ upload_speed = 921600 [env:openevse_esp32-heltec-wifi-lora-v2] board = heltec_wifi_lora_32_V2 +lib_deps = + ${common.lib_deps} + ${common.lora_lib} build_flags = ${common.build_flags} ${common.src_build_flags} + ${common.lora_build_flags} ${common.version}.dev -D DEBUG_PORT=Serial -D WIFI_LED=25 @@ -328,3 +342,10 @@ build_flags = -D RAPI_PORT=Serial1 -D RX1=25 -D TX1=27 + -D CFG_sx1276_radio ; SX1275 radio + -D ENABLE_LORA=1 + -D LORA_NSS=18 + -D LORA_RST=14 + -D LORA_DIO0=26 + -D LORA_DIO1=35 + -D LORA_DIO2=34 diff --git a/src/app_config.cpp b/src/app_config.cpp index 5fffaa41..8e3b0c49 100644 --- a/src/app_config.cpp +++ b/src/app_config.cpp @@ -62,6 +62,11 @@ String mqtt_vehicle_range; String mqtt_vehicle_eta; String mqtt_announce_topic; +// LoraWAN network settings +String lora_deveui; +String lora_appeui; +String lora_appkey; + // OCPP 1.6 Settings String ocpp_server; String ocpp_chargeBoxId; @@ -181,6 +186,11 @@ ConfigOpt *opts[] = // RFID storage new ConfigOptDefenition(rfid_storage, "", "rfid_storage", "rs"), +// Lora settings + new ConfigOptDefenition(lora_deveui, "", "lora_deveui", "lde"), + new ConfigOptDefenition(lora_appeui, "", "lora_appeui", "lae"), + new ConfigOptDefenition(lora_appeui, "", "lora_appkey", "lak"), + #if RGB_LED // LED brightness new ConfigOptDefenition(led_brightness, LED_DEFAULT_BRIGHTNESS, "led_brightness", "lb"), @@ -439,6 +449,19 @@ config_save_ohm(bool enable, String qohm) user_config.commit(); } +void +config_save_lora(bool enable, String devEui, String appEui, String appKey) +{ + uint32_t newflags = flags & ~CONFIG_LORA; + if(enable) + newflags |= CONFIG_LORA; + + user_config.set("flags", newflags); + user_config.set("lora_deveui", devEui); + user_config.set("lora_appeui", appEui); + user_config.set("lora_appkey", appKey); +} + void config_save_rfid(bool enable, String storage){ uint32_t newflags = flags & ~CONFIG_RFID; diff --git a/src/app_config.h b/src/app_config.h index 9648e5cd..d36a5976 100644 --- a/src/app_config.h +++ b/src/app_config.h @@ -49,6 +49,11 @@ extern String mqtt_vehicle_range; extern String mqtt_vehicle_eta; extern String mqtt_announce_topic; +// LoraWAN Settings +extern String lora_deveui; +extern String lora_appeui; +extern String lora_appkey; + // OCPP 1.6 Settings extern String ocpp_server; extern String ocpp_chargeBoxId; @@ -97,6 +102,7 @@ extern uint32_t flags; #define CONFIG_OCPP_AUTO_AUTH (1 << 22) #define CONFIG_OCPP_OFFLINE_AUTH (1 << 23) #define CONFIG_THREEPHASE (1 << 24) +#define CONFIG_LORA (1 << 25) inline bool config_emoncms_enabled() { @@ -127,6 +133,10 @@ inline bool config_mqtt_reject_unauthorized() { return 0 == (flags & CONFIG_MQTT_ALLOW_ANY_CERT); } +inline bool config_lora_enabled() { + return CONFIG_LORA == (flags & CONFIG_LORA); +} + inline bool config_ocpp_enabled() { return CONFIG_SERVICE_OCPP == (flags & CONFIG_SERVICE_OCPP); } @@ -232,6 +242,11 @@ extern void config_save_ohm(bool enable, String qohm); // ------------------------------------------------------------------- extern void config_save_rfid(bool enable, String storage); +// ------------------------------------------------------------------- +// Save Lora settings +// ------------------------------------------------------------------- +extern void config_save_lora(bool enable, String devEui, String appEui, String appKey); + // ------------------------------------------------------------------- // Save the flags // ------------------------------------------------------------------- diff --git a/src/lora.cpp b/src/lora.cpp new file mode 100644 index 00000000..0b941637 --- /dev/null +++ b/src/lora.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2019-2020 Alexander von Gluck IV for OpenEVSE + * + * ------------------------------------------------------------------- + * + * Additional Adaptation of OpenEVSE ESP Wifi + * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor + * All adaptation GNU General Public License as below. + * + * ------------------------------------------------------------------- + * + * This file is part of Open EVSE. + * Open EVSE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * Open EVSE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with Open EVSE; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef ENABLE_LORA + +#include +#include +#include + +#include "emonesp.h" +#include "input.h" +#include "lora.h" + +#include "app_config.h" + + +#define LORA_HTOI(c) ((c<='9')?(c-'0'):((c<='F')?(c-'A'+10):((c<='f')?(c-'a'+10):(0)))) +#define LORA_TWO_HTOI(h, l) ((LORA_HTOI(h) << 4) + LORA_HTOI(l)) +#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]); } +#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) + +#define ANNOUNCE_INTERVAL 30 * 1000 // (In Milliseconds) + + +// LoRa module pin mapping +const lmic_pinmap lmic_pins = { + .nss = LORA_NSS, + .rxtx = LMIC_UNUSED_PIN, + .rst = LORA_RST, + .dio = {LORA_DIO0, LORA_DIO1, LORA_DIO2}, +}; + +// Used for OTAA, not used (yet) +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +void +create_rapi_cayennelpp(EvseManager* _evse, CayenneLPP* lpp) +{ + if (_evse == NULL) { + DBUGF("Corrupt EvseManager!") + return; + } + if (lpp == NULL) { + DBUGF("Corrupt CayenneLPP buffer!") + return; + } + + lpp->reset(); + lpp->addDigitalInput(0, _evse->getEvseState()); + lpp->addAnalogInput(1, _evse->getVoltage()); + lpp->addAnalogInput(2, _evse->getAmps()); + lpp->addAnalogInput(3, _evse->getChargeCurrent()); + lpp->addDigitalInput(4, _evse->getSessionElapsed() / 60); + if(evse.isTemperatureValid(EVSE_MONITOR_TEMP_MONITOR)) + lpp->addTemperature(5, _evse->getTemperature(EVSE_MONITOR_TEMP_MONITOR) * TEMP_SCALE_FACTOR); + if(evse.isTemperatureValid(EVSE_MONITOR_TEMP_MAX)) + lpp->addTemperature(6, _evse->getTemperature(EVSE_MONITOR_TEMP_MAX) * TEMP_SCALE_FACTOR); +} + +/// Reset LoRa modem. Reload LoRaWAN keys +void onEvent(ev_t ev) { + switch (ev) { + case EV_TXCOMPLETE: + DBUGF("LoRa: TX Complete."); + // LoRaWAN transmission complete + if (LMIC.txrxFlags & TXRX_ACK) { + // Received ack + DBUGF("LoRa: TX ack."); + } + break; + case EV_TXSTART: + DBUGF("LoRa: TX Begin."); + break; + default: + // Ignore anything else for now + break; + } +} + +LoraTask::LoraTask() + : + MicroTasks::Task() +{ +} + +void +LoraTask::begin(EvseManager &evse) +{ + _evse = &evse; + MicroTask.startTask(this); +} + +/// Initial setup of LoRa modem. +void +LoraTask::setup() +{ + Profile_Start(LoraTask::setup); + + os_init(); + modem_reset(); + + Profile_End(LoraTask::setup, 1); +} + +/// Reset LoRa modem. Reload LoRaWAN keys +void +LoraTask::modem_reset() +{ + Profile_Start(LoraTask::modem_reset); + // LoRaWAN credentials to use + uint8_t DEVADDR[4]; + uint8_t NWKSKEY[16]; + uint8_t APPSKEY[16]; + + LORA_HEX_TO_BYTE(DEVADDR, lora_deveui.c_str(), 4); + LORA_HEX_TO_BYTE(NWKSKEY, lora_appeui.c_str(), 16); + LORA_HEX_TO_BYTE(APPSKEY, lora_appkey.c_str(), 16); + + LMIC_reset(); + LMIC_setSession (0x13, LORA_DEVADDR(DEVADDR), NWKSKEY, APPSKEY); + LMIC_setAdrMode(0); + LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100); + LMIC_selectSubBand(1); + LMIC_setLinkCheckMode(0); + LMIC.dn2Dr = DR_SF7; + Profile_End(LoraTask::modem_reset, 1); +} + +/// Announce our status to LoraWAN if it's time +void +LoraTask::publish(CayenneLPP* lpp) +{ + Profile_Start(LoraTask::publish); + DBUGF("LoRa: Starting LoRaWAN broadcast..."); + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + DBUGF("LoRa: Modem busy. Retry later"); + return; + } + LMIC_setTxData2(1, lpp->getBuffer(), lpp->getSize(), false); + Profile_End(LoraTask::publish, 1); +} + +unsigned long +LoraTask::loop(MicroTasks::WakeReason reason) +{ + if (!config_lora_enabled()) + return 1000; + + CayenneLPP lpp(24); + create_rapi_cayennelpp(_evse, &lpp); + lora.publish(&lpp); + return ANNOUNCE_INTERVAL; +} + +LoraTask lora; + +#endif /* ENABLE_LORA */ diff --git a/src/lora.h b/src/lora.h new file mode 100644 index 00000000..e2aaef97 --- /dev/null +++ b/src/lora.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2023 Alexander von Gluck IV for OpenEVSE + * + * ------------------------------------------------------------------- + * + * Additional Adaptation of OpenEVSE ESP Wifi + * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor + * All adaptation GNU General Public License as below. + * + * ------------------------------------------------------------------- + * + * This file is part of Open EVSE. + * Open EVSE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * Open EVSE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with Open EVSE; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef ENABLE_LORA +#ifndef _LORA_H +#define _LORA_H + +#include +#include + +#include "evse_man.h" + +void create_rapi_cayennelpp(EvseManager* _evse, CayenneLPP* lpp); + +class LoraTask : public MicroTasks::Task { + private: + EvseManager* _evse; + + protected: + void setup(); + unsigned long loop(MicroTasks::WakeReason reason); + void publish(CayenneLPP* lpp); + void modem_reset(); + + public: + LoraTask(); + void begin(EvseManager &evse); +}; + +extern LoraTask lora; + +#endif // _LORA_H +#endif /* ENABLE_LORA */ diff --git a/src/main.cpp b/src/main.cpp index 65fc109e..345d6996 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #include "divert.h" #include "ota.h" #include "lcd.h" +#include "lora.h" #include "openevse.h" #include "root_ca.h" #include "espal.h" @@ -156,6 +157,11 @@ void setup() ocpp.begin(evse, lcd, eventLog, rfid); +#ifdef ENABLE_LORA + // initialise LoRA if supported + lora.begin(evse); +#endif + shaper.begin(evse); lcd.display(F("OpenEVSE WiFI"), 0, 0, 0, LCD_CLEAR_LINE); @@ -335,4 +341,4 @@ void handle_serial() DEBUG_PORT.printf("{\"code\":200,\"msg\":\"%s\"}\n", config_modified ? "done" : "no change"); } } -} \ No newline at end of file +}