Kutluhan Aktar
Published © CC BY

AI-driven LoRaWAN Fertilizer Pollution Detector w/ WhatsApp

Collect data to train a NN to detect synthetic fertilizer contamination. Then, get informed of the results via WhatsApp over LoRaWAN.

ExpertFull instructions provided4,192

Things used in this project

Hardware components

Seeed Studio SenseCAP A1101 - LoRaWAN Vision AI Sensor
×1
Seeed Studio SenseCAP M2 Data-Only LoRaWAN Indoor Gateway
×1
Arduino Nano R3
Arduino Nano R3
×1
LattePanda 3 Delta
LattePanda 3 Delta
×1
Elecrow 8.8″ (1920*480) IPS Screen
×1
SH1106 OLED Display (128x64)
×1
Anycubic Kobra 2
×1
5mm Common Anode RGB LED
×1
SparkFun Button (6x6x12mm)
×3
Solderless Breadboard Half Size
Solderless Breadboard Half Size
×2
Jumper wires (generic)
Jumper wires (generic)
×1

Software apps and online services

Edge Impulse Studio
Edge Impulse Studio
Arduino IDE
Arduino IDE
Twilio API for WhatsApp
Twilio API for WhatsApp
Thonny
Fusion
Autodesk Fusion
Ultimaker Cura
XAMPP
Seeed Studio SenseCAP Portal
Seeed Studio SenseCAP Mate

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Custom parts and enclosures

Edge Impulse Model (SenseCAP A1101 Firmware [UF2])

Fertilizer_Contamination_Detector_main_case.stl

Fertilizer_Contamination_Detector_remote_cover.stl

Fertilizer_Contamination_Detector_front_cover.stl

Elecrow_Module_Left.stl

Elecrow_Module_Right.stl

Elecrow_Module_Cover.stl

Schematics

SenseCAP A1101

Code

A1101_data_img_collection.py

Python
# AI-driven LoRaWAN Fertilizer Pollution Detector w/ WhatsApp
#
# LattePanda 3 Delta
#
# Collect data to train a NN to detect fertilizer contamination.   
# Then, get informed of the results via WhatsApp over LoRaWAN.
#
# By Kutluhan Aktar

import cv2
import serial
from threading import Thread
from time import sleep
import os
from PIL import Image
from io import BytesIO
import usb1
import numpy as np
import datetime

class A1101_data_img_collection():
    def __init__(self):
        # Define the required settings to obtain information from SenseCAP A1101 LoRaWAN Vision AI Sensor.
        self.WEBUSB_JPEG = (0x2B2D2B2D)
        self.WEBUSB_TEXT = 0x0F100E12
        self.VendorId = 0x2886
        self.ProductId = [0x8060, 0x8061]
        self.img_buff = bytearray()
        self.buff_size = 0
        self.time_out = 1000
        # Get the connected USB device context.       
        self.context = usb1.USBContext()
        # Initialize serial communication with Arduino Nano to obtain the given commands.
        self.arduino_nano = serial.Serial("COM7", 115200, timeout=2000)
        # Initialize and test SenseCAP A1101 connection.
        self.get_rlease_device(False)
        self.disconnect()  

    def read_data(self):
        # If SenseCAP A1101 is accessible:
        with self.handle.claimInterface(2):
            # Get the data endpoints.
            self.handle.setInterfaceAltSetting(2, 0)
            self.handle.controlRead(0x01 << 5, request=0x22, value=0x01, index=2, length=2048, timeout=self.time_out)
            # Get all transferred data objects.
            transfer_list = []
            for _ in range(1):
                transfer = self.handle.getTransfer()
                transfer.setBulk(usb1.ENDPOINT_IN | 2, 2048, callback=self.processReceivedData, timeout=self.time_out)
                transfer.submit()
                transfer_list.append(transfer)
            # Check for any submitted data object in the received data packet.
            while any(x.isSubmitted() for x in transfer_list):
                self.context.handleEvents()

    def processReceivedData(self, transfer):
        # If SenseCAP A1101 generates a data packet successfully, process the received information.
        if transfer.getStatus() != usb1.TRANSFER_COMPLETED:
            # transfer.close()
            return
        # Extract the captured image from the processed data packet.
        data = transfer.getBuffer()[:transfer.getActualLength()]
        self.convert_and_show_img(data)
        # Resubmit the data packet after processing to avoid errors.
        transfer.submit()

    def convert_and_show_img(self, data: bytearray):
        # Convert the received data packet.
        if (len(data) == 8) and (int.from_bytes(bytes(data[:4]), 'big') == self.WEBUSB_JPEG):
            self.buff_size = int.from_bytes(bytes(data[4:]), 'big')
            self.img_buff = bytearray()
        elif (len(data) == 8) and (int.from_bytes(bytes(data[:4]), 'big') == self.WEBUSB_TEXT):
            self.buff_size = int.from_bytes(bytes(data[4:]), 'big')
            self.img_buff = bytearray()
        else:
            self.img_buff = self.img_buff + data
        # If the received data packet is converted to an image buffer successfully, display the generated image on the screen.
        if self.buff_size == len(self.img_buff):
            try:
                img = Image.open(BytesIO(self.img_buff))
                img = np.array(img)
                cv2.imshow('A1101_data_img_collection', cv2.cvtColor(img,cv2.COLOR_RGB2BGR))
                # Stop the video stream if requested.
                if cv2.waitKey(1) != -1:
                    cv2.destroyAllWindows()
                    print("\nCamera Feed Stopped!")
                # Store the latest frame captured by SenseCAP A1101.
                self.latest_frame = img
            except:
                self.img_buff = bytearray()
                return

    def connect(self):
        # Connect to SenseCAP A1101.
        self.handle = self.get_rlease_device(True)
        if self.handle is None:
            print('\nSenseCAP A1101 not detected!')
            return False
        with self.handle.claimInterface(2):
            self.handle.setInterfaceAltSetting(2, 0)
            self.handle.controlRead(0x01 << 5, request=0x22, value=0x01, index=2, length=2048, timeout=self.time_out)
            print('\nSenseCAP A1101 detected successfully!')
        return True

    def disconnect(self):
        # Reset the SenseCAP A1101 connection.
        try:
            print('Resetting the device connection... ')
            with usb1.USBContext() as context:
                handle = context.getByVendorIDAndProductID(self.VendorId, self.d_ProductId, skip_on_error=False).open()
                handle.controlRead(0x01 << 5, request=0x22, value=0x00, index=2, length=2048, timeout=self.time_out)
                handle.close()
                print('Device connection has been reset successfully!')
            return True
        except:
            return False

    def get_rlease_device(self, get=True):
        # Establish the SenseCAP A1101 connection.
        print('*' * 50)
        print('Establishing connection...')
        # Get the device information. 
        for device in self.context.getDeviceIterator(skip_on_error=True):
            product_id = device.getProductID()
            vendor_id = device.getVendorID()
            device_addr = device.getDeviceAddress()
            bus = '->'.join(str(x) for x in ['Bus %03i' % (device.getBusNumber(),)] + device.getPortNumberList())
            # Check if there is a connected device.
            if(vendor_id == self.VendorId) and (product_id in self.ProductId):
                self.d_ProductId = product_id
                print('\r' + f'\033[4;31mID {vendor_id:04x}:{product_id:04x} {bus} Device {device_addr} \033[0m', end='')
                # Turn on or off SenseCAP A1101.
                if get:
                    return device.open()
                else:
                    device.close()
                    print('\r' + f'\033[4;31mID {vendor_id:04x}:{product_id:04x} {bus} Device {device_addr} CLOSED\033[0m', flush=True)
                    
    def collect_data(self):
        while True:
            if not self.connect():
                continue
            self.read_data()
            del self.handle
            self.disconnect()

    def get_transferred_data_packets(self):
        # Obtain the transferred commands from Arduino Nano via serial communication, including fertilizer hazard classes (labels).
        if self.arduino_nano.in_waiting > 0:
            command = self.arduino_nano.readline().decode("utf-8", "ignore").rstrip()
            if(command.find("Enriched") >= 0):
                print("\nCapturing an image! Label: Enriched")
                self.save_img_sample("Enriched")
            if(command.find("Unsafe") >= 0):
                print("\nCapturing an image! Label: Unsafe")
                self.save_img_sample("Unsafe")
            if(command.find("Toxic") >= 0):
                print("\nCapturing an image! Label: Toxic")
                self.save_img_sample("Toxic")
        sleep(1)
        
    def save_img_sample(self, _class):    
        date = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = './samples/{}_IMG_{}.jpg'.format(_class, date)
        # If requested, save the recently captured image (latest frame) as a sample.
        cv2.imwrite(filename, self.latest_frame)
        print("\nSample Saved Successfully: " + filename)
        
        
# Define the soil object.
soil = A1101_data_img_collection()

# Define and initialize threads.
def A1101_camera_feed():
    soil.collect_data()
        
def activate_received_commands():
    while True:
        soil.get_transferred_data_packets()

Thread(target=A1101_camera_feed).start()
Thread(target=activate_received_commands).start()

A1101_whatsapp_interface.py

Python
# AI-driven LoRaWAN Fertilizer Pollution Detector w/ WhatsApp
#
# LattePanda 3 Delta
#
# Collect data to train a NN to detect fertilizer contamination.   
# Then, get informed of the results via WhatsApp over LoRaWAN.
#
# By Kutluhan Aktar

from twilio.rest import Client
import requests
import json
from time import sleep

# Define the Twilio account settings and the client object.
twilio_account_sid = '<_SID_>'
twilio_auth_token = '<_TOKEN_>'
twilio_client = Client(twilio_account_sid, twilio_auth_token)

# Define the API ID and Access API key variables to connect to the SenseCAP Portal.
API_ID = '<_ID_>'
API_key = '<_KEY_>'

# Define the required device information of SenseCAP A1101 LoRaWAN Vision AI Sensor.
device_eui = "2CF7F1C04340004A"
measurement_id = "4175"
channel_index = "1"

# Define the host of the SenseCAP HTTP API. 
host = "https://sensecap.seeed.cc/openapi/"

# Define the URL endpoint to obtain model detection results via the SenseCAP HTTP API.
get_latest_result = "view_latest_telemetry_data?device_eui={}&measurement_id={}&channel_index={}".format(device_eui, measurement_id, channel_index)

def send_WhatsApp_message(_from, _to, _message):
    # Send the given message via WhatsApp to inform the user of the model detection results.
    twilio_message = twilio_client.messages.create(
      from_ = 'whatsapp:'+_from,
      body = _message,
      media_url = 'https://media-cdn.seeedstudio.com/media/catalog/product/cache/bb49d3ec4ee05b6f018e93f896b8a25d/1/0/101990962-a1101-first-new-10.17.jpg',
      to = 'whatsapp:'+_to
    )
    print("\nWhatsApp Message Sent: "+twilio_message.sid)

def transfer_latest_result():
    # Obtain the latest model detection result via the SenseCAP HTTP API and notify the user of the received information through WhatsApp.
    url = host + get_latest_result
    # Make an HTTP GET request to the SenseCAP Portal by utilizing the provided HTTP authentication credentials (username and password).
    res = requests.get(url, auth = (API_ID, API_key))
    # Decode the received JSON object.
    res = json.loads(res.text)
    detection_digit = res["data"][0]["points"][0]["measurement_value"]
    date = res["data"][0]["points"][0]["time"]
    # Convert the obtained result digits to the detected class and the precision score.
    detected_class = "Nothing!"
    precision = 0
    if(detection_digit > 0 and detection_digit < 1):
        detected_class = "Enriched"
        precision = detection_digit
    if(detection_digit > 1 and detection_digit < 2):
        detected_class = "Toxic"
        precision = detection_digit-1
    if(detection_digit > 2):
        detected_class = "Unsafe"
        precision = detection_digit-2
    # Create a WhatsApp message from the retrieved information.
    message = " Latest Model Detection Result\n\n {}\n Class: {}\n Precision: {}".format(date, detected_class, round(precision, 2))
    print(message)
    # Transmit the generated message to the user via WhatsApp.
    send_WhatsApp_message('+_____________', '+_____________', message)
    
    
while True:
    # Notify the user of the latest model detection result every 10 minutes.
    transfer_latest_result()
    sleep(60*10)

AIoT_Fertilizer_Contamination_Detector_remote_control.ino

Arduino
         /////////////////////////////////////////////  
        //     AI-driven LoRaWAN Fertilizer        //
       //     Pollution Detector w/ WhatsApp      //
      //             ---------------             //
     //             (Arduino Nano)              //           
    //             by Kutluhan Aktar           // 
   //                                         //
  /////////////////////////////////////////////

//
// Collect data to train a NN to detect fertilizer contamination. Then, get informed of the results via WhatsApp over LoRaWAN.
//
// For more information:
// https://www.theamplituhedron.com/projects/AI_driven_LoRaWAN_Fertilizer_Pollution_Detector_w_WhatsApp/
//
//
// Connections
// Arduino Nano :
//                                SH1106 OLED Display (128x64)
// D11  --------------------------- SDA
// D13  --------------------------- SCK
// D8   --------------------------- RST
// D9   --------------------------- DC
// D10  --------------------------- CS
//                                Control Button (A)
// A0   --------------------------- +
//                                Control Button (B)
// A1   --------------------------- +
//                                Control Button (C)
// A2   --------------------------- +
//                                5mm Common Anode RGB LED
// D3   --------------------------- R
// D5   --------------------------- G
// D6   --------------------------- B  


// Include the required libraries:
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>

// Define the SH1106 screen settings:
#define OLED_MOSI      11  // MOSI (SDA)
#define OLED_CLK       13  // SCK
#define OLED_DC        9
#define OLED_CS        10
#define OLED_RESET     8
Adafruit_SH1106 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

// Define monochrome graphics:
static const unsigned char PROGMEM _home [] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x01, 0xF8, 0x07, 0xC0, 0x7E, 0x81, 0xFA, 0x07, 0xC0, 0x7E,
0xC1, 0xFB, 0x07, 0xC8, 0x66, 0x01, 0x98, 0x06, 0x40, 0x66, 0x01, 0x98, 0x06, 0x60, 0x7F, 0xE1,
0xFF, 0x87, 0xFE, 0x60, 0x61, 0x81, 0x86, 0x06, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x60, 0x61, 0x81,
0x86, 0x06, 0x60, 0x61, 0x81, 0x86, 0x06, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x60, 0x61, 0xA5, 0x86,
0x06, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x02, 0x00, 0x18, 0x00, 0x40,
0x03, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x00, 0x18, 0x00, 0x80, 0x01, 0xF8, 0x18, 0x1F, 0x80, 0x00,
0x0C, 0x18, 0x30, 0x00, 0x00, 0x04, 0x18, 0x20, 0x00, 0x00, 0x15, 0x19, 0xA8, 0x00, 0x00, 0x0F,
0x18, 0xF0, 0x00, 0x00, 0x06, 0x18, 0x60, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x24,
0x00, 0x00, 0x00, 0x01, 0xE7, 0x80, 0x00, 0x00, 0x1F, 0xA5, 0xF8, 0x00, 0x00, 0xFF, 0xC3, 0xFF,
0x00, 0x03, 0xFF, 0xE7, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x07, 0x0F, 0xFF, 0xF0, 0xE0,
0x07, 0xE0, 0xFF, 0x07, 0xE0, 0x07, 0xFE, 0x18, 0x7F, 0xE0, 0x07, 0xFF, 0xC3, 0xFF, 0xE0, 0x00,
0x7F, 0xE7, 0xFE, 0x00, 0x00, 0x0F, 0xE7, 0xF0, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};
static const unsigned char PROGMEM enriched [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x3F,
0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x0F, 0xFE, 0x3F, 0xBF,
0xC0, 0x3F, 0xFE, 0x3F, 0xDF, 0xC0, 0xFF, 0xBC, 0x1F, 0xCF, 0xC0, 0xFE, 0x7C, 0x1F, 0xE7, 0xC1,
0xFD, 0xFC, 0x1F, 0xF7, 0xC1, 0xF3, 0xF8, 0x0F, 0xFB, 0xC1, 0xE7, 0xF8, 0x07, 0xF9, 0xC1, 0xDF,
0xF0, 0x03, 0xFD, 0xC1, 0x9F, 0xF0, 0x00, 0xFC, 0x83, 0x3F, 0xE0, 0x00, 0x00, 0xC6, 0x7F, 0x80,
0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x03,
0xBB, 0x80, 0x00, 0x00, 0x1F, 0xB9, 0xF8, 0x00, 0x00, 0x38, 0x18, 0x1C, 0x00, 0x00, 0x70, 0x18,
0x0E, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFC,
0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00,
0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00,
0x07, 0xFF, 0xE0, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x03,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 
};
static const unsigned char PROGMEM unsafe [] = {
0x10, 0x00, 0x3C, 0x0C, 0x00, 0x38, 0x01, 0xFF, 0x8E, 0x00, 0x18, 0x03, 0xFF, 0xCC, 0x00, 0x02,
0x73, 0xFF, 0x80, 0x00, 0x00, 0xF9, 0xFF, 0xBC, 0x00, 0x01, 0xF9, 0xFF, 0xBE, 0x00, 0x01, 0xF9,
0xFF, 0xBC, 0x80, 0x00, 0xF9, 0x81, 0x9C, 0x30, 0x00, 0x73, 0x3E, 0xC0, 0x00, 0x01, 0x07, 0x00,
0xFC, 0x00, 0x03, 0x27, 0x00, 0xFC, 0x00, 0x06, 0x21, 0xBD, 0xF0, 0x00, 0x04, 0x2D, 0x81, 0xE6,
0x00, 0x10, 0x2D, 0xBD, 0xEF, 0x4C, 0x30, 0x21, 0xBD, 0xEF, 0x08, 0x00, 0x1F, 0x8D, 0xE6, 0x00,
0x00, 0x1F, 0xBD, 0xF0, 0x00, 0x00, 0x0F, 0x8D, 0xF2, 0x00, 0x00, 0x07, 0x8D, 0xE2, 0x00, 0x00,
0x03, 0xBD, 0xC0, 0x00, 0x00, 0xF8, 0x8D, 0x18, 0x00, 0x03, 0xDE, 0x3C, 0x7B, 0x40, 0x1F, 0xDF,
0xBD, 0xFC, 0x70, 0x7C, 0xFD, 0xBD, 0xBF, 0xFE, 0x1F, 0xFF, 0x9D, 0xBF, 0xF8, 0xC7, 0xDF, 0xC3,
0xEC, 0xE3, 0x71, 0xFF, 0xE7, 0xEF, 0x8F, 0x7C, 0x7C, 0xFE, 0x7E, 0x3F, 0x7F, 0x1F, 0xFF, 0xF8,
0xF7, 0x4F, 0xC3, 0xE7, 0xC3, 0xF7, 0x7F, 0xB8, 0xFF, 0x1F, 0xFE, 0x1F, 0xBE, 0x3C, 0x7C, 0xFC,
0x07, 0xFF, 0x81, 0xFF, 0xE0, 0x01, 0xF7, 0xE7, 0xA7, 0x80, 0x00, 0x77, 0xFF, 0xBE, 0x00, 0x00,
0x1F, 0xBF, 0xF8, 0x00, 0x00, 0x07, 0xBB, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};
static const unsigned char PROGMEM toxic [] = {
0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x06, 0x00, 0x60, 0x00, 0x00,
0x0C, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x00, 0x30,
0x00, 0x0C, 0x00, 0x00, 0x70, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00,
0x0E, 0x00, 0x00, 0x70, 0x7E, 0x0E, 0x00, 0x00, 0x71, 0xFF, 0x8E, 0x00, 0x00, 0x73, 0xFF, 0xCE,
0x00, 0x00, 0x7B, 0x81, 0xDE, 0x00, 0x00, 0xFD, 0x00, 0xBF, 0x00, 0x01, 0xFC, 0x00, 0x3F, 0x80,
0x07, 0xFF, 0x00, 0xFF, 0xE0, 0x0F, 0xFF, 0xC3, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x1E,
0x07, 0xE7, 0xE0, 0x78, 0x18, 0x31, 0xC3, 0x8C, 0x18, 0x30, 0x30, 0xC3, 0x0C, 0x0C, 0x30, 0x30,
0x00, 0x0C, 0x0C, 0x20, 0x30, 0x24, 0x0C, 0x04, 0x60, 0x38, 0x3C, 0x1C, 0x06, 0x40, 0x38, 0x3C,
0x1C, 0x02, 0x40, 0x1C, 0x3C, 0x38, 0x02, 0x40, 0x1C, 0x3C, 0x38, 0x02, 0x40, 0x0F, 0x3C, 0xF0,
0x02, 0x00, 0x07, 0xBD, 0xE0, 0x00, 0x00, 0x03, 0xBD, 0xC0, 0x00, 0x00, 0x01, 0x7E, 0x80, 0x00,
0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x08, 0x01, 0xFF, 0x80, 0x10, 0x04,
0x03, 0xFF, 0xC0, 0x20, 0x03, 0x9F, 0xE7, 0xF9, 0xC0, 0x00, 0xFF, 0x81, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

// Define the fertilizer hazard class (label) names and color codes.
String classes[] = {"Enriched", "Unsafe", "Toxic"};
int color_codes[3][3] = {{0,255,0}, {255,255,0}, {255,0,0}};

// Define the RGB LED pins:
#define redPin     3
#define greenPin   5
#define bluePin    6

// Define the control button pins:
#define button_A   A0
#define button_B   A1
#define button_C   A2

void setup(){
  Serial.begin(115200);

  pinMode(button_A, INPUT_PULLUP);
  pinMode(button_B, INPUT_PULLUP);
  pinMode(button_C, INPUT_PULLUP);
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
  adjustColor(0,0,0);

  // Initialize the SH1106 screen:
  display.begin(SH1106_SWITCHCAPVCC);
  display.display();
  delay(1000);

  // If successful:  
  display.clearDisplay();   
  display.setTextSize(2); 
  display.setTextColor(BLACK, WHITE);
  display.setCursor(0,0);
  display.println("AIoT");
  display.println("Fertilizer");
  display.println("Detector");
  display.display();
  delay(2000);
  adjustColor(255,0,255);
}

void loop(){
  home_screen();

  // If one of the control buttons (A, B, or C) is pressed, transmit the selected fertilizer hazard class (label)
  // to LattePanda 3 Delta via serial communication.
  if(!digitalRead(button_A)){ Serial.println("Label: Enriched"); data_screen(0); delay(2000); }
  if(!digitalRead(button_B)){ Serial.println("Label: Unsafe"); data_screen(1); delay(2000); }
  if(!digitalRead(button_C)){ Serial.println("Label: Toxic"); data_screen(2); delay(2000); }

}

void home_screen(){
  display.clearDisplay();   
  display.drawBitmap((128 - 40), 20, _home, 40, 40, WHITE);
  display.setTextSize(1); 
  display.setTextColor(BLACK, WHITE);
  display.setCursor(10,5);
  display.println(" Select Label: ");
  display.setTextColor(WHITE);
  display.setCursor(10,25);
  display.println("A) Enriched");  
  display.setCursor(10,40);
  display.println("B) Unsafe");  
  display.setCursor(10,55);
  display.println("C) Toxic");  
  display.display();
  delay(100);
}

void data_screen(int i){
  display.clearDisplay(); 
  if(i==0) display.drawBitmap((128 - 40) / 2, 0, enriched, 40, 40, WHITE);
  if(i==1) display.drawBitmap((128 - 40) / 2, 0, unsafe, 40, 40, WHITE);
  if(i==2) display.drawBitmap((128 - 40) / 2, 0, toxic, 40, 40, WHITE);
  // Print:
  int str_x = classes[i].length() * 11;
  display.setTextSize(2); 
  display.setTextColor(WHITE);
  display.setCursor((128 - str_x) / 2, 48);
  display.println(classes[i]);
  display.display();
  adjustColor(color_codes[i][0], color_codes[i][1], color_codes[i][2]);
  delay(4000);
  adjustColor(255,0,255);
}

void adjustColor(int r, int g, int b){
  analogWrite(redPin, (255-r));
  analogWrite(greenPin, (255-g));
  analogWrite(bluePin, (255-b));
}

Credits

Kutluhan Aktar

Kutluhan Aktar

81 projects • 307 followers
AI & Full-Stack Developer | @EdgeImpulse | @Particle | Maker | Independent Researcher

Comments