Kutluhan Aktar
Published © CC BY

IoT AI-driven Food Irradiation Dose Detector w/ Edge Impulse

Collate weight, color, and emitted ionizing radiation of foods to train a NN. Then, run it on Beetle C3 to detect food irradiation doses.

ExpertFull instructions provided4,020

Things used in this project

Hardware components

Beetle ESP32 - C3 (RISC-V Core Development Board)
DFRobot Beetle ESP32 - C3 (RISC-V Core Development Board)
×1
Raspberry Pi 4 Model B
Raspberry Pi 4 Model B
Raspberry Pi 3B+ or 4
×1
Raspberry Pi 3 Model B+
Raspberry Pi 3 Model B+
Raspberry Pi 3B+ or 4
×1
DFRobot Gravity: Geiger Counter Module
×1
Gravity: I2C 1Kg Weight Sensor Kit - HX711
DFRobot Gravity: I2C 1Kg Weight Sensor Kit - HX711
×1
DFRobot Fermion: 1.51” OLED Transparent Display
×1
DFRobot Gravity: AS7341 11-Channel Visible Light Sensor
×1
Creality CR-200B 3D Printer
×1
SparkFun Button (6x6)
×3
Xiaomi 20000 mAh 3 Pro Type-C Power Bank
×1
USB Buck-Boost Converter Board
×1
Breadboard (generic)
Breadboard (generic)
×1
Mini Breadboard
×1
Jumper wires (generic)
Jumper wires (generic)
×1

Software apps and online services

Edge Impulse Studio
Edge Impulse Studio
Arduino IDE
Arduino IDE
Fusion 360
Autodesk Fusion 360
Ultimaker Cura
Visual Studio 2017
Microsoft Visual Studio 2017

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

IoT_Food_Irradiation_Detector_case_v1.stl

IoT_Food_Irradiation_Detector_handle_v1.stl

Edge Impulse Model (Arduino Library)

food_irradiation_data_logger.zip

Schematics

Schematic

Code

IoT_food_irradiation_data_collect.ino

Arduino
         /////////////////////////////////////////////  
        //   IoT AI-driven Food Irradiation Dose   // 
       //        Detector w/ Edge Impulse         //
      //           -----------------             //
     //            (Beetle ESP32-C3)            //           
    //             by Kutluhan Aktar           // 
   //                                         //
  /////////////////////////////////////////////

// 
// Collate weight, color, and emitted ionizing radiation of foods to train a NN. Then, run it on Beetle C3 to detect food irradiation doses.
//
// For more information:
// https://www.theamplituhedron.com/projects/IoT_AI_driven_Food_Irradiation_Dose_Detector_w_Edge_Impulse
//
//
// Connections
// Beetle ESP32-C3 : 
//                                Gravity: Geiger Counter Module
// D5   --------------------------- D
// VCC  --------------------------- +
// GND  --------------------------- -
//                                Gravity: I2C 1Kg Weight Sensor Kit - HX711
// VCC  --------------------------- VCC
// GND  --------------------------- GND
// D9   --------------------------- SCL
// D8   --------------------------- SDA
//                                Fermion: 1.51 SSD1309 OLED Transparent Display
// D4   --------------------------- SCLK
// D6   --------------------------- MOSI
// D7   --------------------------- CS
// D2   --------------------------- RES
// D1   --------------------------- DC
//                                AS7341 11-Channel Spectral Color Sensor
// VCC  --------------------------- +
// GND  --------------------------- -
// D9   --------------------------- C
// D8   --------------------------- D
//                                Control Button (A)
// D0   --------------------------- +
//                                Control Button (B)
// D20  --------------------------- +
//                                Control Button (C)
// D21  --------------------------- +


// Include the required libraries:
#include <WiFi.h>
#include <DFRobot_Geiger.h>
#include <DFRobot_HX711_I2C.h>
#include <U8g2lib.h>
#include <SPI.h>
#include "DFRobot_AS7341.h"

char ssid[] = "<_SSID_>";        // your network SSID (name)
char pass[] = "<_PASSWORD_>";    // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;                // your network key Index number (needed only for WEP)

// Define the server (Raspberry Pi).
char server[] = "192.168.1.20";
// Define the web application path.
String application = "/food_irradiation_data_logger/get_data.php";

// Initialize the WiFi client library.
WiFiClient client; /* WiFiSSLClient client; */

// Define the Geiger counter module.
DFRobot_Geiger geiger(5);

// Define the HX711 weight sensor.
DFRobot_HX711_I2C MyScale;

// Define the AS7341 object.
DFRobot_AS7341 as7341;
// Define AS7341 data objects:
DFRobot_AS7341::sModeOneData_t data1;
DFRobot_AS7341::sModeTwoData_t data2;

// Define the 1.51 OLED transparent display (SSD1309).
#define OLED_DC  1
#define OLED_CS  7
#define OLED_RST 2

U8G2_SSD1309_128X64_NONAME2_1_4W_HW_SPI u8g2(/* rotation=*/U8G2_R0, /* cs=*/ OLED_CS, /* dc=*/ OLED_DC,/* reset=*/OLED_RST);

// Define monochrome graphics:
static const unsigned char error_bits[] U8X8_PROGMEM = {
   0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0xfc,
   0xff, 0x3f, 0x00, 0x00, 0xfe, 0xff, 0x7f, 0x00, 0x80, 0xff, 0xff, 0xff,
   0x01, 0xc0, 0xff, 0x81, 0xff, 0x03, 0xe0, 0xff, 0x00, 0xff, 0x07, 0xf0,
   0xff, 0x00, 0xff, 0x0f, 0xf0, 0x7f, 0x00, 0xfe, 0x0f, 0xf8, 0x7f, 0x00,
   0xfe, 0x1f, 0xfc, 0x7f, 0x00, 0xfe, 0x3f, 0xfc, 0xff, 0x00, 0xff, 0x3f,
   0xfe, 0xff, 0x00, 0xff, 0x7f, 0xfe, 0xff, 0x00, 0xff, 0x7f, 0xfe, 0xff,
   0x00, 0xff, 0x7f, 0xfe, 0xff, 0x00, 0xff, 0x7f, 0xff, 0xff, 0x00, 0xff,
   0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
   0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0x81,
   0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff,
   0xfe, 0xff, 0xc3, 0xff, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0xff,
   0xff, 0xff, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0x7f, 0xfc, 0xff, 0xc3, 0xff,
   0x3f, 0xfc, 0xff, 0x81, 0xff, 0x3f, 0xf8, 0xff, 0x81, 0xff, 0x1f, 0xf0,
   0xff, 0x81, 0xff, 0x0f, 0xf0, 0xff, 0x81, 0xff, 0x0f, 0xe0, 0xff, 0xc3,
   0xff, 0x07, 0xc0, 0xff, 0xff, 0xff, 0x03, 0x80, 0xff, 0xff, 0xff, 0x01,
   0x00, 0xfe, 0xff, 0x7f, 0x00, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0x00, 0xf0,
   0xff, 0x0f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00
};
static const unsigned char data_colllect_bits[] U8X8_PROGMEM = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x0f, 0xf0, 0x3f, 0x00,
   0x1f, 0x00, 0x70, 0x1c, 0x30, 0x30, 0x80, 0x39, 0x00, 0x18, 0x30, 0x10,
   0x20, 0xc0, 0x20, 0x00, 0x8c, 0x63, 0xf0, 0x3f, 0x40, 0x60, 0x00, 0xc6,
   0xc6, 0x10, 0x20, 0x70, 0xf0, 0x00, 0x46, 0xcc, 0x10, 0x20, 0x38, 0x98,
   0x01, 0x42, 0x8c, 0xf0, 0x3f, 0x18, 0x08, 0x01, 0xc2, 0x86, 0xf0, 0x3f,
   0x18, 0x80, 0x01, 0xe2, 0x8f, 0x10, 0x20, 0xf0, 0xff, 0x01, 0x76, 0x9c,
   0xf0, 0x3f, 0xe0, 0xff, 0x00, 0x1e, 0xf0, 0xe0, 0x1f, 0xe0, 0x00, 0x00,
   0x1c, 0x60, 0x00, 0x03, 0x70, 0x00, 0x00, 0x18, 0x70, 0x00, 0x03, 0x38,
   0x00, 0x00, 0x70, 0xf8, 0x00, 0x03, 0x1c, 0x00, 0x00, 0xe0, 0xcf, 0xe1,
   0x1f, 0x0e, 0x00, 0x00, 0x00, 0x80, 0xfb, 0x7f, 0x07, 0x00, 0x00, 0x00,
   0x00, 0xdf, 0xec, 0x03, 0x00, 0x00, 0x00, 0x00, 0x66, 0x98, 0x01, 0x00,
   0x00, 0x00, 0x00, 0x67, 0x98, 0x03, 0x00, 0x00, 0xfe, 0x07, 0xff, 0xff,
   0x83, 0xff, 0x01, 0x06, 0x8c, 0x21, 0x10, 0xc6, 0x80, 0x01, 0x06, 0x8c,
   0x31, 0x30, 0xc6, 0x80, 0x01, 0xfe, 0x8f, 0x31, 0x30, 0xc6, 0xff, 0x01,
   0x06, 0xfc, 0xff, 0xff, 0xff, 0x80, 0x01, 0x06, 0xfc, 0xff, 0xff, 0xff,
   0x80, 0x01, 0xfe, 0x8f, 0x31, 0x30, 0xc6, 0xff, 0x01, 0x06, 0x8c, 0x31,
   0x30, 0xc6, 0x80, 0x01, 0x06, 0x8c, 0x21, 0x10, 0xc6, 0x80, 0x01, 0xfe,
   0x07, 0xff, 0xff, 0x83, 0xff, 0x01, 0x00, 0x00, 0x67, 0x98, 0x03, 0x00,
   0x00, 0x00, 0x00, 0x66, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0xdf, 0xec,
   0x03, 0x00, 0x00, 0x00, 0x80, 0xfb, 0x7f, 0x07, 0x00, 0x00, 0x00, 0xc0,
   0xe1, 0x1f, 0x0e, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x03, 0x1c, 0x00, 0x00,
   0x00, 0x70, 0x00, 0x03, 0x38, 0x00, 0x00, 0x00, 0x20, 0x00, 0x03, 0xfc,
   0xff, 0x01, 0xe0, 0x01, 0xe0, 0x1f, 0x0c, 0x80, 0x01, 0xf8, 0x07, 0xf0,
   0x3f, 0x04, 0x00, 0x01, 0x0e, 0x1c, 0x10, 0x20, 0x24, 0x00, 0x01, 0xc4,
   0x00, 0xf0, 0x3f, 0x24, 0x08, 0x01, 0xf0, 0x03, 0xf0, 0x3f, 0x24, 0x18,
   0x01, 0x10, 0x02, 0x10, 0x20, 0x24, 0x19, 0x01, 0x00, 0x00, 0x10, 0x20,
   0xe4, 0x1b, 0x01, 0xe0, 0x01, 0xf0, 0x3f, 0xe4, 0x3f, 0x01, 0x20, 0x01,
   0x10, 0x20, 0x04, 0x00, 0x01, 0xe0, 0x01, 0x30, 0x30, 0x0c, 0x80, 0x01,
   0xe0, 0x01, 0xf0, 0x3f, 0xfc, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00
};

// Define the control button pins:
#define button_A 0
#define button_B 20
#define button_C 21

// Define the data holders:
float weight;

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

  pinMode(button_A, INPUT_PULLUP);
  pinMode(button_B, INPUT_PULLUP);
  pinMode(button_C, INPUT_PULLUP);

  // Initialize the SSD1309 transparent display.
  u8g2.begin();
  u8g2.setFontPosTop();
  //u8g2.setDrawColor(0);
  
  // Check the connection status between the weight (HX711) sensor and the Beetle ESP32-C3.
  while (!MyScale.begin()) {
    Serial.println("HX711 initialization is failed!");
    err_msg();
    delay(1000);
  }
  Serial.println("HX711 initialization is successful!");
  
  // Set the calibration weight (g) to calibrate the weight sensor automatically.
  MyScale.setCalWeight(100);
  // Set the calibration threshold (g).
  MyScale.setThreshold(30);
  // Display the current calibration value. 
  Serial.print("\nCalibration Value: "); Serial.println(MyScale.getCalibration());
  MyScale.setCalibration(MyScale.getCalibration());
  delay(1000);

  // Check the connection status between the AS7341 visible light sensor and the Beetle ESP32-C3.
  while (as7341.begin() != 0) {
    Serial.println("AS7341 initialization is failed!");
    err_msg();
    delay(1000);
  }
  Serial.println("AS7341 initialization is successful!");

  // Enable the built-in LED on the AS7341 sensor.
  as7341.enableLed(true);

  // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
  WiFi.begin(ssid, pass);
  // Attempt to connect to the WiFi network:
  while(WiFi.status() != WL_CONNECTED){
    // Wait for the connection:
    delay(500);
    Serial.print(".");
  }
  // If connected to the network successfully:
  Serial.println("Connected to the WiFi network successfully!");
  u8g2.firstPage();  
  do{
    u8g2.setFont(u8g2_font_open_iconic_all_8x_t);
    u8g2.drawGlyph(/* x=*/32, /* y=*/0, /* encoding=*/247);  
  }while(u8g2.nextPage());
  delay(2000);
}

void loop() {
  get_Weight();
  get_Visual_Light();
  activate_Geiger_counter();

  // Show the collected data on the screen.
  home_screen(8, 90, 20);

  // Transmit the collected data to the PHP web application with the selected irradiation dose class:
  if(!digitalRead(button_A)) make_a_get_request("0");
  if(!digitalRead(button_B)) make_a_get_request("1");
  if(!digitalRead(button_C)) make_a_get_request("2");
}

void make_a_get_request(String _class){
  // Connect to the web application named food_irradiation_data_logger. Change '80' with '443' if you are using SSL connection.
  if (client.connect(server, 80)){
    // If successful:
    Serial.println("\nConnected to the web application successfully!");
    // Create the query string:
    String query = application+"?weight="+String(weight)+"&F1="+data1.ADF1+"&F2="+data1.ADF2+"&F3="+data1.ADF3+"&F4="+data1.ADF4+"&F5="+data2.ADF5+"&F6="+data2.ADF6+"&F7="+data2.ADF7+"&F8="+data2.ADF8;
    query += "&CPM="+String(geiger.getCPM())+"&nSv="+String(geiger.getnSvh())+"&uSv="+String(geiger.getuSvh());
    query += "&class="+_class;
    // Make an HTTP Get request:
    client.println("GET " + query + " HTTP/1.1");
    client.println("Host: 192.168.1.20");
    client.println("Connection: close");
    client.println();
  }else{
    Serial.println("\nConnection failed to the web application!");
    err_msg();
  }
  delay(2000); // Wait 2 seconds after connecting...
  // If there are incoming bytes available, get the response from the web application.
  String response = "";
  while (client.available()) { char c = client.read(); response += c; }
  if(response != "" && response.indexOf("Data received and saved successfully!") > 0){
    Serial.println("Data registered successfully!");
    u8g2.firstPage();  
    do{
      //u8g2.setBitmapMode(true /* transparent*/);
      u8g2.drawXBMP( /* x=*/36 , /* y=*/0 , /* width=*/50 , /* height=*/50 , data_colllect_bits);
      u8g2.setFont(u8g2_font_4x6_tr);
      u8g2.drawStr(6, 55, "Data registered successfully!");
    }while(u8g2.nextPage());
  }
}

void home_screen(int y, int x, int s){
  u8g2.firstPage();  
  do{
    u8g2.setFont(u8g2_font_open_iconic_all_2x_t);
    u8g2.drawGlyph(/* x=*/0, /* y=*/y-3, /* encoding=*/142);
    u8g2.drawGlyph(/* x=*/0, /* y=*/y+s-3, /* encoding=*/259);
    u8g2.drawGlyph(/* x=*/0, /* y=*/y+(2*s)-3, /* encoding=*/280);
    u8g2.setFont(u8g2_font_freedoomr10_mu);
    u8g2.drawStr(25, y, "WEIGHT:"); drawNumber(x, y, weight);
    u8g2.drawStr(25, y+s, "F1:"); drawNumber(x, y+s, data1.ADF1);
    u8g2.drawStr(25, y+(2*s), "CPM:"); drawNumber(x, y+(2*s), geiger.getCPM());
  }while(u8g2.nextPage());
}

void activate_Geiger_counter(){
  // Initialize the Geiger counter module and enable the external interrupt.
  geiger.start();
  delay(3000);
  // If necessary, pause the count and turn off the external interrupt trigger.
  geiger.pause();
  
  // Evaluate the current CPM (Counts per Minute) by dropping the edge pulse within 3 seconds: the error is 3CPM.
  Serial.print("\nCPM: "); Serial.println(geiger.getCPM());
  // Get the current nSv/h (nanoSieverts per hour).
  Serial.print("nSv/h: "); Serial.println(geiger.getnSvh());
  // Get the current Sv/h (microSieverts per hour).
  Serial.print("Sv/h: "); Serial.println(geiger.getuSvh());
}

void get_Weight(){
  weight = MyScale.readWeight();
  if(weight < 0.5) weight = 0;
  Serial.print("\nWeight: "); Serial.print(weight); Serial.println(" g");
  delay(1000);
}

void get_Visual_Light(){
  // Start spectrum measurement:
  // Channel mapping mode: 1.eF1F4ClearNIR
  as7341.startMeasure(as7341.eF1F4ClearNIR);
  // Read the value of sensor data channel 0~5, under eF1F4ClearNIR
  data1 = as7341.readSpectralDataOne();
  // Channel mapping mode: 2.eF5F8ClearNIR
  as7341.startMeasure(as7341.eF5F8ClearNIR);
  // Read the value of sensor data channel 0~5, under eF5F8ClearNIR
  data2 = as7341.readSpectralDataTwo();
  // Print data:
  Serial.print("\nF1(405-425nm): "); Serial.println(data1.ADF1);
  Serial.print("F2(435-455nm): "); Serial.println(data1.ADF2);
  Serial.print("F3(470-490nm): "); Serial.println(data1.ADF3);
  Serial.print("F4(505-525nm): "); Serial.println(data1.ADF4);
  Serial.print("F5(545-565nm): "); Serial.println(data2.ADF5);
  Serial.print("F6(580-600nm): "); Serial.println(data2.ADF6);
  Serial.print("F7(620-640nm): "); Serial.println(data2.ADF7);
  Serial.print("F8(670-690nm): "); Serial.println(data2.ADF8);
  // CLEAR and NIR:
  Serial.print("Clear_1: "); Serial.println(data1.ADCLEAR);
  Serial.print("NIR_1: "); Serial.println(data1.ADNIR);
  Serial.print("Clear_2: "); Serial.println(data2.ADCLEAR);
  Serial.print("NIR_2: "); Serial.println(data2.ADNIR);
  delay(1000);
}

void err_msg(){
  // Show the error message on the SSD1309 transparent display.
  u8g2.firstPage();  
  do{
    //u8g2.setBitmapMode(true /* transparent*/);
    u8g2.drawXBMP( /* x=*/44 , /* y=*/0 , /* width=*/40 , /* height=*/40 , error_bits);
    u8g2.setFont(u8g2_font_4x6_tr);
    u8g2.drawStr(0, 47, "Check the serial monitor to see");
    u8g2.drawStr(40, 55, "the error!");
  }while(u8g2.nextPage());
}

void drawNumber(int x, int y, int __){
    char buf[7];
    u8g2.drawStr(x, y, itoa(__, buf, 10));
}

IoT_food_irradiation_run_model.ino

Arduino
         /////////////////////////////////////////////  
        //   IoT AI-driven Food Irradiation Dose   // 
       //        Detector w/ Edge Impulse         //
      //           -----------------             //
     //            (Beetle ESP32-C3)            //           
    //             by Kutluhan Aktar           // 
   //                                         //
  /////////////////////////////////////////////

// 
// Collate weight, color, and emitted ionizing radiation of foods to train a NN. Then, run it on Beetle C3 to detect food irradiation doses.
//
// For more information:
// https://www.theamplituhedron.com/projects/IoT_AI_driven_Food_Irradiation_Dose_Detector_w_Edge_Impulse
//
//
// Connections
// Beetle ESP32-C3 : 
//                                Gravity: Geiger Counter Module
// D5   --------------------------- D
// VCC  --------------------------- +
// GND  --------------------------- -
//                                Gravity: I2C 1Kg Weight Sensor Kit - HX711
// VCC  --------------------------- VCC
// GND  --------------------------- GND
// D9   --------------------------- SCL
// D8   --------------------------- SDA
//                                Fermion: 1.51 SSD1309 OLED Transparent Display
// D4   --------------------------- SCLK
// D6   --------------------------- MOSI
// D7   --------------------------- CS
// D2   --------------------------- RES
// D1   --------------------------- DC
//                                AS7341 11-Channel Spectral Color Sensor
// VCC  --------------------------- +
// GND  --------------------------- -
// D9   --------------------------- C
// D8   --------------------------- D
//                                Control Button (A)
// D0   --------------------------- +
//                                Control Button (B)
// D20  --------------------------- +
//                                Control Button (C)
// D21  --------------------------- +


// Include the required libraries:
#include <DFRobot_Geiger.h>
#include <DFRobot_HX711_I2C.h>
#include <U8g2lib.h>
#include <SPI.h>
#include "DFRobot_AS7341.h"

// Include the Edge Impulse model converted to an Arduino library:
#include <IoT_AI-driven_Food_Irradiation_Classifier_inferencing.h>

// Define the required parameters to run an inference with the Edge Impulse model.
#define FREQUENCY_HZ        EI_CLASSIFIER_FREQUENCY
#define INTERVAL_MS         (1000 / (FREQUENCY_HZ + 1))

// Define the features array to classify one frame of data.
float features[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];
size_t feature_ix = 0;

// Define the threshold value for the model outputs (predictions).
float threshold = 0.60;

// Define the food irradiation dose (class) names:
String classes[] = {"Hazardous", "Regulated", "Unsafe"};

// Define the Geiger counter module.
DFRobot_Geiger geiger(5);

// Define the HX711 weight sensor.
DFRobot_HX711_I2C MyScale;

// Define the AS7341 object.
DFRobot_AS7341 as7341;
// Define AS7341 data objects:
DFRobot_AS7341::sModeOneData_t data1;
DFRobot_AS7341::sModeTwoData_t data2;

// Define the 1.51 OLED transparent display (SSD1309).
#define OLED_DC  1
#define OLED_CS  7
#define OLED_RST 2

U8G2_SSD1309_128X64_NONAME2_1_4W_HW_SPI u8g2(/* rotation=*/U8G2_R0, /* cs=*/ OLED_CS, /* dc=*/ OLED_DC,/* reset=*/OLED_RST);

// Define monochrome graphics:
static const unsigned char error_bits[] U8X8_PROGMEM = {
   0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0xfc,
   0xff, 0x3f, 0x00, 0x00, 0xfe, 0xff, 0x7f, 0x00, 0x80, 0xff, 0xff, 0xff,
   0x01, 0xc0, 0xff, 0x81, 0xff, 0x03, 0xe0, 0xff, 0x00, 0xff, 0x07, 0xf0,
   0xff, 0x00, 0xff, 0x0f, 0xf0, 0x7f, 0x00, 0xfe, 0x0f, 0xf8, 0x7f, 0x00,
   0xfe, 0x1f, 0xfc, 0x7f, 0x00, 0xfe, 0x3f, 0xfc, 0xff, 0x00, 0xff, 0x3f,
   0xfe, 0xff, 0x00, 0xff, 0x7f, 0xfe, 0xff, 0x00, 0xff, 0x7f, 0xfe, 0xff,
   0x00, 0xff, 0x7f, 0xfe, 0xff, 0x00, 0xff, 0x7f, 0xff, 0xff, 0x00, 0xff,
   0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
   0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0x81,
   0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff,
   0xfe, 0xff, 0xc3, 0xff, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0xff,
   0xff, 0xff, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0x7f, 0xfc, 0xff, 0xc3, 0xff,
   0x3f, 0xfc, 0xff, 0x81, 0xff, 0x3f, 0xf8, 0xff, 0x81, 0xff, 0x1f, 0xf0,
   0xff, 0x81, 0xff, 0x0f, 0xf0, 0xff, 0x81, 0xff, 0x0f, 0xe0, 0xff, 0xc3,
   0xff, 0x07, 0xc0, 0xff, 0xff, 0xff, 0x03, 0x80, 0xff, 0xff, 0xff, 0x01,
   0x00, 0xfe, 0xff, 0x7f, 0x00, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0x00, 0xf0,
   0xff, 0x0f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00
};
static const unsigned char regulated_bits[] U8X8_PROGMEM = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x80, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00,
   0x34, 0x00, 0x30, 0x84, 0x03, 0x00, 0x00, 0x62, 0x12, 0x50, 0x44, 0x02,
   0x00, 0x00, 0x42, 0x36, 0x28, 0x88, 0x03, 0x00, 0x00, 0x8a, 0x36, 0x28,
   0x08, 0x00, 0x00, 0x00, 0x8a, 0x3e, 0x14, 0x08, 0x00, 0x00, 0x00, 0x92,
   0x3e, 0x14, 0x08, 0x30, 0x00, 0x00, 0xd4, 0x7c, 0x14, 0x08, 0x2f, 0x00,
   0x40, 0x5c, 0x9c, 0x15, 0xcc, 0x20, 0x00, 0xa0, 0x70, 0x04, 0x09, 0x74,
   0x42, 0x00, 0xa0, 0x20, 0x02, 0x0a, 0x5a, 0x42, 0x00, 0xe0, 0x40, 0x02,
   0x92, 0x89, 0x41, 0x00, 0x00, 0x40, 0x8c, 0x53, 0x04, 0x43, 0x00, 0x00,
   0x8e, 0x04, 0x72, 0x14, 0x4c, 0x00, 0x00, 0x19, 0x08, 0x02, 0x12, 0x70,
   0x00, 0x80, 0xf0, 0x38, 0x02, 0x12, 0x40, 0x00, 0xe0, 0x00, 0x11, 0x72,
   0x0a, 0x40, 0x00, 0x10, 0x00, 0xb1, 0x52, 0x0a, 0x20, 0x00, 0x08, 0x80,
   0x60, 0x52, 0x0a, 0x20, 0x00, 0x48, 0x40, 0x40, 0x22, 0x02, 0x10, 0x00,
   0xd0, 0x43, 0x5c, 0x02, 0x02, 0x08, 0x00, 0x10, 0x40, 0x96, 0x02, 0x03,
   0x06, 0x00, 0x10, 0x92, 0x94, 0x83, 0x81, 0x01, 0x00, 0x10, 0xb7, 0x08,
   0xe3, 0xe0, 0x00, 0x00, 0xe0, 0xa5, 0x00, 0xc0, 0x37, 0x00, 0x00, 0x00,
   0xff, 0x01, 0x80, 0x3f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x03,
   0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00,
   0x00, 0x02, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0x11,
   0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00,
   0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x46, 0x00, 0x00, 0x80,
   0x01, 0x00, 0x00, 0x84, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x08, 0x03,
   0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00,
   0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x0c, 0x00,
   0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00
};
static const unsigned char unsafe_bits[] U8X8_PROGMEM = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f, 0x00,
   0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0xc0, 0xff,
   0xff, 0x0f, 0x00, 0x00, 0x00, 0xf0, 0x1f, 0xe0, 0x3f, 0x00, 0x00, 0x00,
   0xf8, 0x01, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0xf8, 0x01,
   0x00, 0x00, 0x3f, 0x00, 0x01, 0xf0, 0x03, 0x00, 0x00, 0x0f, 0x80, 0x01,
   0xc0, 0x03, 0x00, 0xc0, 0x07, 0x80, 0x00, 0xc0, 0x07, 0x00, 0xc0, 0x03,
   0x80, 0x00, 0xe0, 0x0f, 0x00, 0xe0, 0x01, 0x40, 0x00, 0xf0, 0x1f, 0x00,
   0xe0, 0x00, 0x00, 0x00, 0x78, 0x3c, 0x00, 0xf0, 0x58, 0x6b, 0x03, 0x38,
   0x3c, 0x00, 0x78, 0xfc, 0xff, 0x07, 0x1e, 0x78, 0x00, 0x78, 0xfc, 0xff,
   0x07, 0x0f, 0x70, 0x00, 0x38, 0x00, 0x00, 0x80, 0x07, 0xf0, 0x00, 0x3c,
   0x74, 0x77, 0xc7, 0x03, 0xf0, 0x00, 0x1c, 0xfc, 0xff, 0xe7, 0x01, 0xe0,
   0x00, 0x1c, 0xfc, 0xff, 0xf7, 0x00, 0xe0, 0x00, 0x1c, 0xf8, 0xff, 0x7b,
   0x00, 0xe0, 0x01, 0x1e, 0xf8, 0xff, 0x38, 0x00, 0xc0, 0x01, 0x0e, 0xf8,
   0xff, 0x1e, 0x00, 0xc0, 0x01, 0x0e, 0xf8, 0x3f, 0xdf, 0x01, 0xc0, 0x01,
   0x1e, 0xf8, 0xbf, 0x07, 0x0c, 0xc0, 0x01, 0x0e, 0xf0, 0xcf, 0x23, 0x21,
   0xc0, 0x01, 0x1e, 0xf0, 0xef, 0x01, 0x40, 0xc0, 0x01, 0x1e, 0xf0, 0xf3,
   0x90, 0x94, 0xe0, 0x01, 0x1c, 0xf0, 0x7b, 0x02, 0x00, 0xc0, 0x01, 0x1c,
   0xf0, 0x7c, 0x02, 0x80, 0xe0, 0x00, 0x1c, 0xe0, 0x9e, 0xff, 0xff, 0xe0,
   0x00, 0x3c, 0x20, 0xdf, 0xff, 0xff, 0xe0, 0x00, 0x38, 0xa0, 0xe7, 0xff,
   0xff, 0xf0, 0x00, 0x38, 0xc0, 0x47, 0x55, 0x95, 0x70, 0x00, 0x78, 0xe0,
   0x01, 0x00, 0x00, 0x78, 0x00, 0x70, 0xe0, 0x04, 0x00, 0x80, 0x38, 0x00,
   0xf0, 0x78, 0x28, 0x49, 0x52, 0x3c, 0x00, 0xe0, 0x3d, 0x00, 0x00, 0x00,
   0x1e, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xc0, 0x0f, 0x00,
   0x00, 0x80, 0x0f, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00,
   0x1f, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xf0, 0x01,
   0x00, 0x00, 0xfc, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x80,
   0x3f, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xfd, 0x1f, 0x00, 0x00, 0x00, 0x80,
   0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00
};
static const unsigned char hazardous_bits[] U8X8_PROGMEM = {
   0x00, 0x00, 0xa0, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xff, 0x02,
   0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0xe0, 0x17,
   0xa0, 0x1f, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x7c, 0x00, 0x00, 0x00,
   0x7c, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0xe0, 0x03,
   0x00, 0x80, 0x07, 0x00, 0x01, 0x80, 0x03, 0x00, 0xc0, 0x03, 0xe0, 0x1f,
   0x00, 0x07, 0x00, 0xc0, 0x01, 0xf0, 0x3f, 0x00, 0x0e, 0x00, 0xe0, 0x00,
   0xfc, 0xff, 0x00, 0x1c, 0x00, 0x70, 0x00, 0xfe, 0xff, 0x01, 0x38, 0x00,
   0x70, 0x00, 0xff, 0xff, 0x03, 0x38, 0x00, 0x38, 0x00, 0xff, 0xff, 0x03,
   0x70, 0x00, 0x18, 0x00, 0xff, 0xff, 0x03, 0x60, 0x00, 0x1c, 0x80, 0xff,
   0xff, 0x07, 0xe0, 0x00, 0x1c, 0x80, 0xff, 0xff, 0x07, 0xc0, 0x00, 0x0c,
   0x80, 0xff, 0xff, 0x07, 0xc0, 0x00, 0x0e, 0x80, 0x03, 0x83, 0x07, 0xc0,
   0x01, 0x0e, 0x80, 0x01, 0x01, 0x07, 0xc0, 0x01, 0x0e, 0x80, 0x01, 0x03,
   0x06, 0x80, 0x01, 0x06, 0x00, 0x03, 0x03, 0x03, 0x80, 0x03, 0x06, 0x00,
   0x83, 0x07, 0x03, 0x80, 0x01, 0x07, 0x00, 0xd6, 0xef, 0x01, 0x80, 0x03,
   0x07, 0x00, 0xfc, 0xff, 0x00, 0x80, 0x03, 0x07, 0x10, 0x78, 0x38, 0x00,
   0x80, 0x03, 0x06, 0x30, 0x70, 0x3b, 0x70, 0x80, 0x03, 0x06, 0x78, 0xf0,
   0x3f, 0x70, 0x80, 0x01, 0x06, 0x78, 0xd0, 0x2f, 0xf8, 0x80, 0x03, 0x0e,
   0xfc, 0xc1, 0x0f, 0xfc, 0xc0, 0x01, 0x06, 0xfc, 0x43, 0x0b, 0xff, 0x80,
   0x01, 0x0e, 0x80, 0x0f, 0xc0, 0x03, 0xc0, 0x01, 0x0c, 0x00, 0x1e, 0xe0,
   0x01, 0xc0, 0x01, 0x0e, 0x00, 0x7c, 0xfc, 0x00, 0xc0, 0x00, 0x1c, 0x00,
   0xf0, 0x3f, 0x00, 0xe0, 0x00, 0x18, 0x00, 0xc0, 0x0f, 0x00, 0x60, 0x00,
   0x38, 0x00, 0xc0, 0x1f, 0x00, 0x70, 0x00, 0x78, 0x00, 0xfe, 0xff, 0x01,
   0x38, 0x00, 0x70, 0x00, 0x7e, 0xf8, 0x01, 0x38, 0x00, 0xe0, 0x01, 0x1e,
   0xf0, 0x01, 0x1c, 0x00, 0xc0, 0x03, 0x1c, 0xe0, 0x00, 0x0f, 0x00, 0xc0,
   0x07, 0x1c, 0xc0, 0x00, 0x07, 0x00, 0x80, 0x07, 0x00, 0x00, 0xc0, 0x03,
   0x00, 0x00, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x7c, 0x00, 0x00,
   0xf0, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x7e, 0x00, 0x00, 0x00, 0xe0,
   0x0f, 0xa0, 0x1f, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x07, 0x00, 0x00,
   0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x27, 0x00,
   0x00, 0x00
};

// Create an array including icons for labels (classes).
static const unsigned char *class_icons[] U8X8_PROGMEM = {hazardous_bits, regulated_bits, unsafe_bits}; 

// Define the control button pins:
#define button_A 0
#define button_B 20
#define button_C 21

// Define the data holders:
float weight;
volatile boolean model_activation = false; 
int predicted_class = -1;

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

  pinMode(button_B, INPUT_PULLUP);

  // Initialize the SSD1309 transparent display.
  u8g2.begin();
  u8g2.setFontPosTop();
  //u8g2.setDrawColor(0);
  
  // Check the connection status between the weight (HX711) sensor and the Beetle ESP32-C3.
  while (!MyScale.begin()) {
    Serial.println("HX711 initialization is failed!");
    err_msg();
    delay(1000);
  }
  Serial.println("HX711 initialization is successful!");
  
  // Set the calibration weight (g) to calibrate the weight sensor automatically.
  MyScale.setCalWeight(100);
  // Set the calibration threshold (g).
  MyScale.setThreshold(30);
  // Display the current calibration value. 
  Serial.print("\nCalibration Value: "); Serial.println(MyScale.getCalibration());
  MyScale.setCalibration(MyScale.getCalibration());
  delay(1000);

  // Check the connection status between the AS7341 visible light sensor and the Beetle ESP32-C3.
  while (as7341.begin() != 0) {
    Serial.println("AS7341 initialization is failed!");
    err_msg();
    delay(1000);
  }
  Serial.println("AS7341 initialization is successful!");

  // Enable the built-in LED on the AS7341 sensor.
  as7341.enableLed(true);

  delay(1000);
}

void loop() {
  get_Weight();
  get_Visual_Light();
  activate_Geiger_counter();

  // Show the collected data on the screen.
  home_screen(8, 90, 20);

  // Execute the Edge Impulse model to make predictions on the food irradiation doses (classes).
  if(!digitalRead(button_B)){
    model_activation = true;
    u8g2.firstPage();  
    do{
      u8g2.setFont(u8g2_font_open_iconic_all_8x_t);
      u8g2.drawGlyph(/* x=*/32, /* y=*/0, /* encoding=*/233);  
    }while(u8g2.nextPage());
  }
  while(model_activation){
    get_Weight();
    get_Visual_Light();
    activate_Geiger_counter();

    // Run inference:
    run_inference_to_make_predictions(1);

    // If the Edge Impulse model predicted a label (class) successfully:
    if(predicted_class != -1){
      // Display the predicted class:
      String c = "Class: " + classes[predicted_class];
      int str_x = c.length() * 4;
      u8g2.firstPage();  
      do{
        //u8g2.setBitmapMode(true /* transparent*/);
        u8g2.drawXBMP( /* x=*/(u8g2.getDisplayWidth()-50)/2 , /* y=*/0 , /* width=*/50 , /* height=*/50 , class_icons[predicted_class]);
        u8g2.setFont(u8g2_font_4x6_tr);
        u8g2.drawStr((u8g2.getDisplayWidth()-str_x)/2, 55, c.c_str());
      }while(u8g2.nextPage());      
      
      // Clear the predicted class (label).
      predicted_class = -1;

      // Stop the running inference and return to the home screen.
      model_activation = false;
    }
  }
}

void run_inference_to_make_predictions(int multiply){
  // Scale (normalize) data items depending on the given model:
  float scaled_weight = weight / 10;
  float scaled_F1 = data1.ADF1 / 100;
  float scaled_F2 = data1.ADF2 / 100;
  float scaled_F3 = data1.ADF3 / 100;
  float scaled_F4 = data1.ADF4 / 100;
  float scaled_F5 = data2.ADF5 / 100;
  float scaled_F6 = data2.ADF6 / 100;
  float scaled_F7 = data2.ADF7 / 100;
  float scaled_F8 = data2.ADF8 / 100;
  float scaled_CPM = geiger.getCPM() / 100;
  float scaled_nSv = geiger.getnSvh() / 100;
  float scaled_uSv = geiger.getuSvh();
  
  // Copy the scaled data items to the features buffer.
  // If required, multiply the scaled data items while copying them to the features buffer.
  for(int i=0; i<multiply; i++){  
    features[feature_ix++] = scaled_weight;
    features[feature_ix++] = scaled_F1;
    features[feature_ix++] = scaled_F2;
    features[feature_ix++] = scaled_F3;
    features[feature_ix++] = scaled_F4;
    features[feature_ix++] = scaled_F5;
    features[feature_ix++] = scaled_F6;
    features[feature_ix++] = scaled_F7;
    features[feature_ix++] = scaled_F8;
    features[feature_ix++] = scaled_CPM;
    features[feature_ix++] = scaled_nSv;
    features[feature_ix++] = scaled_uSv;
  }

  // Display the progress of copying data to the features buffer.
  Serial.print("\nFeatures Buffer Progress: "); Serial.print(feature_ix); Serial.print(" / "); Serial.println(EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
  
  // Run inference:
  if(feature_ix == EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE){    
    ei_impulse_result_t result;
    // Create a signal object from the features buffer (frame).
    signal_t signal;
    numpy::signal_from_buffer(features, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
    // Run the classifier:
    EI_IMPULSE_ERROR res = run_classifier(&signal, &result, false);
    ei_printf("\nrun_classifier returned: %d\n", res);
    if(res != 0) return;

    // Print the inference timings on the serial monitor.
    ei_printf("Predictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n", 
        result.timing.dsp, result.timing.classification, result.timing.anomaly);

    // Obtain the prediction results for each label (class).
    for(size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++){
      // Print the prediction results on the serial monitor.
      ei_printf("%s:\t%.5f\n", result.classification[ix].label, result.classification[ix].value);
      // Get the predicted label (class).
      if(result.classification[ix].value >= threshold) predicted_class = ix;
    }
    Serial.print("\nPredicted Class: "); Serial.println(predicted_class);

    // Detect anomalies, if any:
    #if EI_CLASSIFIER_HAS_ANOMALY == 1
      ei_printf("Anomaly : \t%.3f\n", result.anomaly);
    #endif

    // Clear the features buffer (frame):
    feature_ix = 0;
  }
}

void home_screen(int y, int x, int s){
  u8g2.firstPage();  
  do{
    u8g2.setFont(u8g2_font_open_iconic_all_2x_t);
    u8g2.drawGlyph(/* x=*/0, /* y=*/y-3, /* encoding=*/142);
    u8g2.drawGlyph(/* x=*/0, /* y=*/y+s-3, /* encoding=*/259);
    u8g2.drawGlyph(/* x=*/0, /* y=*/y+(2*s)-3, /* encoding=*/280);
    u8g2.setFont(u8g2_font_freedoomr10_mu);
    u8g2.drawStr(25, y, "WEIGHT:"); drawNumber(x, y, weight);
    u8g2.drawStr(25, y+s, "F1:"); drawNumber(x, y+s, data1.ADF1);
    u8g2.drawStr(25, y+(2*s), "CPM:"); drawNumber(x, y+(2*s), geiger.getCPM());
  }while(u8g2.nextPage());
}

void activate_Geiger_counter(){
  // Initialize the Geiger counter module and enable the external interrupt.
  geiger.start();
  delay(3000);
  // If necessary, pause the count and turn off the external interrupt trigger.
  geiger.pause();
  
  // Evaluate the current CPM (Counts per Minute) by dropping the edge pulse within 3 seconds: the error is 3CPM.
  Serial.print("\nCPM: "); Serial.println(geiger.getCPM());
  // Get the current nSv/h (nanoSieverts per hour).
  Serial.print("nSv/h: "); Serial.println(geiger.getnSvh());
  // Get the current Sv/h (microSieverts per hour).
  Serial.print("Sv/h: "); Serial.println(geiger.getuSvh());
}

void get_Weight(){
  weight = MyScale.readWeight();
  if(weight < 0.5) weight = 0;
  Serial.print("\nWeight: "); Serial.print(weight); Serial.println(" g");
  delay(1000);
}

void get_Visual_Light(){
  // Start spectrum measurement:
  // Channel mapping mode: 1.eF1F4ClearNIR
  as7341.startMeasure(as7341.eF1F4ClearNIR);
  // Read the value of sensor data channel 0~5, under eF1F4ClearNIR
  data1 = as7341.readSpectralDataOne();
  // Channel mapping mode: 2.eF5F8ClearNIR
  as7341.startMeasure(as7341.eF5F8ClearNIR);
  // Read the value of sensor data channel 0~5, under eF5F8ClearNIR
  data2 = as7341.readSpectralDataTwo();
  // Print data:
  Serial.print("\nF1(405-425nm): "); Serial.println(data1.ADF1);
  Serial.print("F2(435-455nm): "); Serial.println(data1.ADF2);
  Serial.print("F3(470-490nm): "); Serial.println(data1.ADF3);
  Serial.print("F4(505-525nm): "); Serial.println(data1.ADF4);
  Serial.print("F5(545-565nm): "); Serial.println(data2.ADF5);
  Serial.print("F6(580-600nm): "); Serial.println(data2.ADF6);
  Serial.print("F7(620-640nm): "); Serial.println(data2.ADF7);
  Serial.print("F8(670-690nm): "); Serial.println(data2.ADF8);
  // CLEAR and NIR:
  Serial.print("Clear_1: "); Serial.println(data1.ADCLEAR);
  Serial.print("NIR_1: "); Serial.println(data1.ADNIR);
  Serial.print("Clear_2: "); Serial.println(data2.ADCLEAR);
  Serial.print("NIR_2: "); Serial.println(data2.ADNIR);
  delay(1000);
}

void err_msg(){
  // Show the error message on the SSD1309 transparent display.
  u8g2.firstPage();  
  do{
    //u8g2.setBitmapMode(true /* transparent*/);
    u8g2.drawXBMP( /* x=*/44 , /* y=*/0 , /* width=*/40 , /* height=*/40 , error_bits);
    u8g2.setFont(u8g2_font_4x6_tr);
    u8g2.drawStr(0, 47, "Check the serial monitor to see");
    u8g2.drawStr(40, 55, "the error!");
  }while(u8g2.nextPage());
}

void drawNumber(int x, int y, int __){
    char buf[7];
    u8g2.drawStr(x, y, itoa(__, buf, 10));
}

class.php

PHP
<?php

// Define the _main class and its functions:
class _main {
	public $conn, $table;
	
	public function __init__($conn, $table){
		$this->conn = $conn;
		$this->table = $table;
	}
	
    // Database -> Insert Data:
	public function insert_new_data($d1, $d2, $d3, $d4, $d5, $d6, $d7, $d8, $d9, $d10, $d11, $d12, $c){
		$sql = "INSERT INTO `$this->table`(`weight`, `f1`, `f2`, `f3`, `f4`, `f5`, `f6`, `f7`, `f8`, `cpm`, `nsv`, `usv`, `class`) VALUES ('$d1', '$d2', '$d3', '$d4', '$d5', '$d6', '$d7', '$d8', '$d9', '$d10', '$d11', '$d12', '$c')";
		if(mysqli_query($this->conn, $sql)){ return true; }else { return false; }
	}

	// Database -> Create Table
	public function database_create_table(){
		// Create a new database table.
		$sql_create = "CREATE TABLE `$this->table`(		
							id int AUTO_INCREMENT PRIMARY KEY NOT NULL,
							weight varchar(255) NOT NULL,
							f1 varchar(255) NOT NULL,
							f2 varchar(255) NOT NULL,
							f3 varchar(255) NOT NULL,
							f4 varchar(255) NOT NULL,
							f5 varchar(255) NOT NULL,
							f6 varchar(255) NOT NULL,
							f7 varchar(255) NOT NULL,
							f8 varchar(255) NOT NULL,
							cpm varchar(255) NOT NULL,
							nsv varchar(255) NOT NULL,
							usv varchar(255) NOT NULL,
							`class` varchar(255) NOT NULL
					   );";
		if(mysqli_query($this->conn, $sql_create)) echo("<br><br>Database Table Created Successfully!");
	}
}

// Define the sample class and its functions:
class sample Extends _main {
	// Define the irradiation dose class (label) names.
	public $class_names = ["Regulated", "Unsafe", "Hazardous"];
	
	// Count the registered data records (samples) in the given database table. 
	public function count_samples(){
		$count = [
			"total" => mysqli_num_rows(mysqli_query($this->conn, "SELECT * FROM `$this->table`")),
			"regulated" => mysqli_num_rows(mysqli_query($this->conn, "SELECT * FROM `$this->table` WHERE class='0'")),
			"unsafe" => mysqli_num_rows(mysqli_query($this->conn, "SELECT * FROM `$this->table` WHERE class='1'")),
			"hazardous" => mysqli_num_rows(mysqli_query($this->conn, "SELECT * FROM `$this->table` WHERE class='2'")),
		];
		return $count;
	}
	
	// Create a CSV file for each data record (sample) identified with the sample number.
	public function create_sample_files($type){
		// Obtain the registered data records (samples) from the given database table.
		$sql = "SELECT * FROM `$this->table`";
		$result = mysqli_query($this->conn, $sql);
		$check = mysqli_num_rows($result);
		if($check > 0){
			while($row = mysqli_fetch_assoc($result)){
				// Scale (normalize) data items to define appropriately formatted inputs (samples).
				$scaled = [
					"weight" => $row["weight"] / 10,
					"f1" => $row["f1"] / 100,
					"f2" => $row["f2"] / 100,
					"f3" => $row["f3"] / 100,
					"f4" => $row["f4"] / 100,
					"f5" => $row["f5"] / 100,
					"f6" => $row["f6"] / 100,
					"f7" => $row["f7"] / 100,
					"f8" => $row["f8"] / 100,
					"cpm" => $row["cpm"] / 100,
					"nsv" => $row["nsv"] / 100,
					"usv" => $row["usv"]
				];
				// Add the header as the first row.
				$processed_data = [
					['weight','f1','f2','f3','f4','f5','f6','f7','f8','cpm','nsv','usv'],
					[$scaled["weight"],$scaled["f1"],$scaled["f2"],$scaled["f3"],$scaled["f4"],$scaled["f5"],$scaled["f6"],$scaled["f7"],$scaled["f8"],$scaled["cpm"],$scaled["nsv"],$scaled["usv"]]
				];
				$filename = "data/".$this->class_names[$row["class"]].".".$type.".sample_".$row["id"].".csv";
				$f = fopen($filename, "w");
				foreach($processed_data as $r){
					fputcsv($f, $r);
				}
				fclose($f);
			}
		}
	}
	
	// Download all generated CSV sample files in the ZIP file format.
	public function download_samples($zipname){
		if(count(scandir("data")) > 2){
			$zip = new ZipArchive;
			$zip->open($zipname, ZipArchive::CREATE);
			foreach(glob("data/*.csv") as $sample){
				$zip->addFile($sample);
			}
			$zip->close();

			header('Content-Type: application/zip');
			header("Content-Disposition: attachment; filename='$zipname'");
			header('Content-Length: ' . filesize($zipname));
			header("Location: $zipname");
		}else{
			header("Location: .");
			exit();
		}
	}
}

// Define database and server settings:
$server = array(
	"name" => "localhost",
	"username" => "root",
	"password" => "bot",
	"database" => "foodirradiation",
	"table" => "entries"

);

$conn = mysqli_connect($server["name"], $server["username"], $server["password"], $server["database"]);

?>

get_data.php

PHP
<?php

include_once "assets/class.php";

// Define the new 'food' object:
$food = new _main();
$food->__init__($conn, $server["table"]); 

// Obtain the transferred information from the Beetle ESP32-C3.
// Then, insert the received information into the given database table.
if(isset($_GET["weight"]) && isset($_GET["F1"]) && isset($_GET["F2"]) && isset($_GET["F3"]) && isset($_GET["F4"]) && isset($_GET["F5"]) && isset($_GET["F6"]) && isset($_GET["F7"]) && isset($_GET["F8"]) && isset($_GET["CPM"]) && isset($_GET["nSv"]) && isset($_GET["uSv"]) && isset($_GET["class"])){
	if($food->insert_new_data($_GET["weight"], $_GET["F1"], $_GET["F2"], $_GET["F3"], $_GET["F4"], $_GET["F5"], $_GET["F6"], $_GET["F7"], $_GET["F8"], $_GET["CPM"], $_GET["nSv"], $_GET["uSv"], $_GET["class"])){
		echo("Data received and saved successfully!");
	}else{
		echo("Database error!");
	}
}else{
	echo("Waiting Data...");
}

// If requested, create a new database table.
if(isset($_GET["create_table"]) && $_GET["create_table"] == "OK") $food->database_create_table();

?>

index.php

PHP
<?php
	include_once "assets/class.php";
	
	// Define the new 'sample' object: 
	$sample = new sample();
	$sample->__init__($conn, $server["table"]);
    
    // Elicit the total number of data records (samples) for classes (labels) in the given database table.
    $count = $sample->count_samples();

    // Create a CSV file for each data record (sample) in the given database table.
    if(isset($_POST["data"]) && $_POST["data"] != ""){
		$sample->create_sample_files($_POST["data"]);
	}
	
    // Download all generated CSV sample files in the ZIP file format.
    if(isset($_GET["download"])){
		$sample->download_samples("data.zip");
	}	
?>
<!DOCTYPE html>
<html>
<head>
<title>Food Irradiation Data Logger</title>

<!--link to index.css-->
<link rel="stylesheet" type="text/css" href="assets/index.css"></link>

<!--link to favicon-->
<link rel="icon" type="image/png" sizes="36x36" href="assets/icon.png">

<!-- link to FontAwesome-->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.1.1/css/all.css">
 
<!-- link to font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Oswald&display=swap" rel="stylesheet">

</head>
<body>
<?php ini_set('display_errors',1);?> 
<h1><i class="fa-solid fa-circle-radiation"></i> Food Irradiation Data Logger</h1>

<div class="container">
<section>
<h2>Created Samples:</h2>
<table>
  <tr>
    <th>Samples</th>
    <th>Download</th>
  </tr>
  <tr>
	<td>Download all samples.</td>
	<td><a href="?download"><button><i class="fa-solid fa-cloud-arrow-down"></i></button></a></td>
  </tr>
<?php
  foreach(glob("data/*.csv") as $sample){
	  echo '
        <tr>
           <td>'.explode("/", $sample)[1].'</td>
        </tr>		   
	  ';
  }
?>
</table>
</section>
<section>
<div>
<h2><i class="fa-solid fa-database"></i> Database Status:</h2>
<p>Total Samples: <span><?php echo $count["total"]; ?></span></p>
<p>Regulated: <span><?php echo $count["regulated"]; ?></span></p>
<p>Unsafe: <span><?php echo $count["unsafe"]; ?></span></p>
<p>Hazardous: <span><?php echo $count["hazardous"]; ?></span></p>
<form method="post">
<fieldset>
<legend>Data Type</legend>
<br>
<label><input type="radio" name="data" value="training" /><span class="mark"></span> Training</label>
<label><input type="radio" name="data" value="testing" /><span class="mark"></span> Testing</label>
<br><br>
</fieldset>
<br>
<button type="submit"><i class="fa-solid fa-folder-plus"></i> Create Samples</button>
</form>
</div>
</section>
</div>
</body>
</html>

index.css

CSS
html{background-color:#eb2e00;font-family: 'Oswald', sans-serif;}
h1{color:#002699;text-align:center;font-weight:bold;user-select:none;}
h2{color:white;font-weight:bold;}
p, button{line-height:normal;font-weight:bold;color:white;}
fieldset{border: 3px solid #F3D060;margin:auto;width:80%;text-align:center;}
legend{color:#F3D060;font-size:20px;user-select:none;}
input[type="radio"]{position:absolute;height:0;width:0;user-select:none;}
label{position:relative;font-size:15px;padding-left:50px;margin-left:40px;color:#F3D060;font-size:15px;cursor:pointer;}
.mark{position:absolute;top:0;left:0;height:40px;width:40px;border-radius:50%;background-color:#A5282C;margin-right:10px;}
.mark:after{content:'';position:absolute;top:10px;left:10px;width:20px;height:20px;border-radius:50%;background-color:#EE7762;}
label:hover .mark{background-color:#F3D060;}
input[type="radio"]:checked ~ .mark{background-color:#F3D060;} 
input[type="radio"]:checked ~ .mark:after{background-color:#5EB0E5;} 

.container{position:relative;background-color:none;width:90%;height:500px;margin:auto;margin-top:40px;}
.container section:nth-of-type(1){position:absolute;background-color:#2E3033;top:0;left:0;width:50%;height:100%;overflow-y:auto;border-radius:35px 0 0 35px;}
.container section:nth-of-type(1) h2{color:#F3D060;padding-left:45px;}
.container section:nth-of-type(1) table{border-collapse: collapse;width:90%;margin-left:45px;margin-bottom:20px;}
.container section:nth-of-type(1) table, .container section:nth-of-type(1) td, .container section:nth-of-type(1) th{border:2px solid white;color:white;}
.container section:nth-of-type(1) td{padding-left:20px;padding-top:5px;padding-bottom:5px;}
.container section:nth-of-type(1) th{color:#F3D060;background-color:#A5282C;}
.container section:nth-of-type(1) button{color:white;background-color:#A5282C;border:none;border-radius:5px;cursor:pointer;width:75%;}
.container section:nth-of-type(1) button:hover{background-color:white;color:#A5282C;}
.container section:nth-of-type(2){position:absolute;background-color:#ff5c33;top:0;right:0;width:50%;height:100%;border-radius:0 30px 30px 0;}
.container section:nth-of-type(2) div{position:relative;width:90%;height:100%;background-color:none;overflow-y:auto;margin:auto;}
.container section:nth-of-type(2) button{display:block;color:#F3D060;font-size:25px;background-color:#A5282C;border:5px solid #F3D060;border-radius:10px;cursor:pointer;width:75%;margin:auto;}
.container section:nth-of-type(2) button:hover{background-color:#EE7762;}
.container section:nth-of-type(2) span{color:#002699;}

::selection {color: #ff5c33; background: #1a53ff;}
/* Width */
::-webkit-scrollbar {width: 10px;height: 10px;}
/* Track */
::-webkit-scrollbar-track {background-color: #4d79ff; }
/* Button */
::-webkit-scrollbar-button{background-color:#ffc2b3;height:10px;width:10px;}
::-webkit-scrollbar-button:hover{background-color:white;}
/* Handle */
::-webkit-scrollbar-thumb {background-color: #ff9980; }
::-webkit-scrollbar-thumb:hover {background-color: #ffd6cc;}
/* Corner */
::-webkit-scrollbar-corner{background-color:#4d79ff;}

Credits

Kutluhan Aktar

Kutluhan Aktar

79 projects • 290 followers
Self-Taught Full-Stack Developer | @EdgeImpulse Ambassador | Maker | Independent Researcher

Comments