LoRa App Example

The example app described here, aims for users who want to utilize LoRa (Long Range), a radio communication protocol used as messaging service, in their custom apps. It describes the process to send or receive text messages by using the LoRa daughter board, a dedicated hardware board which mounts on the back of primary WiPhone's PCB to activate this message communication in WiPhone. Range of communication may reaches upto few hundred Kilometers depending upon the terrain and environment in the vicinity.

After completing this Example, user will learn how to:
  • Send data ("PING" or "PONG") to another device with LoRa messaging enabled
  • Receive a data from another device through LoRa communication
  • Initialize RHSoftwareSPI and RH_RF95 software objects for WiPhone to use LoRa device daughterboard

How it works in WiPhone Interface

This example can be found in WiPhone menu like; Tools -> Development -> Software Examples -> LoRa App. Once you select that app, the main activity page will open containing a text header "LoRa Test App", an empty body, and two buttons described as below:-

  • A Send button at left footer, broadcasts a “PING” message via the LoRa App. In response, it receives back a "PONG" message from other WiPhones, having LoRa daughterboard and running LoRa app. The response will get display on the screen of sender's phone.
  • A Back button takes user back to previous screen.

image1 image2

Implementation in Firmware

Identify Major Files & Functions

  1. GUI.h
  2. GUI.cpp
  3. typedef enum ActionID : uint16_t {}
  4. GUIMenuItem menu[xx] PROGMEM = {}
  5. processEvent (EventType event)
  6. changeState(int i)
  7. redrawScreen (bool redrawAll)

Disable LoRa Messaging

WiPhone firmware has LoRa messaging activated by default. Disable LoRa messaging to run this app. You can do this by commenting out the following line in the Hardware.h file, which is available in the main WiPhone source tree.

#define LORA_MESSAGING

Note

Disabling the above flag prevents a conflict of accessing the same hardware by two different apps at the same time.

Declaring a unique name for your app in GUI.h file

Search for "typedef enum ActionID" method in GUI.h and put your app name "GUI_APP_LoRaApp" as Action ID at designated place`.

Adding App ID into Menu

Search for GUIMenuItem menu[xx] PROGMEM in GUI.h and add an array line with following arguments in GUI Menu Item Method.

  1. Index for Picrures Demo App -> 45
  2. Index of Parent Menu -> 39
  3. Menu Name -> LoRa App
  4. argument empty -> ""
  5. argument empty -> ""
  6. Enumeration ID -> GUI_APP_LoRaApp

{ 45, 39, "LoRa App", "", "", GUI_APP_LoRaApp }

Adding App Class into Header File

Insert following class code into GUI.h file.

class LoRaTestApp : public WindowedApp {
 public:
  LoRaTestApp( LCD& disp, ControlState& state, HeaderWidget* header, FooterWidget* footer);
  virtual ~LoRaTestApp();
  ActionID_t getId() { return GUI_APP_LoRaApp; };
  appEventResult processEvent(EventType event);
  void redrawScreen(bool redrawAll = false);
 protected:
  static const int EXIT_CNT = 5;
  ButtonWidget* bbKeys[2];
  bool anyKeyPressed;
  bool screenInited;

  RHSoftwareSPI* loraSPI;
  RH_RF95* rf95;

  LabelWidget* label1;
};

Instantiating the App

Before defining the App classes, search for GUI::enterApp(ActionID_t app) in GUI.cpp file and append below line in switch statement to instantiate the App object.

case GUI_APP_LoRaApp::
  // NOTE : THIS IS AN EXAMPLE LoRa APP
  runningApp = new LoRaTestApp(*screen, state, header, footer);
  break;

The designated place to add above line within the switch statement in code can be found at enterApp Method in GUI.cpp.

Define App Constructor

A constructor method that we already instantiated in step above, needs to be defined first in GUI.CPP. Constructor for this app initializes a RadioHead library, which is used to control the LoRa hardware on daughter board. Moreover, it sets up the Send and Back buttons along with the label which is used to display the data received via LoRa communication. Let’s add this constructor function for class LoRaTestApp.

LoRaTestApp::LoRaTestApp( LCD& lcd, ControlState& state, HeaderWidget* header, FooterWidget* footer)
  : WindowedApp(lcd, state, header, footer), screenInited(false) {
  header->setTitle("LoRa Test APP");
  footer->setButtons("Send", "Back");

  state.msAppTimerEventLast = millis();
  state.msAppTimerEventPeriod = 33;

  // Create all the widgets
  const uint16_t spacing = 1;
  uint16_t xOff = spacing;
  uint16_t yOff = header->height();

  loraSPI = new RHSoftwareSPI();
  rf95 = new RH_RF95(RFM95_CS, RFM95_INT, *loraSPI);

  loraSPI->setPins(HSPI_MISO, HSPI_MOSI, HSPI_SCLK);
  pinMode(RFM95_RST, OUTPUT);
  rf95->init();
  rf95->setFrequency(RF95_FREQ);
  rf95->setTxPower(23, false);

  label1 = new LabelWidget(0, (lcd.height() - fonts[AKROBAT_EXTRABOLD_22]->height()) / 2.5, lcd.width(), fonts[AKROBAT_EXTRABOLD_22]->height(),
                           "", WHITE, BLACK, fonts[AKROBAT_EXTRABOLD_22], LabelWidget::CENTER);
}

Declare and Define other Methods

This segments will show us a way of adding source code of other functions/ methods used in LoRaTestApp Example. We declare and define these methods within the firmware code to fully implement our example App and subsequently put in place in GUI.cpp file. These functions are defined outside the LoRaTestApp class and thus presented here with scope resolution operator (::) as below:

  1. LoRaTestApp::~LoRaTestApp();
  2. LoRaTestApp::processEvent(EventType event);
  3. LoRaTestApp::redrawScreen(bool redrawAll);

LoRaTestApp::~LoRaTestApp() This function defines LoRaTestApp's destructor to release memory after usage. All widgets must be registered and deleted by WiPhoneApp destructor.

     LoRaTestApp::~LoRaTestApp() {
  delete rf95;
  delete loraSPI;
}

LoRaTestApp::processEvent(EventType event); This function defines the app events that are detected when a button is pressed on WiPhone Keypad. In this method, Pressing Back button closes out the app while Pressing Send button sends out a “PING” message to a remote LoRa message listner WiPhone. Apart from recognizing the button press events, this method also receives a timer event which can be used to check for any data arriving. If the data arrived is equal to “PING”, we sends back a "PONG" message in response.

     appEventResult LoRaTestApp::processEvent(EventType event) {
  appEventResult res = DO_NOTHING;
  if (LOGIC_BUTTON_BACK(event) ) {
    return EXIT_APP;
  }

  if (LOGIC_BUTTON_OK(event)) {
    rf95->send((uint8_t*)"PING", strlen("PING"));
    rf95->waitPacketSent();
  }

  if (event == APP_TIMER_EVENT) {
    if (rf95->available()) {
        // Should be a message for us now
        uint8_t buf[RH_RF95_MAX_MESSAGE_LEN] = {0};
        uint8_t len = sizeof(buf);

        if (rf95->recv(buf, &len)) {
            label1->setText((char*)buf);
            if (strcmp((char*)buf, "PING") == 0) {
              rf95->send((uint8_t*)"PONG", strlen("PONG"));
              rf95->waitPacketSent();
            }
        }
        else {
          log_e("LoRa: Unable to receive data");
        }
      }
  }

  res |= REDRAW_SCREEN;
  return res;
}

LoRaTestApp::redrawScreen(bool redrawAll); This method is used whenever user wants to update/ clear GUI for this app. Calling this method will redraw the default main activity screen designed for the App.

void LoRaTestApp::redrawScreen(bool redrawAll) {

  if (!this->screenInited) {
    redrawAll = true;
  }
  if (redrawAll) {
    lcd.fillRect(0, header->height(), lcd.width(), lcd.height() - header->height() - footer->height(), TFT_BLACK);
  }

  ((GUIWidget *) label1)->redraw(lcd);
  screenInited = true;
}