Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Ulli
Created March 10, 2024

Mind the official way

When walking in national forest you are obliged to stay on official paths. Detect presence with sensor, play sound + notify ranger with AVR

14
Mind the official way

Things used in this project

Hardware components

AVR IoT Mini Cellular Board
Microchip AVR IoT Mini Cellular Board
×1
DEBO REC2ISD1820 Entwicklerboards - Aufnahme-/Wiedergabe-Modul, ISD1820
×1
LD2410S
×1
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1
SAMSUNG Portable 9000mAh external power bank
×1

Software apps and online services

MQTT
MQTT
VS Code
Microsoft VS Code

Story

Read more

Schematics

wired

Code

forest.ino

Arduino
avr-iot firmware arduino
#include <Arduino.h>
#include <log.h>
#include <sequans_controller.h>
#include <led_ctrl.h>
#include <lte.h>
#include <mqtt_client.h>
#include <low_power.h>
#include <mcp9808.h>
#include <veml3328.h>
#include <http_client.h>

#include "config.h"

#define MQTT_ENABLED       true
#define MQTT_USE_TLS       false
#define MQTT_KEEPALIVE     60
#define MQTT_USE_ECC       false

ResponseResult response_result;
static char response[1024] = "";
static char reason[255] = "";
static char result[1024] = "";
static char x[1024] = "";
static char message[1024] = "";
static int err;
static char *ptr;
static char deviceid[20]; // = "avr-123456789012345\0";
static char simid[21]; // = "12345678901234567890\0";
static int CESQ;
static volatile bool switchir = false;
static volatile bool pinir = false;
static bool error = true;
static float voltage;
static int temperature;

static void switchInterrupt(void) { 
    switchir = true; 
    digitalWrite(PIN_PB2, LOW); // user led on
}

static void pinInterrupt(void) { 
    pinir = true; 
    digitalWrite(PIN_PB2, LOW); // user led on
    digitalWrite(PIN_PE2, HIGH); // trigger pin
}

void(* resetFunc) (void) = 0; //declare reset function @ address 0

void setup() {
    Log.begin(115200);
    Log.info(F("setup"));
    LowPower.configurePowerDown();

    SequansController.begin();
    // telekom.nl sim iotcreators.com APN
    response_result = SequansController.writeCommand(F("AT+CGDCONT=1,\"IPV4V6\",\"m2m.public.nl\""),response,sizeof(response));
    if (response_result != ResponseResult::OK) {
        Log.errorf(F("AT+CGDCONT: %s\r\n"),response);
    }
    // Returns result codes in words 
    response_result = SequansController.writeCommand(F("ATV1"),response,sizeof(response));
    if (response_result != ResponseResult::OK) {
        Log.errorf(F("ATV1: %s\r\n"),response);
    }
    // get deviceid
    response_result = SequansController.writeCommand(F("AT+CGSN=1"),response,sizeof(response));
    if (response_result != ResponseResult::OK) {
        Log.errorf(F("1AT+CGSN=1: %s\r\n"),response);
    } else {
        Log.infof(F("1AT+CGSN=1: %s\r\n"),response);
        SequansController.writeCommand(F("AT+CGSN=1"),response,sizeof(response));
        Log.infof(F("2AT+CGSN=1: %s\r\n"),response);
        if (response[0] == '\0') {
            response_result = SequansController.writeCommand(F("AT^RESET"),response,sizeof(response));
            if (response_result == ResponseResult::OK) {
                Log.infof(F("AT^RESET: %s\r\n"),response);
                response_result = SequansController.writeCommand(F("AT+CGSN=1"),response,sizeof(response));
                if (response_result == ResponseResult::OK) {
                    Log.infof(F("3AT+CGSN=1: %s\r\n"),response);
                    if (response[0] == '\0') {    
                        // the response was alway incomplete or empty until here: giving up, start over        
                        resetFunc();
                    }
                } else {
                    Log.errorf(F("4AT+CGSN=1: %s\r\n"),response);
                }
            } else {
                Log.errorf(F("AT^RESET: %s\r\n"),response);
            }
        } else {
            ptr = strtok(response, ": \"\r\n");
            if (strlen(ptr) != 15) {
                SequansController.writeCommand(F("AT+CGSN=1"),response,sizeof(response));
            }
        }
    }
    // TODO: check for 2 " +CGSN: "dsadadasdasad"
    Log.infof(F("AT^RESET or AT+CGSN=1: %s\r\n"),response);
    ptr = strtok(response, ": \"\r\n");
    // validate deviceid 15
    if (strlen(ptr) != 15) {
        // I hate SequansController it is just not reliable....(this was before I increased strings to 1024)
        response_result = SequansController.writeCommand(F("AT+CGSN=1"),response,sizeof(response));
        ptr = strtok(response, ": \"\r\n");
        if (strlen(ptr) != 15) {
            resetFunc();
        }
    }
    // finaly deviceid
    snprintf(deviceid, sizeof(deviceid), "avr-%s", ptr);
    // start mobile connection
    err = Lte.begin(60000,true);
    if (err) {
        Log.infof(F("lte: %d\r\n"),err);
    } else {
        Log.errorf(F("lte: %d\r\n"),err);
    }
    // get simid
    response_result = SequansController.writeCommand(F("AT+SQNCCID"),response,sizeof(response));
    if (response_result != ResponseResult::OK) {
        Log.errorf(F("AT+SQNCCID: %s\r\n"),response);
    }
    Log.infof(F("AT+SQNCCID: %s\r\n"),response);
    ptr = strtok(response, "\"");
    ptr = strtok(NULL, "\"");
    snprintf(simid, sizeof(simid), "%s", ptr);
    // get modem firmware version
    response_result = SequansController.writeCommand(F("AT+CGMR"),response,sizeof(response));
    if (response_result != ResponseResult::OK) {
        Log.errorf(F("AT+CGMR: %s\r\n"),response);
    }
    //    AT+CGMR UE8.0.5.13
    ptr = strtok(response, "\r\n");
    strcpy(message, "{");
    strcat(message, ptr);
    strcat(message, ", ");
    strcat(message, deviceid);
    strcat(message, ", I:");
    strcat(message, simid);
    strcat(message, ", S:");
    // get modem serial number
    response_result = SequansController.writeCommand(F("ATI2"),response,sizeof(response));
    if (response_result != ResponseResult::OK) {
        Log.errorf(F("AT+SQNCCID: %s\r\n"),response);
    }
    ptr = strtok(response, ": \"\r\n");
    ptr = strtok(NULL, ": \"\r\n");
    strcat(message, ptr);
    // get device ip at+cgpaddr
    LedCtrl.begin();
    if (MQTT_ENABLED) {
        err = MqttClient.begin(deviceid,MQTT_BROKER,MQTT_PORT,MQTT_USE_TLS,MQTT_KEEPALIVE,MQTT_USE_ECC,MOSQUITTO_USERNAME,MOSQUITTO_PASSWORD,30000,true);
        if (err) {
            Log.infof(F("mqtt: %d\r\n"),err);
        } else {
            Log.errorf(F("mqtt: %d\r\n"),err);
        }
        connectToMQTT();
        strcat(message, "}");
        String message_to_publish = String(message);
        strcpy(response, "hackster/forest/");
        strcat(response, deviceid);
        strcat(response, "/boot");
        err = MqttClient.publish(response, message_to_publish.c_str());
        Log.infof(F("publish: boot, %d %s %s"),err,response,message);
    }

    Mcp9808.begin(); 
    Veml3328.begin();
    Veml3328.shutdown();

    // user led
    pinConfigure(PIN_PB2, PIN_DIR_OUTPUT);
    // pin output trigger
    pinConfigure(PIN_PE2, PIN_DIR_OUTPUT);
    // switchir / pinir input
    pinConfigure(PIN_PD2, PIN_DIR_INPUT | PIN_PULLUP_ON); // for debugging ir with sw1
    pinConfigure(PIN_PD0, PIN_DIR_INPUT | PIN_PULLUP_OFF); // for pin ir
    attachInterrupt(PIN_PD2, switchInterrupt, FALLING);
    attachInterrupt(PIN_PD0, pinInterrupt, RISING); // RISING FALLING
    Log.infof(F("------pins: %d\r\n"), digitalRead(PIN_PD0));

    // get signal strength, now that we transmitted smth.
    response_result = SequansController.writeCommand(F("AT+CESQ"),response,sizeof(response));
    err = SequansController.extractValueFromCommandResponse(response,5,result,sizeof(result));
    if (err) {
        Log.infof(F("AT+CESQ: %s; "),result);
        CESQ = atoi(result)-140;
        Log.infof(F("AT+CESQ: %d\r\n"),CESQ);
    }

    // celebrate finishing setup
    LedCtrl.startupCycle();
}

void loop() { 
    Log.infof("- - L O O P - -\r\n");
    static int i=0;
    distance = 0;
    i++;

    strcpy(reason, "hourly");

    // since we might just had finished low power mode
    Mcp9808.wake(); 

    // check if switch was pressed or interrupt on pin
    if (switchir) {
        Log.info(F("switchInterrupt"));
        delay(1000);
        digitalWrite(PIN_PB2, HIGH); // user led off
        switchir = false;
        strcpy(reason, "test");
    }
    if (pinir) {
        Log.info(F("pinInterrupt"));
        delay(1000);
        digitalWrite(PIN_PB2, HIGH); // user led off
        digitalWrite(PIN_PE2, LOW); // untrigger pin
        pinir = false;
        strcpy(reason, "alert");
        distance = 1;
        // todo: implement quite time for 15? minutes...
    }

        SequansController.writeCommand(F("AT+CGSN=0"),response,sizeof(response));
        Log.infof(F("AT+CGSN=0: %s\r\n"),response);

    connectToNetwork();
    // build hourly mqtt reporting
    strcpy(message, "{");
    strcat(message, deviceid);
    strcat(message, ", I:");
    strcat(message, simid);
    // dummy, since I had problems with empty+uncomplete responses
    response_result = SequansController.writeCommand(F("AT+CESQ"),response,sizeof(response));
    // get network operator MC/NC
    response_result = SequansController.writeCommand(F("AT+COPS?"),response,sizeof(response));
    if (response_result == ResponseResult::OK) {
        Log.infof(F("AT+COPS?: %s\r\n"),response);
    }
    err = SequansController.extractValueFromCommandResponse(response,2,result,sizeof(result));
    if (err) {
        ptr = strtok(result, ": \"\r\n");
        strcat(message, ", MC/NC:");
        strcat(message, ptr);
    }
    // get TAC/CI
    // get connection mode: LTE-M or NBIOT
    response_result = SequansController.writeCommand(F("AT+CEREG?"),response,sizeof(response));
    if (response_result == ResponseResult::OK) {
        Log.infof(F("AT+CEREG: %s\r\n"),response);
    }
    err = SequansController.extractValueFromCommandResponse(response,2,result,sizeof(result));
    if (err) {
        ptr = strtok(result, ": \"\r\n");
        strcat(message, ", TAC:");
        strcat(message, ptr);
    }
    err = SequansController.extractValueFromCommandResponse(response,3,result,sizeof(result));
    if (err) {
        ptr = strtok(result, ": \"\r\n");
        strcat(message, ", CI:");
        strcat(message, ptr);
    }
    err = SequansController.extractValueFromCommandResponse(response,4,result,sizeof(result));
    if (err) {
        Log.infof(F("extractValueFromCommandResponse: %s\r\n"),result);
        if (atoi(result) == 7) {sprintf(x, ", LTEM");}
        if (atoi(result) == 9) {sprintf(x, ", NBIOT");}
    }
    strcat(message, x);
    // get date/time
    response_result = SequansController.writeCommand(F("AT+CCLK?"),response,sizeof(response));
    if (response_result == ResponseResult::OK) {
        Log.infof(F("AT+CCLK?: %s\r\n"),response);
    }
    // get voltage of battery
    voltage = LowPower.getSupplyVoltage();
    Log.infof(F("The voltage supplied is: %.2f\r\n"), voltage);
    snprintf(response, sizeof(response), ", bat:%.2f", (double) voltage);
    strcat(message, response);
    // get temperature
    temperature = int(Mcp9808.readTempC());
    Log.infof(F("Temperature (*C): %d\r\n"), temperature);
    snprintf(response, sizeof(response), ", t:%d", temperature);
    strcat(message, response);

    snprintf(response, sizeof(response), ", %d", CESQ);
    strcat(message, response);
    snprintf(response, sizeof(response), ", up:%2d", i);
    strcat(message, response);

    snprintf(response, sizeof(response), ", p:%d", digitalRead(PIN_PD0));
    strcat(message, response);

    if (MQTT_ENABLED) {
        connectToMQTT();
        strcat(message, "}");
        String message_to_publish = String(message);
        strcpy(response, "hackster/forest/");
        strcat(response, deviceid);
        strcat(response, "/");
        strcat(response, reason);
        err = MqttClient.publish(response, message_to_publish.c_str());
        Log.infof(F("publish: %d %d %s %s"),i,err,response,message);
    }
    
    // get signal strength ...
    response_result = SequansController.writeCommand(F("AT+CESQ"),response,sizeof(response));
    err = SequansController.extractValueFromCommandResponse(response,5,result,sizeof(result));
    if (err) {
        Log.infof(F("AT+CESQ: %s\r\n"),result);
        Log.infof(F("AT+CESQ: %d\r\n"),atoi(result)-140);
        CESQ = atoi(result)-140;
    }
    
    // celebrate another loop finish
    LedCtrl.startupCycle();

    // go to sleep
    Mcp9808.shutdown();
    LowPower.powerDown(3540); // 60 min - 1 min for working through the loop ;-) - try to hit same minute every hour
    //delay(600000); // 10min
}

static void connectToNetwork() {
    if (!Lte.isConnected()) {
        while (!Lte.begin(60000,true)) {} // hint: timesout, no danger of endless
        Log.infof(F("REConnected to operator: %s\r\n"), Lte.getOperator().c_str());
    }
}

static void connectToMQTT() {
    int i =0;
    if (Lte.isConnected()) {
        if (!MqttClient.isConnected()) {
            while ((i < 10) && !MqttClient.begin(deviceid,MQTT_BROKER,MQTT_PORT,MQTT_USE_TLS,MQTT_KEEPALIVE,MQTT_USE_ECC,MOSQUITTO_USERNAME,MOSQUITTO_PASSWORD,30000,true)) {
                i++;
            }
            Log.info(F("REConnected to MqttClient"));
        }
    } else {
        Log.error(F("no network, droping MqttClient connect"));
    }
}

config.h

Arduino
config values, replace with your's
/************************ mqtt Config *******************************/
#define MQTT_BROKER        "iot.xxxxxxx.com"
#define MQTT_PORT          1234
#define MOSQUITTO_USERNAME "xxxxxxx"
#define MOSQUITTO_PASSWORD "xxxxxxx"

mqtt.py

Python
server code for listening to alerts and sending an email
replace config values on top with your's
import time
import datetime
import paho.mqtt.subscribe as subscribe
import os
import paho.mqtt.client as mqtt

username='abcdef'
password='12345'
host='forest.example.com'
port=1883
email='forest.ranger@iot.village'
device='avr-0123456789'

# The callback function of connection
def on_connect(client, userdata, flags, rc):
    print(f"Connected with result code {rc}")
    client.subscribe("hackster/forest/"+device+"/alert")

# The callback function for received message
def on_message(client, userdata, msg):
    print(now, end=' ')
    print(f"{msg.topic} {msg.payload}")
    os.system('echo "hackster/forest/alert" | mailx -s "'+msg.payload.decode("utf-8")+'" '+email)

time.sleep(1)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1)
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(username, password=password)
client.connect(host, port, 60)
client.loop_forever()

Credits

Ulli

Ulli

1 project • 0 followers

Comments