KeHoSoftware
Created February 4, 2023 © GPL3+

SEEED Wio Terminal LoRa MQTT Gateway using Arduino IDE

Transform your Wio Terminal into a Matrix LoRa MQTT Gateway. Transmit received LoRa messages to a MQTT Broker and act as SensorNode too.

AdvancedWork in progress2 hours5
SEEED Wio Terminal LoRa MQTT Gateway using Arduino IDE

Things used in this project

Hardware components

Wio Terminal
Seeed Studio Wio Terminal
The Wio Terminal transforms into a Matrix LoRa MQTT Gateway.
×1
Grove - LoRa Radio 868MHz
Seeed Studio Grove - LoRa Radio 868MHz
We need two of them - one connected to your Wio Terminal (LoRa MQTT Gateway) and the other to your Xiao (LoRa SensorNode).
×2
Seeed Studio Grove - Temperature & Humidity Sensor (DHT11)
The Wio Terminal acts also as a MQTT SensorNode too - we use the DHT11 to measure temperature and humidity and the inbuilt sensors light and sound.
×1
Seeed Studio XIAO SAMD21 (Pre-Soldered) - Seeeduino XIAO
Seeed Studio XIAO SAMD21 (Pre-Soldered) - Seeeduino XIAO
The little Xiao acts as a LoRa SensorNode to demonstrate the use case. You can build easy more and different sensor nodes.
×1
Seeed Studio Grove - VOC and eCO2 Gas Sensor(SGP30)
This sensor measures your room air quality - VOC and eCO2.
×1
Seeed Studio Grove - Temp&Humi&Barometer Sensor (BME280)
This sensor measures your room air quality - Temperature and Humidity.
×1
Seeed Studio Grove - PIR Motion Sensor-Low-cost&Easy-to -use motion detector-beginners friendly
This sensors counts intruders. The Chainable LED will go to RED if motion detected.
×1
Seeed Studio Grove - Chainable RGB LED
This actor shows the SensorNode status - GREEN all is fine - BLUE sending data - RED intruder alert.
×1

Software apps and online services

Arduino IDE
Arduino IDE
MQTT
MQTT

Story

Read more

Custom parts and enclosures

Wio Terminal Stand

3D-Printer Wio Terminal Stand

Schematics

Wio Terminal wiring

Wio Terminal wired up with LoRa Radio and DHT11 sensor

Code

WioTerminal_05_LoRa_MQTT_Gateway_V1_12.ino

Arduino
Wio Terminal LoRa MQTT Gateway Arduino code
/****************************************************************************
**                                                                         **
** Name:        WioTerminal_05_LoRaWan_MQTT_Gateway                        **
** Author:      Achim Kern                                                 **
** Interpreter: Arduino IDE 2.0.4 MacOS                                    **
** Licence:     Freeware                                                   **
** Function:    Main Program                                               **
**                                                                         **
** Notes:       based on idea from SEEED STUDIO and LCARS SmartHome        **
**                                                                         **
** History:                                                                **
**                                                                         **
** 1.00        - 01.11.2021 - initial release                              **
**                          - LoRa working                                 **
**                          - MQTT secured server now working              **
** 1.01        - 06.05.2022 - Grove LoRa modul to the left Grove port      **
**                          - (I2C Grove)                                  **                          
**                          - secure MQTT Broker on Synology NAS           ** 
**                          - code optimized from GPS Tracker              **
** 1.02        - 10.01.2023 - config_lora.txt on SD card                   **
** 1.03        - 11.01.2023 - LoRa frequency from SD card                  **
** 1.04        - 12.01.2023 - MATRIX Graphics and ShowMessages             **
** 1.05        - 12.01.2023 - SERIALDEBUG                                  **
** 1.06        - 12.01.2023 - Wio inbuild 3 buttons - TFT screen on /off   **
** 1.07        - 13.01.2023 - TFT screen off after 60 secs                 **
** 1.08        - 13.01.2023 - DHT11 sensor - Wio Terminal SensorNode       **
** 1.09        - 14.01.2023 - Wio inbuild sound mic sensor                 **
** 1.10        - 16.01.2023 - sound mic trigger on SD card                 **
**                          - Wio inbuild light sensor                     **
** 1.11        - 17.01.2023 - screen display connected sensors             **
** 1.12        - 07.02.2023 - MQTT problem solving                         **
**                          - hackster.io version                          **
**                                                                         **                          
****************************************************************************/

/*
 * Application and Version
 */
   const char* application  = "WioTerminal_05_LoRa_MQTT_Gateway";
   const char* aktu_version = "1.12";
   
/*
 * DEBUG:
 * If the following line is uncommented, messages are being printed out to the
 * serial connection for debugging purposes. When using the Arduino Integrated
 * Development Environment (Arduino IDE), these messages are displayed in the
 * Serial Monitor selecting the proper port and a baudrate of 115200.
 *
 */
#define SERIALDEBUG
 
#ifdef SERIALDEBUG
  #define SERIALDEBUG_PRINT(...)   Serial.print(__VA_ARGS__)
  #define SERIALDEBUG_PRINTLN(...) Serial.println(__VA_ARGS__)
#else
  #define SERIALDEBUG_PRINT(...)
  #define SERIALDEBUG_PRINTLN(...)
#endif

/*
 *  RF95, WiFi and MQTT libs
 * 
 */
   // we need these libraries
   #include <wiring_private.h>
   #include "rpcWiFi.h"
   #include "PubSubClient.h"
   #include "ArduinoJson.h"
  
/*
 *  WiFi and MQTT clients
 * 
 */
   WiFiClient     mqttClient;
   PubSubClient   MQTT(mqttClient);

/*
 * WIO Terminal TFT Display and SD card
 * 
 */
   #include <SPI.h>
   #include <Seeed_FS.h>
   #include <SD/Seeed_SD.h>
   #include <SDConfigFile.h>
   
   File myFile;
   int BUFSIZE;
   const char CONFIG_FILE[] = "config_lora.txt";
   boolean readConfiguration();                        
   boolean didReadConfig;
   
   // WIO Terminal SD Card
   char* sd_ssid;
   char* sd_password;
   char* sd_mqttServer;
   int   sd_mqttPort;
   char* sd_mqttUser;
   char* sd_mqttPassword;   
   char* sd_TOPIC_SUBSCRIBE;
   char* sd_TOPIC_PUBLISH;
   int   sd_lora_freq;
   int   sd_mic_trigger;

   // Wio Terminal TFT display
   #include "Free_Fonts.h"
   #include <LGFX_TFT_eSPI.hpp>
   static TFT_eSPI tft;
   // pictures on the sd card
   String iot_picture=""; 
   // Control Pin of LCD
   #define LCD_BACKLIGHT (72Ul)
   bool tft_backlight = true;
   // sleeping counter
   int tft_counter = 0;
   // sending counter
   int MQTT_counter = 0;
   // room or sensor display
   int aktu_room = 1;
   int max_room  = 1; 

/*
 * LoRa Modul
 * Grove LoRa module can be connected on the back (Hardware Serial is required to use the Grove LoRa module)
 * Also with the help of Serial Communication Interfaces (SERCOM) you can easily connect the Grove LoRa module
 * to the left Grove port. (i2c Grove) - actual used
 *
 */
   #ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE    
     #include <RH_RF95.h>
     static Uart Serial3(&sercom3, PIN_WIRE_SCL, PIN_WIRE_SDA, SERCOM_RX_PAD_1, UART_TX_PAD_0);
     RH_RF95 <Uart> rf95(Serial3);
   #endif   

/*
 * Wio Terminal inbuild Buzzer
 * ---------------------------
 */
   // Wio Buzzer
   // sig pin of the buzzer
   #define BUZZER_PIN WIO_BUZZER
   // the number of notes 
   int length = 15;
   char notes[] = "ccggaagffeeddc ";
   int beats[] = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 4 };
   int tempo = 300; 

/*
 * Wio Terminal inbuild Sound Sensor
 * ---------------------------------
 */
   int mic_level = 0;
   int mic_trigger = 300;
   long int mic_counter = 0;
   #define MicSamples (1024*2)  

/*
 * Wio Terminal inbuild Light Sensor
 * ---------------------------------
 */
   int light;
   uint8_t light_percentage;

/*
 * Wio Terminal inbuild LED
 * ------------------------
 */
   int led = 13;

/*
 * GROVE DHT11 Sensor (blue)
 * -------------------------
 * This Temperature&Humidity sensor provides a pre-calibrated digital output. 
 * A unique capacitive sensor element measures relative humidity and the temperature
 * is measured by a negative temperature coefficient (NTC) thermistor. 
 * It has excellent reliability and long term stability. 
 * Please note that this sensor will not work for temperatures below 0 degree.
 * 
 */ 
   // Enable/disable sensor measurements if you want to
   #define ENABLE_SENSOR_DHT
   // Sensors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_SENSOR_DHT
     // we need library
     #include <Adafruit_Sensor.h>
     #include <DHT.h>
     #include <DHT_U.h>
     // signal pin of your dht11 sensor 
     #define DHT_PIN 0
     // type of sensor (dht11=blue, dht22=white)
     #define DHTTYPE DHT11
     // intialize sensor
     DHT_Unified dht(DHT_PIN, DHTTYPE);
     uint32_t delayMS;
     int   grove_dht_temp = 0;
     int   grove_dht_humi = 0;
     /*-----------------------------------------*/
     /* Function void sensor_dht()              */
     /*                                         */
     /* TASK    : read out dht sensor data      */
     /* UPDATE  : 28.08.2020                    */
     /*-----------------------------------------*/   
     void sensor_dht()
     {
       delay(delayMS);
       // Get temperature event and print its value
       sensors_event_t event;
       dht.temperature().getEvent(&event);
       if (isnan(event.temperature)) 
       {
         SERIALDEBUG_PRINTLN(F("[!] Error reading DHTxx temperature!"));
       }
       else 
       {
         grove_dht_temp = event.temperature;
       }
       // Get humidity event and print its value.
       dht.humidity().getEvent(&event);
       if (isnan(event.relative_humidity)) 
       {
         SERIALDEBUG_PRINTLN(F("[!] Error reading DHTxx humidity!"));
       }
       else 
       {
         grove_dht_humi= event.relative_humidity+18;
       }
     }    
   #endif   

/*
 *  list of functions used
 * 
 */
   /* 01 */ boolean readConfiguration();
   /* 02 */ void init_wifi(void);
   /* 03 */ void connect_wifi(void);
   /* 04 */ void verify_wifi(void);
   /* 05 */ void init_mqtt(void);
   /* 06 */ void connect_mqtt(void);
   /* 07 */ void verify_mqtt(void);
   /* 08 */ void mqtt_callback(char* topic, byte* payload, unsigned int length);
   /* 09 */ void ShowMessage(String msg, int Cursor);
   /* 10 */ void LCD();
   /* 11 */ void TransmitData();
   /* 12 */ void tft_display_room_screen(String room_name, long int room_bg);
   /* 13 */ void tft_display_status_bar(String room_name, long int room_bg);
   /* 14 */ void tft_display_sensor_temperature(int temperature);
   /* 15 */ void tft_display_sensor_humidity(int humidity);
   /* 16 */ void tft_display_sensor_noise(int noise);
   /* 17 */ void tft_display_sensor_lux(int lux);
   /* 18 */ void tft_display_wioterminal(void);
   /* 19 */ void tft_display_matrix(void);
   
   void setup();
   void loop();

/*
 * Generally, you should use "unsigned long" for variables that hold time
 * The value will quickly become too large for an int to store
 */
   // this timer is used to update tft display and lorawan data send frequence
   unsigned long previousMillis = 0;
   // send every 10 minutes
   // unsigned long interval = 600000;  
   // send every 3 minutes
   // unsigned long interval = 180000;
   // send every 30 seconds
   unsigned long interval = 30000;   
   unsigned long counter  = 0;

/*-------------------------------------------------------------------------------*/
/* (01) Function boolean readConfiguration()                                     */
/*                                                                               */
/* TASK    : read your config.txt file from the SD Card                          */
/* UPDATE  : 29.04.2022                                                          */
/*-------------------------------------------------------------------------------*/
boolean readConfiguration() 
{
  const uint8_t CONFIG_LINE_LENGTH = 127;
  SDConfigFile cfg;
  
  // Open the configuration file.
  if (!cfg.begin(CONFIG_FILE, CONFIG_LINE_LENGTH)) 
  {
    SERIALDEBUG_PRINT("[x] Failed to open configuration file: ");
    SERIALDEBUG_PRINT(CONFIG_FILE);
    SERIALDEBUG_PRINT(F("\n"));
    return false;
  }
  SERIALDEBUG_PRINT("[x] read out settings from file ");
  SERIALDEBUG_PRINT(CONFIG_FILE);
  SERIALDEBUG_PRINT("\n\n");
  
  // Read each setting from the file.
  while (cfg.readNextSetting()) 
  {
    if (cfg.nameIs("WifiSSID")) 
    {
       sd_ssid = cfg.copyValue();
       SERIALDEBUG_PRINT("[?] WifiSSID:      ");
       SERIALDEBUG_PRINTLN(sd_ssid);
    }  
    else if (cfg.nameIs("WifiPASS")) 
    {
       sd_password = cfg.copyValue();
       SERIALDEBUG_PRINT("[?] WifiPASS:      ");
       SERIALDEBUG_PRINTLN(sd_password);
    }
    else if (cfg.nameIs("MQTTSERVER")) 
    {
       sd_mqttServer = cfg.copyValue();
       SERIALDEBUG_PRINT("[?] MQTTSERVER:    ");
       SERIALDEBUG_PRINTLN(sd_mqttServer);
    }
    else if (cfg.nameIs("MQTTPORT")) 
    {
       sd_mqttPort = cfg.getIntValue();
       SERIALDEBUG_PRINT("[?] MQTTPORT:      ");
       SERIALDEBUG_PRINTLN(String(sd_mqttPort));
    }
    else if (cfg.nameIs("MQTTUSER")) 
    {
       sd_mqttUser = cfg.copyValue();
       SERIALDEBUG_PRINT("[?] MQTTUSER:      ");
       SERIALDEBUG_PRINTLN(sd_mqttUser);
    } 
    else if (cfg.nameIs("MQTTPASSWORD")) 
    {
       sd_mqttPassword = cfg.copyValue();
       SERIALDEBUG_PRINT("[?] MQTTPASSWORD:  ");
       SERIALDEBUG_PRINTLN(sd_mqttPassword);
    }
    else if (cfg.nameIs("MQTTSUBSCRIBE")) 
    {
       sd_TOPIC_SUBSCRIBE = cfg.copyValue();
       SERIALDEBUG_PRINT("[?] MQTTSUBSCRIBE: ");
       SERIALDEBUG_PRINTLN(sd_TOPIC_SUBSCRIBE);
    }
    else if (cfg.nameIs("MQTTPUBLISH")) 
    {
       sd_TOPIC_PUBLISH = cfg.copyValue();
       SERIALDEBUG_PRINT("[?] MQTTPUBLISH:   ");
       SERIALDEBUG_PRINTLN(sd_TOPIC_PUBLISH);
    }
    else if (cfg.nameIs("LORAFREQ")) 
    {
       sd_lora_freq = cfg.getIntValue();
       SERIALDEBUG_PRINT("[?] LORAFREQ:      ");
       SERIALDEBUG_PRINTLN(String(sd_lora_freq));
    }
    else if (cfg.nameIs("MICTRIGGER")) 
    {
       sd_mic_trigger = cfg.getIntValue();
       SERIALDEBUG_PRINT("[?] MICTRIGGER:    ");
       SERIALDEBUG_PRINTLN(String(sd_mic_trigger));
       mic_trigger=sd_mic_trigger;
    }                                                  
    else 
    {
      // report unrecognized names
      SERIALDEBUG_PRINT("[?] Unknown name in config: ");
      SERIALDEBUG_PRINTLN(cfg.getName());
    }
  }
  
  // clean up
  cfg.end();
  SERIALDEBUG_PRINTLN("");
  return true;
}

/*-------------------------------------------------------------------------------*/
/* (02) Function void init_wifi(void)                                            */
/*                                                                               */
/* TASK    : do not provide a wifi hotspot                                       */
/* UPDATE  : 30.08.2020                                                          */
/*-------------------------------------------------------------------------------*/
void init_wifi(void)
{
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(2000);
}

/*-------------------------------------------------------------------------------*/
/* (03) Function void connect_wifi(void)                                         */
/*                                                                               */
/* TASK    : connect to your WiFi                                                */
/* UPDATE  : 30.08.2020                                                          */
/*-------------------------------------------------------------------------------*/
void connect_wifi(void)
{
  /* are we already connect to wifi */
  if (WiFi.status() == WL_CONNECTED)
        return;
    
  SERIALDEBUG_PRINTLN("[?] Connecting to your Wi-Fi");
  ShowMessage("Connecting to your Wi-Fi", 196);
  delay(1000);   
  WiFi.begin(sd_ssid, sd_password);
 
  while (WiFi.status() != WL_CONNECTED) 
  {
    delay(100);
    // Serial.print(".");
  }
 
  SERIALDEBUG_PRINTLN("[x] Wi-Fi connected");
  SERIALDEBUG_PRINT("[x] IP: ");
  SERIALDEBUG_PRINTLN(WiFi.localIP()); 
  SERIALDEBUG_PRINTLN("");
  ShowMessage("Wi-Fi connected", 196);
  delay(1000);       
}

/*-------------------------------------------------------------------------------*/
/* (04) Function void verify_wifi(void)                                          */
/*                                                                               */
/* TASK    : check if WiFi connection is stable                                  */
/* UPDATE  : 30.08.2020                                                          */
/*-------------------------------------------------------------------------------*/
void verify_wifi(void)
{
  connect_wifi();
}

/*-------------------------------------------------------------------------------*/
/* (05) Function void init_mqtt(void)                                            */
/*                                                                               */
/* TASK    : init your MQTT broker                                               */
/* UPDATE  : 30.08.2020                                                          */
/*-------------------------------------------------------------------------------*/
void init_mqtt(void)
{
  MQTT.setServer(sd_mqttServer, sd_mqttPort);
  MQTT.setCallback(mqtt_callback);   
}

/*-------------------------------------------------------------------------------*/
/* (06) Function void connect_mqtt(void)                                         */
/*                                                                               */
/* TASK    : connect to your MQTT broker                                         */
/* UPDATE  : 30.08.2020                                                          */
/*-------------------------------------------------------------------------------*/
void connect_mqtt(void)
{ 
  while (!MQTT.connected()) 
  {
    SERIALDEBUG_PRINTLN("[?] Connecting to your MQTT Broker");
    ShowMessage("Connecting to your MQTT Broker", 196);
    delay(1000);
    char *user = sd_mqttUser;
    char *password = sd_mqttPassword;
    if (MQTT.connect("LoRaMQTTGateway", user, password)) 
    {
      SERIALDEBUG_PRINTLN("[x] MQTT Broker connected");
      SERIALDEBUG_PRINTLN("");
      ShowMessage("MQTT Broker connected", 196);
      delay(500);   
      MQTT.subscribe(sd_TOPIC_SUBSCRIBE);
    }
    else
    {
      SERIALDEBUG_PRINT("[!] MQTT failed with state ");
      SERIALDEBUG_PRINTLN(String(MQTT.state()));
      delay(2000);
    }
  }
}

/*-------------------------------------------------------------------------------*/
/* (07) Function void verify_mqtt(void)                                          */
/*                                                                               */
/* TASK    : check if MQTT connection is stable                                  */
/* UPDATE  : 30.08.2020                                                          */
/*-------------------------------------------------------------------------------*/
void verify_mqtt(void)
{
  connect_mqtt();
}

/*------------------------------------------------------------------------------------*/
/* (08) Function void mqtt_callback(char* topic, byte* payload, unsigned int length)  */
/*                                                                                    */
/* TASK    : setup all needed requirements                                            */
/* UPDATE  : 30.08.2020                                                               */
/*------------------------------------------------------------------------------------*/
void mqtt_callback(char* topic, byte* payload, unsigned int length)
{
  SERIALDEBUG_PRINT("[>] Message arrived in topic: ");
  SERIALDEBUG_PRINTLN(topic);
  SERIALDEBUG_PRINT("[>] Message:");
  for(int i = 0; i < length; i++) 
  {
    SERIALDEBUG_PRINT(String((char)payload[i]));
  }
  SERIALDEBUG_PRINTLN("");

  char p[length + 1];
  memcpy(p, payload, length);
  p[length] = NULL;
  String message(p);
    
  // Parse the command payload if json (V6)
  StaticJsonDocument<200> doc;
  #ifdef SERIALDEBUG
    deserializeJson(doc, (char*)payload);
    SERIALDEBUG_PRINT("[x] Command received:");
    serializeJson(doc, Serial);
    SERIALDEBUG_PRINTLN("");
  #endif
  if (aktu_room==1) { ShowMessage(p, 196); }
}

/*-------------------------------------------------------------------------------*/
/* (09) Function void ShowMessage(String msg, int Cursor)                        */
/*                                                                               */
/* TASK    : display a short message on the WioTerminal TFT screen               */
/* UPDATE  : 29.04.2022                                                          */
/*-------------------------------------------------------------------------------*/
void ShowMessage(String msg, int Cursor) 
{
  tft.setTextColor(TFT_GREEN,TFT_BLACK);
  tft.setCursor(0, Cursor);
  tft.print("                                                                                           ");
  tft.print("                                                                                           ");
  tft.print("                                                                                           ");
  tft.setCursor((320 - tft.textWidth(msg))/2, Cursor);
  tft.print("                                                                                                          ");
  tft.print(msg);
}

/*-------------------------------------------------------------------------------*/
/* (10) Function void LCD()                                                      */
/*                                                                               */
/* TASK    : switch LCD on or off                                                */
/* UPDATE  : 14.10.2022                                                          */
/*-------------------------------------------------------------------------------*/
void LCD()
{
  tft_backlight = !tft_backlight;
  // Turning off the LCD backlight
  if (tft_backlight == false) 
  { digitalWrite(LCD_BACKLIGHT, LOW); 
    tft.setBrightness(0); 
    SERIALDEBUG_PRINT("TFT display OFF\n\r"); 
  }
  // Turning on the LCD backlight
  if (tft_backlight == true)  
  { 
    digitalWrite(LCD_BACKLIGHT, HIGH); 
    tft.setBrightness(255); 
    SERIALDEBUG_PRINT("TFT display ON\n\r"); }  
}

/*-------------------------------------------------------------------------------*/
/* (11) Function void TransmitData()                                             */
/*                                                                               */
/* TASK    : transmit your sensor data to TTN V3 console                         */
/* UPDATE  : 14.10.2022                                                          */
/*-------------------------------------------------------------------------------*/
void TransmitData()
{
  // tft.fillScreen(TFT_BLACK);
  SERIALDEBUG_PRINTLN(F("[x] load and display picture - iot.jpg"));
  tft.drawJpgFile(SD, "iot.jpg");
  delay(1000); 

  // read out all available sensors
  SERIALDEBUG_PRINTLN(F(""));
  SERIALDEBUG_PRINTLN(F("[x] read out all available sensors"));
  
  // Get the Wio Terminal inbuild mic value 
  SERIALDEBUG_PRINT(F("[?] WIO MIC inbuild >>> NoiseCounter:"));  
  SERIALDEBUG_PRINTLN(String(mic_level));
  
  // Get the Wio Terminal inbuild light value 
  light = analogRead(WIO_LIGHT);
  SERIALDEBUG_PRINT(F("[?] WIO LIGHT inbuild >>> AnalogRead:"));  
  SERIALDEBUG_PRINT(String(light));
  // Light (Illuminance) level/percentage in greenhouse
  light_percentage = map(light, 0, 1024, 0, 100);
  SERIALDEBUG_PRINT(F("  Percentage:"));
  SERIALDEBUG_PRINT(light_percentage);
  SERIALDEBUG_PRINT(F("%\n"));

  // create json msg
  char textA[150] = "{\"node\": \"LoRa_MQTT_Gateway\"";
  // sensor data
  char array_sensor[1];	

  #ifdef ENABLE_SENSOR_DHT
     // read the dht sensor */
     sensor_dht();
     SERIALDEBUG_PRINT(F("[?] GROVE DHT >>> "));
     SERIALDEBUG_PRINT(F("T:")); SERIALDEBUG_PRINT(grove_dht_temp);
     SERIALDEBUG_PRINT(F("  H:")); SERIALDEBUG_PRINTLN(grove_dht_humi);
     // sensor humidity
     sprintf(array_sensor, "%d", grove_dht_humi);
     strcat(textA,",\"H\":");
     strcat(textA,array_sensor);
     // sensor temperature
     sprintf(array_sensor, "%d", grove_dht_temp);
     strcat(textA,",\"T\":");
     strcat(textA,array_sensor);
  #endif 

  // sensor wio inbuild sound mic
  sprintf(array_sensor, "%d", mic_level);
  strcat(textA,",\"N\":");
  strcat(textA,array_sensor);

  // sensor wio inbuild light
  sprintf(array_sensor, "%d", light_percentage);
  strcat(textA,",\"L\":");
  strcat(textA,array_sensor);  

  // json end
  strcat(textA,"}");
  // show and publish sensor msg
  SERIALDEBUG_PRINT("[>] Message:");  
  SERIALDEBUG_PRINTLN((char*)textA);
  ShowMessage(textA,196);
  MQTT.publish(sd_TOPIC_PUBLISH,textA);       
  SERIALDEBUG_PRINTLN(F("[x] Uplink to your MQTT broker done\n"));
  mic_level=0; 

  aktu_room=1;
  tft_display_matrix();      
}

/*------------------------------------------------------------------------------------*/
/* (12) Function void tft_display_room_screen(String room_name, long int room_bg)     */
/*                                                                                    */
/* TASK    : show tft display room screen                                             */
/* UPDATE  : 22.09.2020                                                               */
/*           08.01.2021 - room_name pos -22                                           */
/*------------------------------------------------------------------------------------*/
void tft_display_room_screen(String room_name, long int room_bg) 
{
  tft.fillScreen(TFT_WHITE);
  tft.fillRect(0,0,320,50,room_bg);
  tft.setFreeFont(FMB18);
  tft.setTextColor(TFT_WHITE);
  tft.setCursor((320 - tft.textWidth(room_name)) / 2, 32-22);
  tft.print(room_name);
  // drawing verticle line
  tft.drawFastVLine(150,50,190,TFT_DARKGREEN);
  // drawing horizontal line
  tft.drawFastHLine(0,140,320,TFT_DARKGREEN);  
}

/*-------------------------------------------------------------------------------------*/
/* (13) Function void tft_display_status_bar(String room_name, long int room_bg)       */
/*                                                                                     */
/* TASK    : show tft display status bar                                               */
/* UPDATE  : 22.09.2020                                                                */
/*           08.01.2021 - room_name pos -22                                            */
/*-------------------------------------------------------------------------------------*/
void tft_display_status_bar(String room_name, long int room_bg) 
{
  tft.fillRect(0,0,320,50,room_bg);
  tft.setFreeFont(FMB18);
  tft.setTextColor(TFT_WHITE);
  tft.setCursor((320 - tft.textWidth(room_name)) / 2, 32-22);
  tft.print(room_name); 
}

/*-------------------------------------------------------------------------------*/
/* (14) Function void tft_display_sensor_temperature(int temperature)            */
/*                                                                               */
/* TASK    : show tft display sensor temperature                                 */
/* UPDATE  : 22.09.2020                                                          */
/*-------------------------------------------------------------------------------*/
void tft_display_sensor_temperature(int temperature) 
{
  // setting the temperature
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB9);
  tft.drawString("Temperature",15,65);
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB18);
  tft.drawNumber(temperature,50,95);
  //tft.drawNumber(21,50,95);
  tft.setFreeFont(FMB12);
  tft.drawString("C",100,95); 
}

/*-------------------------------------------------------------------------------*/
/* (15) Function void tft_display_sensor_humidity(int humidity)                  */
/*                                                                               */
/* TASK    : show tft display sensor humidity                                    */
/* UPDATE  : 22.09.2020                                                          */
/*-------------------------------------------------------------------------------*/
void tft_display_sensor_humidity(int humidity) 
{
  // setting the humidity
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB9);
  tft.drawString("Humidity",30,160);
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB18);
  tft.drawNumber(humidity,50,190);
  tft.setFreeFont(FMB12);
  tft.drawString("%",100,190);
}

/*-------------------------------------------------------------------------------*/
/* (16) Function void tft_display_sensor_noise(int noise)                        */
/*                                                                               */
/* TASK    : show tft display sensor noise                                       */
/* UPDATE  : 22.09.2020                                                          */
/*-------------------------------------------------------------------------------*/
void tft_display_sensor_noise(int noise) 
{
  // setting the voc
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB9);
  tft.drawString("Sounds",200,65);
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB18);
  //tft.drawNumber(noise,160,95);
  tft.drawNumber(noise,200,95);
  //tft.setFreeFont(FMB12);
  //tft.drawString("sum",250,95);
}

/*-------------------------------------------------------------------------------*/
/* (17) Function void tft_display_sensor_lux(int lux)                            */
/*                                                                               */
/* TASK    : show tft display sensor lux                                         */
/* UPDATE  : 22.09.2020                                                          */
/*-------------------------------------------------------------------------------*/
void tft_display_sensor_lux(int lux) 
{ 
  // setting the luminance lux
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB9);
  tft.drawString("Luminance",185,160);
  tft.setTextColor(TFT_BLACK);
  tft.setFreeFont(FMB18);
  tft.drawNumber(lux,200,190);
  tft.setFreeFont(FMB12);
  tft.drawString("%",250,190);
}

/*-------------------------------------------------------------------------------*/
/* (18) Function void tft_display_wioterminal(void)                              */
/*                                                                               */
/* TASK    : show tft display wioterminal (own connected sensors)                */
/* UPDATE  : 30.11.2020                                                          */
/*-------------------------------------------------------------------------------*/
void tft_display_wioterminal(void) 
{
  // display wioterminal screen
  tft_display_room_screen("WioTerminal 5", TFT_DARKGREEN); 
  // display temperature sensor
  #ifdef ENABLE_SENSOR_DHT
     // read the dht sensor
     sensor_dht();
     tft_display_sensor_temperature(grove_dht_temp);
     // display humidity sensor
     tft_display_sensor_humidity(grove_dht_humi);
  #endif    
  // display noise sensor
  tft_display_sensor_noise(mic_level); 
  // display light sensor
  light = analogRead(WIO_LIGHT);
  // Light (Illuminance) level/percentage in greenhouse
  light_percentage = map(light, 0, 1024, 0, 100);
  tft_display_sensor_lux(light_percentage); 
}

/*-------------------------------------------------------------------------------*/
/* (19) Function void tft_display_matrix(void)                                   */
/*                                                                               */
/* TASK    : show tft display matrix (lora and mqtt connected sensors)           */
/* UPDATE  : 30.11.2020                                                          */
/*-------------------------------------------------------------------------------*/
void tft_display_matrix(void) 
{
  // display matrix screen
  tft.fillScreen(TFT_BLACK);
  SERIALDEBUG_PRINTLN(F("[x] load and display picture - matrix.jpg"));
  tft.drawJpgFile(SD, "matrix.jpg");
  tft.setTextColor(TFT_GREEN);
  tft.setFreeFont(FMB12);
  tft.setCursor((320 - tft.textWidth("LoRa MQTT GATEWAY"))/2, 3);
  tft.print("LoRa MQTT GATEWAY");
  tft.setTextFont(0);
  ShowMessage("Waiting until receiving LoRa or MQTT packet...", 196);
  delay(1000);
  SERIALDEBUG_PRINTLN("[>] Waiting until receiving LoRa or MQTT packet");
  SERIALDEBUG_PRINTLN("");
}

/*-------------------------------------------------------------------------------*/
/* Functions void SERCOM3_x_Handler()                                            */
/*                                                                               */
/* TASK    : Serial COM Handler                                                  */
/* UPDATE  : 29.04.2022                                                          */
/*-------------------------------------------------------------------------------*/

void SERCOM3_0_Handler()
{
  Serial3.IrqHandler();
}

void SERCOM3_1_Handler()
{
  Serial3.IrqHandler();
}

void SERCOM3_2_Handler()
{
  Serial3.IrqHandler();
}

void SERCOM3_3_Handler()
{
  Serial3.IrqHandler();
}

/*-------------------------------------------------------------------------------*/
/* Function void setup()                                                         */
/*                                                                               */
/* TASK    : setup all needed requirements                                       */
/* UPDATE  : 28.08.2020                                                          */
/*-------------------------------------------------------------------------------*/
void setup() 
{  
  // Serial baud rate
  #ifdef SERIALDEBUG
    Serial.begin(9600);
  #endif  

  // boot application
  delay(3000);
  SERIALDEBUG_PRINTLN(F(" "));
  SERIALDEBUG_PRINTLN(F(" "));
  SERIALDEBUG_PRINTLN(F("Starting..."));
  SERIALDEBUG_PRINT(F(application)); SERIALDEBUG_PRINT(F(" Version ")); SERIALDEBUG_PRINTLN(F(aktu_version));
  SERIALDEBUG_PRINTLN(F("connected via LORA WIFI WLAN MQTT"));
  SERIALDEBUG_PRINTLN(F(" "));  

  // Display Setup
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  
  //Initialise SD card
  if (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI)) 
  {
    SERIALDEBUG_PRINTLN("[!] SD Card init failed!");
    ShowMessage("SD Card init FAILED", 196);
    while (1);
  }
  SERIALDEBUG_PRINTLN("[x] SD Card init OK");
  ShowMessage("SD Card init OK", 196);
  delay(1000);

  // read config file from sd card
  SERIALDEBUG_PRINT(F("[?] Read configuration file from SD\n"));
  ShowMessage("Read configuration file from SD", 196);
  delay(1000);
  didReadConfig = readConfiguration();
  if (!didReadConfig) 
  {
    SERIALDEBUG_PRINT(F("[!] Read configuration file FAILED!\n"));
    while (1);
  }
  else
  {
    SERIALDEBUG_PRINT(F("[x] Configuration file loaded\n\n"));
    ShowMessage("Configuration file loaded", 196);
  }
  delay(1000);

  // display IoT picture
  SERIALDEBUG_PRINTLN(F("[x] load and display picture - iot.jpg"));
  tft.drawJpgFile(SD, "iot.jpg");
  delay(2000);

  tft.fillScreen(TFT_BLACK);
  SERIALDEBUG_PRINTLN(F("[x] load and display picture - matrix.jpg"));
  tft.drawJpgFile(SD, "matrix.jpg");
  tft.setTextColor(TFT_GREEN);
  tft.setFreeFont(FMB12);
  tft.setCursor((320 - tft.textWidth("LoRa MQTT GATEWAY"))/2, 3);
  tft.print("LoRa MQTT GATEWAY");
  tft.setTextColor(TFT_YELLOW);
  tft.setTextFont(0);
  ShowMessage("CREATING THE MATRIX", 196);
  delay(1000); 

  // Wio Terminal inbuild sensors and actors
  pinMode(WIO_LIGHT,INPUT);
  pinMode(BUZZER_PIN,OUTPUT);
  pinMode(WIO_MIC,INPUT);

  // Wio Terminal Joystick
  pinMode(WIO_5S_PRESS,INPUT_PULLUP);
  pinMode(WIO_5S_UP,INPUT_PULLUP);
  pinMode(WIO_5S_DOWN,INPUT_PULLUP);
  pinMode(WIO_5S_LEFT,INPUT_PULLUP);
  pinMode(WIO_5S_RIGHT,INPUT_PULLUP);

  // 3 Wio Terminal buttons
  pinMode(WIO_KEY_A, INPUT_PULLUP);
  pinMode(WIO_KEY_B, INPUT_PULLUP);
  pinMode(WIO_KEY_C, INPUT_PULLUP);
  SERIALDEBUG_PRINTLN("[x] initialize inbuild sensors, joystick, buttons and buzzer");

  // LoRa Serial
  pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT);
  pinPeripheral(PIN_WIRE_SDA, PIO_SERCOM_ALT);
  SERIALDEBUG_PRINTLN("[x] initialize pinPeripheral SCL and SCA");
  SERIALDEBUG_PRINTLN("");
  delay(100); 

  #ifdef ENABLE_SENSOR_DHT
     // start the dht sensor
     dht.begin();
     SERIALDEBUG_PRINT(F("[x] GROVE DHT Sensor connected\n"));
     // read the dht sensor */
     sensor_dht();
     SERIALDEBUG_PRINT(F("[?] GROVE DHT --> "));
     SERIALDEBUG_PRINT(F("T:")); SERIALDEBUG_PRINT(grove_dht_temp);
     SERIALDEBUG_PRINT(F("  H:")); SERIALDEBUG_PRINTLN(grove_dht_humi);
     SERIALDEBUG_PRINTLN("");
  #endif  

  // connect to WiFi
  SERIALDEBUG_PRINT(F("[?] RPC-System-Firmware-Version: "));
  SERIALDEBUG_PRINTLN(F(rpc_system_version())); 
  init_wifi();
  connect_wifi();

  // connect to MQTT Broker secured
  init_mqtt();
  connect_mqtt();
  delay(2000);

  SERIALDEBUG_PRINTLN("[?] LoRa RF95 server init");
  ShowMessage("LoRa RF95 server init", 196);

  pinMode(led, OUTPUT);

  if (!rf95.init()) 
  {
    SERIALDEBUG_PRINTLN("[!] LoRa R95 server init failed");
    while (1);
  }
  // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on

  // The default transmitter power is 13dBm, using PA_BOOST.
  // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
  // you can set transmitter powers from 5 to 23 dBm:
  //rf95.setTxPower(13, false);
 
  // rf95.setFrequency(868.0);  
  rf95.setFrequency(sd_lora_freq);  

  ShowMessage("Waiting until receiving LoRa or MQTT packet...", 196);
  delay(1000);
  SERIALDEBUG_PRINTLN("[>] Waiting until receiving LoRa or MQTT packet");
  SERIALDEBUG_PRINTLN("");    
}

/*-------------------------------------------------------------------------------*/
/* Function void loop()                                                          */
/*                                                                               */
/* TASK    : this runs forever                                                   */
/* UPDATE  : 28.08.2020                                                          */
/*           08.01.2021 - backlight on/off no only works via brightness change   */
/*-------------------------------------------------------------------------------*/
void loop() 
{
  // check if all connections are stable
  verify_wifi();
  verify_mqtt();
  MQTT.loop();

  //first we check noise level on the WIO inbuild MIC
  long signalAvg = 0, signalMax = 0, signalMin = 1024, t0 = millis();
  for (int i = 0; i < MicSamples; i++)
  {
    int k = analogRead(WIO_MIC);
    signalMin = min(signalMin, k);
    signalMax = max(signalMax, k);
    signalAvg += k;
  }
  signalAvg /= MicSamples;

  // if ((signalMax - signalMin) > 75)  
  if ((signalMax - signalMin) > mic_trigger)    
  {  
    SERIALDEBUG_PRINTLN("[x] Noise detected"); 
    SERIALDEBUG_PRINTLN("[?] Span: " + String(signalMax - signalMin));
    mic_level=mic_level+1;
    SERIALDEBUG_PRINTLN("[x] noise-counter: " + String(mic_level));
...

This file has been truncated, please download it to see its full contents.

Free_Fonts.h

Arduino
Wio Terminal Free_Fonts Arduino Code
// Attach this header file to your sketch to use the GFX Free Fonts. You can write
// sketches without it, but it makes referencing them easier.

// This calls up ALL the fonts but they only get loaded if you actually
// use them in your sketch.
//
// No changes are needed to this header file unless new fonts are added to the
// library "Fonts/GFXFF" folder.
//
// To save a lot of typing long names, each font can easily be referenced in the
// sketch in three ways, either with:
//
//    1. Font file name with the & in front such as &FreeSansBoldOblique24pt7b
//       an example being:
//
//       tft.setFreeFont(&FreeSansBoldOblique24pt7b);
//
//    2. FF# where # is a number determined by looking at the list below
//       an example being:
//
//       tft.setFreeFont(FF32);
//
//    3. An abbreviation of the file name. Look at the list below to see
//       the abbreviations used, for example:
//
//       tft.setFreeFont(FSSBO24)
//
//       Where the letters mean:
//       F = Free font
//       M = Mono
//      SS = Sans Serif (double S to distinguish is form serif fonts)
//       S = Serif
//       B = Bold
//       O = Oblique (letter O not zero)
//       I = Italic
//       # =  point size, either 9, 12, 18 or 24
//
//  Setting the font to NULL will select the GLCD font:
//
//      tft.setFreeFont(NULL); // Set font to GLCD

#define LOAD_GFXFF

#ifdef LOAD_GFXFF // Only include the fonts if LOAD_GFXFF is defined in User_Setup.h

    // Use these when printing or drawing text in GLCD and high rendering speed fonts
    #define GFXFF 1
    #define GLCD  0
    #define FONT2 2
    #define FONT4 4
    #define FONT6 6
    #define FONT7 7
    #define FONT8 8

    // Use the following when calling setFont()
    //
    // Reserved for GLCD font  // FF0
    //

    #define TT1 &TomThumb

    #define FM9 &FreeMono9pt7b
    #define FM12 &FreeMono12pt7b
    #define FM18 &FreeMono18pt7b
    #define FM24 &FreeMono24pt7b

    #define FMB9 &FreeMonoBold9pt7b
    #define FMB12 &FreeMonoBold12pt7b
    #define FMB18 &FreeMonoBold18pt7b
    #define FMB24 &FreeMonoBold24pt7b

    #define FMO9 &FreeMonoOblique9pt7b
    #define FMO12 &FreeMonoOblique12pt7b
    #define FMO18 &FreeMonoOblique18pt7b
    #define FMO24 &FreeMonoOblique24pt7b

    #define FMBO9 &FreeMonoBoldOblique9pt7b
    #define FMBO12 &FreeMonoBoldOblique12pt7b
    #define FMBO18 &FreeMonoBoldOblique18pt7b
    #define FMBO24 &FreeMonoBoldOblique24pt7b

    #define FSS9 &FreeSans9pt7b
    #define FSS12 &FreeSans12pt7b
    #define FSS18 &FreeSans18pt7b
    #define FSS24 &FreeSans24pt7b

    #define FSSB9 &FreeSansBold9pt7b
    #define FSSB12 &FreeSansBold12pt7b
    #define FSSB18 &FreeSansBold18pt7b
    #define FSSB24 &FreeSansBold24pt7b

    #define FSSO9 &FreeSansOblique9pt7b
    #define FSSO12 &FreeSansOblique12pt7b
    #define FSSO18 &FreeSansOblique18pt7b
    #define FSSO24 &FreeSansOblique24pt7b

    #define FSSBO9 &FreeSansBoldOblique9pt7b
    #define FSSBO12 &FreeSansBoldOblique12pt7b
    #define FSSBO18 &FreeSansBoldOblique18pt7b
    #define FSSBO24 &FreeSansBoldOblique24pt7b

    #define FS9 &FreeSerif9pt7b
    #define FS12 &FreeSerif12pt7b
    #define FS18 &FreeSerif18pt7b
    #define FS24 &FreeSerif24pt7b

    #define FSI9 &FreeSerifItalic9pt7b
    #define FSI12 &FreeSerifItalic12pt7b
    #define FSI19 &FreeSerifItalic18pt7b
    #define FSI24 &FreeSerifItalic24pt7b

    #define FSB9 &FreeSerifBold9pt7b
    #define FSB12 &FreeSerifBold12pt7b
    #define FSB18 &FreeSerifBold18pt7b
    #define FSB24 &FreeSerifBold24pt7b

    #define FSBI9 &FreeSerifBoldItalic9pt7b
    #define FSBI12 &FreeSerifBoldItalic12pt7b
    #define FSBI18 &FreeSerifBoldItalic18pt7b
    #define FSBI24 &FreeSerifBoldItalic24pt7b

    #define FF0 NULL //ff0 reserved for GLCD
    #define FF1 &FreeMono9pt7b
    #define FF2 &FreeMono12pt7b
    #define FF3 &FreeMono18pt7b
    #define FF4 &FreeMono24pt7b

    #define FF5 &FreeMonoBold9pt7b
    #define FF6 &FreeMonoBold12pt7b
    #define FF7 &FreeMonoBold18pt7b
    #define FF8 &FreeMonoBold24pt7b

    #define FF9 &FreeMonoOblique9pt7b
    #define FF10 &FreeMonoOblique12pt7b
    #define FF11 &FreeMonoOblique18pt7b
    #define FF12 &FreeMonoOblique24pt7b

    #define FF13 &FreeMonoBoldOblique9pt7b
    #define FF14 &FreeMonoBoldOblique12pt7b
    #define FF15 &FreeMonoBoldOblique18pt7b
    #define FF16 &FreeMonoBoldOblique24pt7b

    #define FF17 &FreeSans9pt7b
    #define FF18 &FreeSans12pt7b
    #define FF19 &FreeSans18pt7b
    #define FF20 &FreeSans24pt7b

    #define FF21 &FreeSansBold9pt7b
    #define FF22 &FreeSansBold12pt7b
    #define FF23 &FreeSansBold18pt7b
    #define FF24 &FreeSansBold24pt7b

    #define FF25 &FreeSansOblique9pt7b
    #define FF26 &FreeSansOblique12pt7b
    #define FF27 &FreeSansOblique18pt7b
    #define FF28 &FreeSansOblique24pt7b

    #define FF29 &FreeSansBoldOblique9pt7b
    #define FF30 &FreeSansBoldOblique12pt7b
    #define FF31 &FreeSansBoldOblique18pt7b
    #define FF32 &FreeSansBoldOblique24pt7b

    #define FF33 &FreeSerif9pt7b
    #define FF34 &FreeSerif12pt7b
    #define FF35 &FreeSerif18pt7b
    #define FF36 &FreeSerif24pt7b

    #define FF37 &FreeSerifItalic9pt7b
    #define FF38 &FreeSerifItalic12pt7b
    #define FF39 &FreeSerifItalic18pt7b
    #define FF40 &FreeSerifItalic24pt7b

    #define FF41 &FreeSerifBold9pt7b
    #define FF42 &FreeSerifBold12pt7b
    #define FF43 &FreeSerifBold18pt7b
    #define FF44 &FreeSerifBold24pt7b

    #define FF45 &FreeSerifBoldItalic9pt7b
    #define FF46 &FreeSerifBoldItalic12pt7b
    #define FF47 &FreeSerifBoldItalic18pt7b
    #define FF48 &FreeSerifBoldItalic24pt7b

    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    // Now we define "s"tring versions for easy printing of the font name so:
    //   tft.println(sFF5);
    // will print
    //   Mono bold 9
    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    #define sFF0 "GLCD"
    #define sTT1 "Tom Thumb"
    #define sFF1 "Mono 9"
    #define sFF2 "Mono 12"
    #define sFF3 "Mono 18"
    #define sFF4 "Mono 24"

    #define sFF5 "Mono bold 9"
    #define sFF6 "Mono bold 12"
    #define sFF7 "Mono bold 18"
    #define sFF8 "Mono bold 24"

    #define sFF9 "Mono oblique 9"
    #define sFF10 "Mono oblique 12"
    #define sFF11 "Mono oblique 18"
    #define sFF12 "Mono oblique 24"

    #define sFF13 "Mono bold oblique 9"
    #define sFF14 "Mono bold oblique 12"
    #define sFF15 "Mono bold oblique 18"
    #define sFF16 "Mono bold oblique 24" // Full text line is too big for 480 pixel wide screen

    #define sFF17 "Sans 9"
    #define sFF18 "Sans 12"
    #define sFF19 "Sans 18"
    #define sFF20 "Sans 24"

    #define sFF21 "Sans bold 9"
    #define sFF22 "Sans bold 12"
    #define sFF23 "Sans bold 18"
    #define sFF24 "Sans bold 24"

    #define sFF25 "Sans oblique 9"
    #define sFF26 "Sans oblique 12"
    #define sFF27 "Sans oblique 18"
    #define sFF28 "Sans oblique 24"

    #define sFF29 "Sans bold oblique 9"
    #define sFF30 "Sans bold oblique 12"
    #define sFF31 "Sans bold oblique 18"
    #define sFF32 "Sans bold oblique 24"

    #define sFF33 "Serif 9"
    #define sFF34 "Serif 12"
    #define sFF35 "Serif 18"
    #define sFF36 "Serif 24"

    #define sFF37 "Serif italic 9"
    #define sFF38 "Serif italic 12"
    #define sFF39 "Serif italic 18"
    #define sFF40 "Serif italic 24"

    #define sFF41 "Serif bold 9"
    #define sFF42 "Serif bold 12"
    #define sFF43 "Serif bold 18"
    #define sFF44 "Serif bold 24"

    #define sFF45 "Serif bold italic 9"
    #define sFF46 "Serif bold italic 12"
    #define sFF47 "Serif bold italic 18"
    #define sFF48 "Serif bold italic 24"

#else // LOAD_GFXFF not defined so setup defaults to prevent error messages

    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    // Free fonts are not loaded in User_Setup.h so we must define all as font 1
    // to prevent compile error messages
    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    #define GFXFF 1
    #define GLCD  1
    #define FONT2 2
    #define FONT4 4
    #define FONT6 6
    #define FONT7 7
    #define FONT8 8

    #define FF0 1
    #define FF1 1
    #define FF2 1
    #define FF3 1
    #define FF4 1
    #define FF5 1
    #define FF6 1
    #define FF7 1
    #define FF8 1
    #define FF9 1
    #define FF10 1
    #define FF11 1
    #define FF12 1
    #define FF13 1
    #define FF14 1
    #define FF15 1
    #define FF16 1
    #define FF17 1
    #define FF18 1
    #define FF19 1
    #define FF20 1
    #define FF21 1
    #define FF22 1
    #define FF23 1
    #define FF24 1
    #define FF25 1
    #define FF26 1
    #define FF27 1
    #define FF28 1
    #define FF29 1
    #define FF30 1
    #define FF31 1
    #define FF32 1
    #define FF33 1
    #define FF34 1
    #define FF35 1
    #define FF36 1
    #define FF37 1
    #define FF38 1
    #define FF39 1
    #define FF40 1
    #define FF41 1
    #define FF42 1
    #define FF43 1
    #define FF44 1
    #define FF45 1
    #define FF46 1
    #define FF47 1
    #define FF48 1

    #define FM9  1
    #define FM12 1
    #define FM18 1
    #define FM24 1

    #define FMB9  1
    #define FMB12 1
    #define FMB18 1
    #define FMB24 1

    #define FMO9  1
    #define FMO12 1
    #define FMO18 1
    #define FMO24 1

    #define FMBO9  1
    #define FMBO12 1
    #define FMBO18 1
    #define FMBO24 1

    #define FSS9  1
    #define FSS12 1
    #define FSS18 1
    #define FSS24 1

    #define FSSB9  1
    #define FSSB12 1
    #define FSSB18 1
    #define FSSB24 1

    #define FSSO9  1
    #define FSSO12 1
    #define FSSO18 1
    #define FSSO24 1

    #define FSSBO9  1
    #define FSSBO12 1
    #define FSSBO18 1
    #define FSSBO24 1

    #define FS9  1
    #define FS12 1
    #define FS18 1
    #define FS24 1

    #define FSI9  1
    #define FSI12 1
    #define FSI19 1
    #define FSI24 1

    #define FSB9  1
    #define FSB12 1
    #define FSB18 1
    #define FSB24 1

    #define FSBI9  1
    #define FSBI12 1
    #define FSBI18 1
    #define FSBI24 1

#endif // LOAD_GFXFF

LoRa_MQTT_Gateway.UF2

Arduino
Wio Terminal UF2 file
No preview (download only).

Xiao_01_LoRa_Node_V1_05.ino

Arduino
XIAO LoRa Node
/****************************************************************************
**                                                                         **
** Name:        Xiao_01_LoRa_Node                                          **
** Author:      Achim Kern                                                 **
** Interpreter: Arduino IDE 2.0.4 - MacOS                                  **
** Licence:     Freeware                                                   **
** Function:    Main Program                                               **
**                                                                         **
** Notes:       based on idea from SEEED STUDIO and LCARS SmartHome        **
**                                                                         **
** History:                                                                **
**                                                                         **
** 1.00        - 15.12.2022 - initial release                              **
**                          - GROVE LoRa R95 implemented                   **
**                          - code clearing                                **
**                          - BME280 sensor included                       **
** 1.01        - 19.12.2022 - JSON msg                                     **                          
** 1.02        - 21.12.2022 - SGP30 Sensor implemented                     **  
** 1.03        - 01.02.2023 - Node >> WioTerminal LoRa MQTT Gateway        **
** 1.04        - 03.02.2023 - sensor libraries changed                     **
**                          - function SendData() included                 **
** 1.05        - 04.02.2023 - PIR sensor included                          **
**                          - chainable LED                                **
**                                                                         **
****************************************************************************/

/*
 * Application and Version
 */
   const char* application  = "Xiao_LoRa_Node";
   const char* aktu_version = "1.04";

/*
 * DEBUG:
 * If the following line is uncommented, messages are being printed out to the
 * serial connection for debugging purposes. When using the Arduino Integrated
 * Development Environment (Arduino IDE), these messages are displayed in the
 * Serial Monitor selecting the proper port and a baudrate of 115200.
 *
 */
#define SERIALDEBUG
 
#ifdef SERIALDEBUG
  #define SERIALDEBUG_PRINT(...)   Serial.print(__VA_ARGS__)
  #define SERIALDEBUG_PRINTLN(...) Serial.println(__VA_ARGS__)
#else
  #define SERIALDEBUG_PRINT(...)
  #define SERIALDEBUG_PRINTLN(...)
#endif

/*
 * LoRa Radio
 * ----------------
 * We have integrated the grove connector to most boards produced by Seeed to make them become a system. 
 * This time, we combined Grove with LoRa to provide an ultra-long-range wireless module for you.
 * The main functional module in Grove - LoRa Radio 433MHz is RFM98, 
 * which is a transceiver features the LoRa long range modem that provides ultra-long range spread spectrum communication 
 * and high interference immunity whilst mini-missing current consumption. 
 * The heart of Grove - LoRa Radio 433MHz is ATmega168, a widely used chip with very high-performance and low power consumption, 
 * especially suitable for this grove module.
 * There we already integrated a simple wire antenna to receive signal, if the signal is too weak to receive, dont worry, 
 * the MHF connector next to the antenna is for adding a second antenna which has MHF interface to gain more signal.
 * This is the 433MHz version, which can be used for 433MHz communication. 
 * You can also find the version for 868MHz at Grove - LoRa Radio 868MHz.
 * https://wiki.seeedstudio.com/Grove_LoRa_Radio/
 *
 *
 */
   // Enable/disable actor if you want to  
   #define ENABLE_ACTOR_RF95_GROVE_LORAWAN
   // Actors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_ACTOR_RF95_GROVE_LORAWAN   
     // rf95_client.pde
     // -*- mode: C++ -*-
     // Example sketch showing how to create a simple messageing client
     // with the RH_RF95 class. RH_RF95 class does not provide for addressing or
     // reliability, so you should only use RH_RF95 if you do not need the higher
     // level messaging abilities.
     // It is designed to work with the Dragino LG01 LoRa-MQTT Server Gateway
     // system libraries we need //
     // #include <SoftwareSerial.h>
     #include <RH_RF95.h>   
     #ifdef __AVR__
       #include <SoftwareSerial.h>
       SoftwareSerial SSerial(7, 6); // RX, TX
       #define COMSerial SSerial
       #define ShowSerial Serial
       // Singleton instance of the radio driver on D5 connected
       RH_RF95<SoftwareSerial> rf95(COMSerial);
     #endif
     #ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE
       #define COMSerial Serial1
       #define ShowSerial SerialUSB
       RH_RF95<Uart> rf95(COMSerial);
     #endif
     #ifdef ARDUINO_ARCH_STM32F4
       #define COMSerial Serial
       #define ShowSerial SerialUSB
       RH_RF95<HardwareSerial> rf95(COMSerial);
     #endif
     int msg_nr=0;
   #endif
     
/*
 * Barometer sensor (BME280)
 * -------------------------
 * This sensor is a breakout board for Bosch BMP280 high-precision, low-power combined humidity, pressure, and temperature sensor.
 * This module can be used to measure temperature, atmospheric pressure and humidity accurately and fast.
 * As the atmospheric pressure changes with altitude, it can also measure approximate altitude of a place. 
 * https://github.com/adafruit/Adafruit_BME280_Library
 * 
 */
   // Enable/disable sensor measurements if you want to  
   #define ENABLE_SENSOR_BME280
   // Sensors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_SENSOR_BME280
     #include <Wire.h>
     #include <SPI.h>
     #include <Adafruit_Sensor.h>
     #include <Adafruit_BME280.h>
     #define BME_SCK 13
     #define BME_MISO 12
     #define BME_MOSI 11
     #define BME_CS 10
     #define SEALEVELPRESSURE_HPA (1013.25)
     // Create the BME280 object on I2C     
     Adafruit_BME280 bme;
     // BME280_I2C bme;              // I2C using default 0x77 
     // or BME280_I2C bme(0x76);     // I2C using address 0x76
     int bme280_temperature = 0;
     int bme280_humidity    = 0;
     int bme280_pressure    = 0;
     int bme280_altitude    = 0;
   
     /*-----------------------------------------*/
     /* Function void sensor_bme280()           */
     /*                                         */
     /* TASK    : read out bme280 sensor data   */
     /* UPDATE  : 20.05.2020                    */
     /*-----------------------------------------*/   
   void sensor_bme280()
   {
       // read the bme280 sensor
       bme280_pressure    = (bme.readPressure() / 100.0F);
       bme280_humidity    = bme.readHumidity();
       bme280_temperature = bme.readTemperature();    
   }
   #endif

 /*  
  *  VOC & eCO2 Gas sensor (SGP30)
  *  -----------------------------
  *  The VOC and eCO2 Gas Sensor(SGP30) is an air quality detection sensor.  
  *  We provide TVOC(Total Volatile Organic Compounds) and CO2eq output for this module.
  *  The SGP30 is a digital multi-pixel gas sensor designed for easy integration into air purifier, 
  *  demand-controlled ventilation, and IoT applications. 
  *  Sensirions CMOSenstechnology offers a complete sensor system on a single chip featuring a digital I2C interface, 
  *  a temperature controlled micro hotplate, and two preprocessed indoor air quality signals. 
  *  As the first metal-oxide gas sensor featuring multiple sensing elements on one chip, 
  *  the SGP30 provides more detailed information about the air quality. 
  *  https://github.com/sparkfun/SparkFun_SGP30_Arduino_Library
  *
  */
    // Enable/disable sensor measurements if you want to
    #define ENABLE_SENSOR_SGP30
    // Sensors enabled, but not found in the hardware will be ignored
    #ifdef ENABLE_SENSOR_SGP30
      #include <Wire.h>
      #include "SparkFun_SGP30_Arduino_Library.h"
      // create an object of the SGP30 class
      SGP30 sgp30_sensor; 
      int sgp30_voc  = 0;
      int sgp30_co2  = 0;
      /*-----------------------------------------*/
      /* Function void sensor_sgp30()            */
      /*                                         */
      /* TASK    : read out air sensor data      */
      /* UPDATE  : 14.10.2020                    */
      /*-----------------------------------------*/   
      void sensor_sgp30()
      {
        sgp30_sensor.measureAirQuality();
        sgp30_voc = sgp30_sensor.TVOC;
        sgp30_co2 = sgp30_sensor.CO2;             
      }
    #endif

 /*  
  *  PIR motion sensor
  *  -----------------------------
  *  This sensor allows you to sense motion, usually human movement in its range.
  *  Simply connect it to your Xiao and program it, 
  *  when anyone moves in its detecting range, 
  *  the sensor will output HIGH on its SIG pin.
  * https://wiki.seeedstudio.com/Grove-PIR_Motion_Sensor/
  *   
  */
   // Enable/disable sensor measurements if you want to
   #define ENABLE_SENSOR_PIR_MOTION
   // Sensors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_SENSOR_PIR_MOTION
     // #define PIR_MOTION_SENSOR 2 
     #define PIR_MOTION_SENSOR A0
     bool MOTION = false;
     int motion_counter = 0;
   #endif   


/*
 * Chainable LED
 * -------------
 * Grove - Chainable RGB LED is based on P9813 chip which is a full-color LED driver. 
 * It provides 3 constant-current drivers as well as modulated output of 256 shades of gray. 
 * It communicates with a MCU using 2-wire transmission (Data and Clock). 
 * This 2-wire transmission can be used to cascade additional Grove - Chainable RGB LED modules. 
 * The built-in clock regeneration enhances the transmission distance. 
 * This Grove module is suitable for any colorful LED based projects.
 * https://wiki.seeedstudio.com/Grove-Chainable_RGB_LED/
 * https://github.com/pjpmarques/ChainableLED
 *
 */
   // Enable/disable actors if you want to
   #define ENABLE_ACTOR_RGB_LED
   // Sensors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_ACTOR_RGB_LED
     #include <ChainableLED.h>
     // defines the num of LEDs used, The undefined
     // will be lost control.
     #define NUM_LEDS 1
     // defines the pin used
     ChainableLED leds(1, 2, NUM_LEDS);
   #endif

/*
 * Led inbuild or external
 */
   int LED=13;

/*
 * Generally, you should use "unsigned long" for variables that hold time
 * The value will quickly become too large for an int to store
 */
   // this timer is used to lorawan data send frequence
   unsigned long previousMillis = 0;
   // send every 10 minutes
   unsigned long interval = 600000;  
   // send every 3 minutes
   // unsigned long interval = 180000;
   // send every 30 seconds
   // unsigned long interval = 30000;   
   unsigned long counter = 0;

/*-------------------------------------------------------------------------------*/
/* Function void SendData()                                                      */
/*                                                                               */
/* TASK    : send sensor data to your WioTerminal LoRa MQTT Gateway              */
/* UPDATE  : 03.02.2023                                                          */
/*-------------------------------------------------------------------------------*/
void SendData()
{
  // sending the data to the WioTerminal LoRa MQTT Gateway
  SERIALDEBUG_PRINTLN("[x] Sending to WioTerminal LoRa MQTT Gateway");
  // turn the LED on
  digitalWrite(LED_BUILTIN, HIGH);
  // LED blue
  leds.setColorRGB(0, 0, 0, 255);        
  // Send a message to rf95_server - some possibilities
  // unsigned char buffer[10] = {46,21,humidity, temperature};
  // rf95.send(buffer, sizeof(buffer));
  // uint8_t data[] = "N:12,C:12,L:100,P:1030,T:20,V:416,B:97";
  // rf95.send(data, sizeof(data));    
  // build the sensor message - format from LCARS SmartHome sensors 
  // the required sensor entries 
  // the name of our node - change the number for needed type
  // unsigned char textA[80] = "N:32";
  // unsigned char textA[80] = "{\"32_node\":32,\"t\":21}";
  char textA[150] = "{\"node\": \"Xiao_01_LoRa_Node\"";
  // sensor counter
  char array_counter[1]; 
  counter++;
  sprintf(array_counter, "%d", counter);
  strcat(textA,",\"C\":");
  strcat(textA,array_counter); 
  // sensor data
  char array_sensor[1];  

  #ifdef ENABLE_SENSOR_BME280
    // read the Bosch bme280 sensor
    sensor_bme280();
    // sensor barometer pressure
    sprintf(array_sensor, "%d", bme280_pressure);
    strcat(textA,",\"bme280_pressure\":");
    strcat(textA,array_sensor);
    // sensor humidity
    sprintf(array_sensor, "%d", bme280_humidity);
    strcat(textA,",\"bme280_humidity\":");
    strcat(textA,array_sensor);
    // sensor temperature
    sprintf(array_sensor, "%d", bme280_temperature);
    strcat(textA,",\"bme280_temperature\":");
    strcat(textA,array_sensor);  
  #endif

  // read the sgp30 sensor */
  #ifdef ENABLE_SENSOR_SGP30
    sensor_sgp30();
    // sensor scd30
    sprintf(array_sensor, "%d", sgp30_co2);
    strcat(textA,",\"SGP30_eCO2\":");
    strcat(textA,array_sensor);
    sprintf(array_sensor, "%d", sgp30_voc);
    strcat(textA,",\"SGP30_VOC\":");
    strcat(textA,array_sensor);     
  #endif

  #ifdef ENABLE_SENSOR_PIR_MOTION
    sprintf(array_sensor, "%d", motion_counter);
    strcat(textA,",\"pir_motion\":");
    strcat(textA,array_sensor);
  #endif           

  // json end
  strcat(textA,"}");
  SERIALDEBUG_PRINTLN((char*)textA);

  // convert the message (new since xiao)
  String OutPut=textA;
  int a = strlen(textA)+1;
  uint8_t data[a];
  OutPut.toCharArray((char *)data, sizeof(data));
   
  rf95.send(data, sizeof(data));   

  rf95.waitPacketSent();    
  // Now wait for a reply
  uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
  uint8_t len = sizeof(buf);
  if (rf95.waitAvailableTimeout(3000))
  {
    // Should be a reply message for us now   
    if(rf95.recv(buf, &len))
    {
      SERIALDEBUG_PRINT("[x] we got a reply: ");
      SERIALDEBUG_PRINTLN((char*)buf);
    }
    else
    {
      SERIALDEBUG_PRINTLN("[!] recv failed");
    }
  }
  else
  {
    SERIALDEBUG_PRINTLN("[!] No reply, is your WioTerminal LoRa MQTT Gateway online?");
  }
  delay(1000);
  // turn the LED off   
  digitalWrite(LED_BUILTIN, LOW);
  // RGB LED green
  leds.setColorRGB(0, 0, 255, 0);      
  // motion couter to zero
  motion_counter=0;  
} 

/*-------------------------------------------------------------------------------*/
/* Function void setup()                                                         */
/*                                                                               */
/* TASK    : setup all needed requirements                                       */
/* UPDATE  : 03.12.2017                                                          */
/*-------------------------------------------------------------------------------*/
void setup() 
{
  Serial.begin(9600);
  
  // boot application
  delay(3000);
  SERIALDEBUG_PRINTLN(" ");
  SERIALDEBUG_PRINTLN(" ");
  SERIALDEBUG_PRINTLN("Starting...");
  SERIALDEBUG_PRINT(application); SERIALDEBUG_PRINT(" Version "); SERIALDEBUG_PRINTLN(aktu_version);
  SERIALDEBUG_PRINTLN("connected to WioTerminal LoRa MQTT Gateway");
  SERIALDEBUG_PRINTLN(" ");

  // LED inbuild 
  pinMode(LED_BUILTIN, OUTPUT); 

  #ifdef ENABLE_ACTOR_RF95_GROVE_LORAWAN
    // start the LoRa client
    if (!rf95.init())
    {
      SERIALDEBUG_PRINTLN("[!] LoRa radio hardware client init failed!");
      while(1);
    }
    // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
    // The default transmitter power is 13dBm, using PA_BOOST.
    // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
    // you can set transmitter powers from 5 to 23 dBm:
    // rf95.setTxPower(13, false);    
    rf95.setFrequency(868.0);
    SERIALDEBUG_PRINTLN("[x] LoRa radio hardware client detected");
  #endif

  #ifdef ENABLE_SENSOR_BME280  
    if (!bme.begin(0x76)) 
    {
      SERIALDEBUG_PRINTLN("[!] Could not find a valid BME280 environment sensor, check wiring!");
      while (1);
    }
    SERIALDEBUG_PRINTLN("[x] BME280 environment sensor detected");
    // read the Bosch bme280 sensor
    sensor_bme280();
    SERIALDEBUG_PRINT("[?] BME280 --> ");
    SERIALDEBUG_PRINT("P:");   SERIALDEBUG_PRINT(bme280_pressure);
    SERIALDEBUG_PRINT("  H:"); SERIALDEBUG_PRINT(bme280_humidity);
    SERIALDEBUG_PRINT("  T:"); SERIALDEBUG_PRINTLN(bme280_temperature);
  #endif

  #ifdef ENABLE_SENSOR_SGP30 
    Wire.begin();
    if(sgp30_sensor.begin() == false)
    {
      SERIALDEBUG_PRINTLN("[!] SGP30 sensor initialisation failed!");
      while (true); // Drop into endless loop requiring restart
    }
    sgp30_sensor.initAirQuality();
    delay(1000);  
    SERIALDEBUG_PRINTLN("[x] SGP30 eCO2&VOC sensor detected");
    sensor_sgp30();
    SERIALDEBUG_PRINT("[?] SGP30 eCO2&VOC --> ");
    SERIALDEBUG_PRINT("SGP30-eCO2:");  SERIALDEBUG_PRINT(sgp30_co2); 
    SERIALDEBUG_PRINT("  SGP30-VOC:"); SERIALDEBUG_PRINTLN(sgp30_voc);        
  #endif

  #ifdef ENABLE_SENSOR_PIR_MOTION
    // init Unit PIR
    pinMode(PIR_MOTION_SENSOR, INPUT);
    SERIALDEBUG_PRINTLN("[x] Initializing PIR Unit"); 
  #endif    

  #ifdef ENABLE_ACTOR_RGB_LED
    // RGB LED
    leds.init();
    SERIALDEBUG_PRINTLN("[x] Initializing RGB LED Unit"); 
    leds.setColorRGB(0, 0, 255, 0);      
  #endif

  // send first data to your WioTerminal LoRa MQTT Gateway - then every 10 minutes
  SERIALDEBUG_PRINTLN(" ");
  SERIALDEBUG_PRINTLN("[>] sending first sensor json data msg to your WioTerminal LoRa MQTT Gateway");
  SERIALDEBUG_PRINTLN(" ");
  SendData();   
  // now we wait until we have a trigger (motion) or time frame 10 minutes
  SERIALDEBUG_PRINTLN(" ");
  SERIALDEBUG_PRINTLN("[x] wait for trigger or time frame 10 minutes"); 
  SERIALDEBUG_PRINTLN(" ");
}

/*-------------------------------------------------------------------------------*/
/* Function void loop()                                                          */
/*                                                                               */
/* TASK    : this runs forever                                                   */
/* UPDATE  : 28.05.2020                                                          */
/*-------------------------------------------------------------------------------*/
void loop()
{
  #ifdef ENABLE_SENSOR_SGP30
    // we have to read every one second to get a good baseline
    sensor_sgp30();
    delay(1000);
  #endif

  #ifdef ENABLE_SENSOR_PIR_MOTION
    int sensorValue = digitalRead(PIR_MOTION_SENSOR);
    // if the sensor value is HIGH we have an intruder ?
    if(sensorValue == HIGH)       
    { 
      if (MOTION == false)
      {
        MOTION=true;
        SERIALDEBUG_PRINTLN("[x] PIR MOTION detected ...");
        // inbuild LED
        digitalWrite(LED_BUILTIN, HIGH);
        // RGB LED red
        leds.setColorRGB(0, 255, 0, 0);                  
        motion_counter++;                     
      }
    }       
    // if the sensor value is HIGH we have an intruder ?
    if(sensorValue == LOW)       
    { 
      MOTION=false;
      // LED OFF
      digitalWrite(LED_BUILTIN, LOW);
      leds.setColorRGB(0, 0, 255, 0);  
    }
    
  #endif  
     
  /* 
   * It is checked whether the time for the transmission interval has already expired
   * If the time difference between the last save and the current time is greater
   * as the interval, the following function is executed.
  */
  if (millis() - previousMillis > interval)
  {
    // correct timer
    previousMillis = millis();
    // sending the data to the WioTerminal LoRa MQTT Gateway
    SendData();
  }          
}

archiv.zip

Arduino
iot.jpg, matrix.jpg and the config_lora.txt file
No preview (download only).

Credits

KeHoSoftware

KeHoSoftware

7 projects • 7 followers

Comments