Hiking is really fun, but sometimes hiking trails are used so often that the local ecosystem can be put at risk. For example, going off trail and causing damage. Additionally, illegal hunting can also put the species at risk.
Goal: Reward hikers for reporting activity and trail conditions in addition to collecting sensor information about trails with the Wio Terminal deployed in various hiking locations.
Live demo: https://trail-conservation-6e76un.spheron.app
The Seeed Studio LoRaWAN Dev Kit makes this extremely user friendly with no real hardware skills required.
The kit comes with all the basics sensors required. In this guide, we will be using the LoRa-E5 and the AI Vision sensor included in the kit.
Attach the AI camera to the left port and the LoRa to the right port.
Use the following Arduino code and upload to the Wio Terminal.
#include <Arduino.h>
#include "disk91_LoRaE5.h"
#include "Seeed_Arduino_GroveAI.h"
#include <Wire.h>
#include "TFT_eSPI.h"
#define FF17 &FreeSans9pt7b
// keys
uint8_t deveui[] = {0x...};
uint8_t appeui[] = {0x...};
uint8_t appkey[] = {0x...};
GroveAI ai(Wire);
TFT_eSPI tft;
Disk91_LoRaE5 lorae5(false); // true, false whatever
void setup()
{
Wire.begin();
Serial.begin(115200);
randomSeed(analogRead(0));
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);
tft.setFreeFont(FF17);
tft.fillScreen(TFT_BLACK);
tft.setCursor(0, 20);
Serial.println("begin");
tft.println("begin");
uint32_t start = millis();
tft.println("LoRa E5 Init");
// init the library, search the LORAE5 over the different WIO port available
if (!lorae5.begin(DSKLORAE5_SEARCH_WIO))
{
Serial.println("LoRa E5 Init Failed");
tft.println("LoRa E5 Init Failed");
while (1)
;
}
tft.println("LoRa E5 Setup");
// Setup the LoRaWan Credentials
if (!lorae5.setup(
DSKLORAE5_ZONE_US915, // LoRaWan Radio Zone EU868 here
deveui,
appeui,
appkey))
{
Serial.println("LoRa E5 Setup Failed");
tft.println("LoRa E5 Setup Failed");
while (1);
}
if (ai.begin(ALGO_OBJECT_DETECTION, MODEL_EXT_INDEX_1)) // Object detection and pre-trained model 1
{
Serial.print("Version: ");
Serial.println(ai.version());
Serial.print("ID: ");
Serial.println(ai.id());
Serial.print("Algo: ");
Serial.println(ai.algo());
Serial.print("Model: ");
Serial.println(ai.model());
Serial.print("Confidence: ");
Serial.println(ai.confidence());
tft.print("AI version ");
tft.println(ai.version());
}
else
{
Serial.println("Algo begin failed. Program halting here.");
tft.println("Algo begin failed. Program halting here.");
while (1)
;
}
}
void loop()
{
uint32_t tick = millis();
tft.fillScreen(TFT_BLACK);
tft.setCursor(0, 20);
tft.println("Begin ai invoke");
// tft.println(ai.state());
if (ai.invoke()) // begin invoke
{
tft.println("wait for ai invoke");
while (1) // wait for invoking finished
{
CMD_STATE_T ret = ai.state();
if (ret == CMD_STATE_IDLE)
{
break;
}
delay(20);
}
tft.println("AI state ready");
uint8_t len = ai.get_result_len(); // receive how many people detect
if (len)
{
int time1 = millis() - tick;
Serial.print("Time consuming: ");
Serial.println(time1);
Serial.print("Number of people: ");
Serial.println(len);
tft.println("Ident success");
object_detection_t data; //get data
for (int i = 0; i < len; i++)
{
Serial.println("result:detected");
Serial.print("Detecting and calculating: ");
Serial.println(i + 1);
ai.get_result(i, (uint8_t *)&data, sizeof(object_detection_t)); //get result
Serial.print("confidence:");
Serial.print(data.confidence);
Serial.println();
uint8_t data[] = { random() };
tft.fillScreen(TFT_BLACK);
tft.setFreeFont(FF17);
tft.setCursor(0, 20);
Serial.print("Unique code: ");
tft.print("Unique code: ");
for (int i = 0; i < 4; i++)
{
Serial.print(data[i]);
tft.print(data[i]);
}
Serial.println();
tft.println();
tft.println("sending to Helium");
// Send an uplink message. The Join is automatically performed
if (lorae5.send_sync(
1, // LoRaWan Port
data, // data array
sizeof(data), // size of the data
false, // we are not expecting a ack
7, // Spread Factor
14 // Tx Power in dBm
))
{
Serial.println("Uplink done");
if (lorae5.isDownlinkReceived())
{
Serial.println("A downlink has been received");
if (lorae5.isDownlinkPending())
{
Serial.println("More downlink are pending");
}
}
} else {
Serial.println("uplink failed");
}
delay(30000);
}
}
else
{
// Serial.println("No identification");
delay(1000);
}
}
else
{
delay(1000);
Serial.println("Invoke Failed.");
tft.println("Invoke Failed.");
}
}
The LoRa code is based off of https://github.com/disk91/Disk91_LoRaE5 and has many examples to test the device before fully committing to the code above.
The code will detect a person then display a random number which can then be redeemed in the frontend to claim a NFT. The Terminal should display the following:
1.b Software1.b.a HeliumThis part will be the bulk of the work. The Helium console should be setup such that the integration flow looks like this
uniq_code_decoder is defined as:
function Decoder(bytes, port, uplink_info) {
var decoded = {};
if (port == 1) {
decoded.unique_code = bytes;
}
return decoded;
}
For the webhook stage, I choose to use RequestBin:
For the "node" stage, the code is:
import { FormData, Blob } from "formdata-node";
import { FormDataEncoder } from "form-data-encoder";
import {Readable} from "stream"
import fetch from "node-fetch"
// To use previous step data, pass the `steps` object to the run() function
export default defineComponent({
async run({ steps, $ }) {
// Return data to use it in future steps
const url = "https://api.web3.storage/upload";
let sensorData = {
"device_pinValue": steps.trigger.event.body.decoded.payload.unique_code
};
console.log(sensorData);
let form = new FormData();
let blob = new Blob([new TextEncoder().encode(JSON.stringify(sensorData))], { type: "application/json;charset=utf-8" });
console.log(blob);
form.append('file', blob, steps.trigger.event.body.uuid + ".json");
console.log(form);
const encoder = new FormDataEncoder(form)
const key = "web3APIKEY"
const options = {
method: "post",
headers: Object.assign({}, encoder.headers, {"Authorization": "Bearer "+ key}),
body: Readable.from(encoder)
}
const resp = await fetch(url, options);
const text = await resp.text()
console.log(text);
return resp;
},
})
"web3APIKEY" needs to be replaced with a API key from web3.storage (it's free).
This part can also be served by the backend code.
1.b.b web3 PortalThe frontend code lives here: https://github.com/exp0nge/trail-conservation/tree/master/trail-ui It is built using ReactJS so should be pretty readable. There's a few integrations:
- NFTPort: minting Trail Completionist NFTs using the unique code from the terminal
- Pexels: images for the NFT
- web3.storage: decentralized storage for the NFT image backup
A demo can be seen here: https://trail-conservation-6e76un.spheron.app/
Comments