TheSoftware
Part 4 – The other stuff
There are a few more SW modules involved in this tutorial, but I will only go into details for the setup() and loop() functions.
Other functions, like the display support, BLE UART output and my logging functions are relatively simple and you can check how they work in the source codes.
The setup()
As in all Arduino applications, setup() is called once after a fresh start or reboot of the device. Here we define out unique device ID, which will be used to identify the device in the Mesh network, initialize BLE and display (if attached) and initialize the Alora RFM1262 LoRa hardware.
Creating the node ID.
Each node in the Mesh network needs a unique identifier. The easiest way to create such an identifier is to use the MAC address of the device. The library that I use under the hood for the LoRa communication (SX126x-Arduino) has a function called BoardGetUniqueId, that I use for this purpose. The function returns an byte array of 8 bytes that is different for each device. This byte array is then used to create the node ID.
// Create node ID
uint8_t deviceMac[8];
BoardGetUniqueId(deviceMac);
deviceID += (uint32_t)deviceMac[2] << 24;
deviceID += (uint32_t)deviceMac[3] << 16;
deviceID += (uint32_t)deviceMac[4] << 8;
deviceID += (uint32_t)deviceMac[5];
myLog_n("Mesh NodeId = %08lX", deviceID);
Next is the initialization of the display and BLE UART functions
#ifdef HAS_DISPLAY
initDisplay();
#endif
// Initialize BLE
initBLE();
As you can see I use #define compiler directives to integrate or skip certain parts of the code.
Last step is the initialization of the LoRa transceiver HW and start the Mesh network handler.
Depending on the used HW the LoRa transceiver initialization is slightly different. If the target HW is an ESP32 or an Adafruit nRF52 Feather board, the LoRa library needs to know the assignment for the GPIOs that are connecting the ESP32 or nRF52 with the SX1262 transceiver. If the target HW is the ISP4520 module the initialization is much easier, because the connections are predefined in the library.
// Initialize the LoRa
#if defined(ESP32) || defined(ADAFRUIT)
// Define the HW configuration between MCU and SX126x
hwConfig.CHIP_TYPE = SX1262_CHIP; // eByte E22 module with an SX1262
hwConfig.PIN_LORA_RESET = PIN_LORA_RESET; // LORA RESET
hwConfig.PIN_LORA_NSS = PIN_LORA_NSS; // LORA SPI CS
hwConfig.PIN_LORA_SCLK = PIN_LORA_SCLK; // LORA SPI CLK
hwConfig.PIN_LORA_MISO = PIN_LORA_MISO; // LORA SPI MISO
hwConfig.PIN_LORA_DIO_1 = PIN_LORA_DIO_1; // LORA DIO_1
hwConfig.PIN_LORA_BUSY = PIN_LORA_BUSY; // LORA SPI BUSY
hwConfig.PIN_LORA_MOSI = PIN_LORA_MOSI; // LORA SPI MOSI
hwConfig.RADIO_TXEN = RADIO_TXEN; // LORA ANTENNA TX ENABLE
hwConfig.RADIO_RXEN = RADIO_RXEN; // LORA ANTENNA RX ENABLE
hwConfig.USE_DIO2_ANT_SWITCH = true; // Example uses an eByte E22 module which uses RXEN and TXEN pins as antenna control
hwConfig.USE_DIO3_TCXO = true; // Example uses an eByte E22 module which uses DIO3 to control oscillator voltage
hwConfig.USE_DIO3_ANT_SWITCH = false; // Only Insight ISP4520 module uses DIO3 as antenna control
if (lora_hardware_init(hwConfig) != 0)
{
myLog_e("Error in hardware init");
}
#else // ISP4520
if (lora_isp4520_init(SX1262) != 0)
{
myLog_e("Error in hardware init");
}
#endif
Then we tell the Mesh network handler which function should be called if LoRa data was received.
MeshEvents.DataAvailable = OnLoraData;
The Mesh handler is running as a independent task in the background, handling the LoRa communication (sending and receiving) without interaction with the loop() task. The OnLoraData() function will be called from the Mesh handler if new data is available from the LoRa Mesh network.
Last thing here is to start the Mesh handler. As the nRF52 has less available heap memory, the maximum number of nodes is limited to 30. On an ESP32, we can set this to the maximum possible number of 48 nodes.
// Initialize the LoRa Mesh
#ifdef ESP32
initMesh(&MeshEvents, 48);
#else
initMesh(&MeshEvents, 30);
#endif
The OnLoraData callback function
Whenever a LoRa data package was received, this function is called with a pointer to the data, the size of the data and some transmission parameters.
Usually you would transfer the received data into a buffer and inform the loop() function about it. Then data handling would be done inside the loop().
But to keep the tutorial simple, we just print out the received data over the Serial port.
/**
* Callback after a LoRa package was received
* @param payload
* Pointer to the received data
* @param size
* Length of the received package
* @param rssi
* Signal strength while the package was received
* @param snr
* Signal to noise ratio while the package was received
*/
void OnLoraData(uint8_t *rxPayload, uint16_t rxSize, int16_t rxRssi, int8_t rxSnr)
{
Serial.println("-------------------------------------");
Serial.println("Got");
for (int idx = 0; idx < rxSize; idx++)
{
Serial.printf("%02X ", rxPayload[idx]);
}
Serial.printf("\n\n%s\n", rxPayload);
Serial.println("-------------------------------------");
#if defined(HAS_DISPLAY) || defined(RED_ESP)
digitalWrite(LED_BUILTIN, LOW);
#else
digitalWrite(LED_BUILTIN, HIGH);
#endif
#ifdef ESP32
ledOffTick.detach();
ledOffTick.once(1, ledOff);
#else
timer.attachInterrupt(&ledOff, 1000 * 1000); // microseconds
#endif
}
The loop()
As mentioned above, the loop() would normally include some data handling tasks, but in this simple tutorial we just print every 30 seconds the current list of known nodes to the Serial port (and display if available), select a random node and send a package to this node.
void loop()
{
delay(30000);
Serial.println("---------------------------------------------");
if (xSemaphoreTake(accessNodeList, (TickType_t)1000) == pdTRUE)
{
numElements = numOfNodes();
Serial.printf("%d nodes in the map\n", numElements + 1);
Serial.printf("Node #01 id: %08X\n", deviceID);
if (bleUARTisConnected) {
char sendData[512] = {0};
int sendLen = snprintf(sendData, 512, "%d nodes in the map\n", numElements + 1);
bleUartWrite(sendData, sendLen);
sendLen = snprintf(sendData, 512, "Node #01 id: %08X\n", deviceID);
bleUartWrite(sendData, sendLen);
}
#ifdef HAS_DISPLAY
dispWriteHeader();
char line[128];
// sprintf(line, "%08X", deviceID);
sprintf(line, "%02X%02X", (uint8_t)(deviceID >> 24), (uint8_t)(deviceID >> 16));
dispWrite(line, 0, 11);
#endif
for (int idx = 0; idx < numElements; idx++)
{
getNode(idx, nodeId[idx], firstHop[idx], numHops[idx]);
}
// Select random node to send a package
getRoute(nodeId[random(0, numElements)], &routeToNode);
// Release access to nodes list
xSemaphoreGive(accessNodeList);
// Prepare data
outData.mark1 = 'L';
outData.mark2 = 'o';
outData.mark3 = 'R';
if (routeToNode.firstHop != 0)
{
outData.dest = routeToNode.firstHop;
outData.from = routeToNode.nodeId;
outData.type = 2;
Serial.printf("Queuing msg to hop to %08X over %08X\n", outData.from, outData.dest);
if (bleUARTisConnected)
{
char sendData[512] = {0};
int sendLen = snprintf(sendData, 512, "Queuing msg to hop to %08X over %08X\n", outData.from, outData.dest);
bleUartWrite(sendData, sendLen);
}
}
else
{
outData.dest = routeToNode.nodeId;
outData.from = deviceID;
outData.type = 1;
Serial.printf("Queuing msg direct to %08X\n", outData.dest);
if (bleUARTisConnected)
{
char sendData[512] = {0};
int sendLen = snprintf(sendData, 512, "Queuing msg direct to %08X\n", outData.dest);
bleUartWrite(sendData, sendLen);
}
}
int dataLen = MAP_HEADER_SIZE + sprintf((char *)outData.data, ">>%08X<<", deviceID);
// Add package to send queue
if (!addSendRequest(&outData, dataLen))
{
Serial.println("Sending package failed");
if (bleUARTisConnected)
{
char sendData[512] = {0};
int sendLen = snprintf(sendData, 512, "Sending package failed\n");
bleUartWrite(sendData, sendLen);
}
}
// Display the nodes
for (int idx = 0; idx < numElements; idx++)
{
#ifdef HAS_DISPLAY
if (firstHop[idx] == 0)
{
// sprintf(line, "%08X", nodeId[idx]);
sprintf(line, "%02X%02X", (uint8_t)(nodeId[idx] >> 24), (uint8_t)(nodeId[idx] >> 16));
}
else
{
// sprintf(line, "%08X*", nodeId[idx]);
sprintf(line, "%02X%02X*", (uint8_t)(nodeId[idx] >> 24), (uint8_t)(nodeId[idx] >> 16));
}
if (idx < 4)
{
dispWrite(line, 0, ((idx + 2) * 10) + 1);
}
else if (idx < 9)
{
dispWrite(line, 42, ((idx - 3) * 10) + 1);
}
else
{
dispWrite(line, 84, ((idx - 8) * 10) + 1);
}
#endif
if (firstHop[idx] == 0)
{
Serial.printf("Node #%02d id: %08X direct\n", idx + 2, nodeId[idx]);
if (bleUARTisConnected)
{
char sendData[512] = {0};
int sendLen = snprintf(sendData, 512, "Node #%02d id: %08LX direct\n", idx + 2, nodeId[idx]);
bleUartWrite(sendData, sendLen);
}
}
else
{
Serial.printf("Node #%02d id: %08X first hop %08X #hops %d\n", idx + 2, nodeId[idx], firstHop[idx], numHops[idx]);
if (bleUARTisConnected)
{
char sendData[512] = {0};
int sendLen = snprintf(sendData, 512, "Node #%02d id: %08X first hop %08X #hops %d\n", idx + 2, nodeId[idx], firstHop[idx], numHops[idx]);
bleUartWrite(sendData, sendLen);
}
}
}
#ifdef HAS_DISPLAY
dispUpdate();
#endif
}
else
{
Serial.println("Could not access the nodes list");
}
Serial.println("---------------------------------------------");
}
And that’s it. If you have any questions, suggestions or problems with this tutorial, leave a message here or in my Github repository.