Sitemap / Advertise

Introduction

Activate your cart with a QR code sent via email by the web app, update product info by scanning barcodes, and get shopping tips from ChatGPT.


Tags

Share

AI-driven IoT Shopping Assistant w/ ChatGPT

Advertisement:


read_later

Read Later



read_later

Read Later

Introduction

Activate your cart with a QR code sent via email by the web app, update product info by scanning barcodes, and get shopping tips from ChatGPT.

Tags

Share





Advertisement

Advertisement




    Components :
  • [1]WIZnet W5300 TOE SHIELD
  • [1]STM32 Nucleo-144 NUCLEO-F439ZI
  • [1]DFRobot GM77 Barcode and QR Code Scanning Module
  • [1]DFRobot Fermion: 1.51” OLED Transparent Display
  • [1]DFRobot Tiny (Embedded) Thermal Printer
  • [1]LattePanda 3 Delta 864
  • [1]DFRobot 8.9” 1920x1200 IPS Touch Display
  • [1]Anycubic Kobra 2
  • [1]Keyes 10mm RGB LED Module (140C05)
  • [3]Button (6x6)
  • [1]Xiaomi 20000 mAh 3 Pro Type-C Power Bank
  • [2]USB Buck-Boost Converter Board
  • [1]Breadboard
  • [1]Ethernet Cable
  • [1]Jumper Wires

Description

In light of recent developments in machine learning and artificial intelligence, various brands aimed to implement AI-based solutions to improve the shopping experience and increase their profit margin by tailoring unique suggestions and advertisements depending on the targeted user's profile and preferences. Especially e-commerce companies, platforms, and marketplaces benefited from AI algorithms to autonomously identify the most optimal ad placements for individual customers.

Even though there are some pioneering methods to combine physical store shopping with AI-based e-commerce features, such as Amazon Go cashierless convenience stores, these self-checkout stations are expensive investments for local stores since they require renovating (remodeling) store layouts or paying monthly fees to cloud services. Furthermore, local businesses may need to construct data sets for niche market products since AI algorithms require broad data sets for methods based on computer vision, sensor fusion, and deep learning.

After scrutinizing the recent research papers and the granted patents on e-commerce methods, I noticed there are nearly no appliances focusing on merging physical store shopping with AI-based solutions without altering existing conditions for local businesses. Therefore, I decided to build a budget-friendly and accessible AIoT shopping assistant utilizing the most common and recognizable product identification method β€” barcodes β€” to bring AI-based e-commerce features to physical store shopping.

Since I wanted to build a shopping assistant providing a wholesome physical shopping experience with the most prominent e-commerce features, I decided to develop a full-fledged e-commerce web application from scratch in PHP, HTML, JavaScript, CSS, and MySQL. Since I developed this complementing web application to bridge the gap between physical store shopping and e-commerce, it is capable of communicating with the shopping assistant (device), obtaining product information from barcodes, and generating AI-based recommendations consecutively.

The complementing e-commerce web application allows the customer to:

As suitable for local businesses, I decided to install an Apache HTTP Server (XAMPP) on LattePanda 3 Delta, which also has a MariaDB database, to host my web application. Since I focused on creating a low-budget but state-of-the-art AIoT shopping assistant, I decided to employ the OpenAI API to generate ChatGPT-powered recommendations related to the obtained product information by barcodes via the Open Food Facts JSON API. I also employed Brevo's Email API to send HTML emails directly from localhost to inform the customer of the generated unique verification QR codes.

After developing my e-commerce web application successfully, I started to work on building the shopping assistant (device). Since Wi-Fi and Bluetooth transmissions may not be suitable options for a shopping assistant requiring as minimal latency as possible to communicate with the e-commerce application, I decided to utilize WIZnet's W5300 TOE SHIELD providing high-speed bus communication and reliable Ethernet transmission speeds. Since W5300 TOE SHIELD cannot perform without an STM32 Nucleo-144 board, I decided to utilize the NUCLEO-F439ZI development board and program it in MicroPython.

Since I needed the shopping assistant to scan barcodes and QR codes simultaneously to recognize customers, activate/deactivate carts, and send the scanned product barcode with the selected command (add/remove) to the e-commerce web application, I connected a GM77 barcode and QR code scanner to NUCLEO-F439ZI. Also, I connected an SSD1309 transparent display and an RGB LED so as to inform the customer of the device status. Furthermore, I added a tiny (embedded) thermal printer to directly notify the customer when the customer requests a device status report or scans the payment confirmation QR code. In that regard, I wanted to enrich the correlation between physical store shopping and e-commerce.

Lastly, to make the shopping assistant as robust and sturdy as possible while being employed as a self-checkout station, I designed a checkout counter-themed case, a removable top cover with snap-fit joints, and handles for moving the shopping assistant if necessary.

So, this is my project in a nutshell πŸ˜ƒ

In the following steps, you can find more detailed information on coding, utilizing QR codes as an authentication method, obtaining product information via barcodes, generating ChatGPT-powered product recommendations, developing a full-fledged e-commerce web application, and communicating with the web application via W5300.

🎁🎨 Huge thanks to WIZnet for providing me with a W5300 TOE SHIELD.

🎁🎨 Huge thanks to DFRobot for sponsoring these products:

⭐ GM77 Barcode and QR Code Scanning Module | Inspect

⭐ Fermion: 1.51” OLED Transparent Display | Inspect

⭐ Tiny (Embedded) Thermal Printer | Inspect

⭐ LattePanda 3 Delta 864 | Inspect

⭐ DFRobot 8.9" 1920x1200 IPS Touch Display | Inspect

🎁🎨 Also, huge thanks to Anycubic for sponsoring a brand-new Anycubic Kobra 2.

project-image
Figure - 91.1


project-image
Figure - 91.2


project-image
Figure - 91.3


project-image
Figure - 91.4


project-image
Figure - 91.5


project-image
Figure - 91.6


project-image
Figure - 91.7


project-image
Figure - 91.8


project-image
Figure - 91.9


project-image
Figure - 91.10


project-image
Figure - 91.11


project-image
Figure - 91.12


project-image
Figure - 91.13


project-image
Figure - 91.14


project-image
Figure - 91.15


project-image
Figure - 91.16


project-image
Figure - 91.17


project-image
Figure - 91.18


project-image
Figure - 91.19


project-image
Figure - 91.20


project-image
Figure - 91.21

Step 1: Designing and printing a checkout counter-themed case

Since I focused on building a budget-friendly and accessible shopping assistant that scans product barcodes, communicates the e-commerce web application, and activates/deactivates the user's cart on the web app via QR codes, I decided to design a stylish and compact case allowing the customer to inspect the device status, scan product barcodes effortlessly, and receive the printed notifications by the embedded thermal printer. To avoid overexposure to dust and prevent loose wire connections, I added a removable top cover with snap-fit joints. Then, I added a slot to integrate the tiny thermal printer and two handles to position the removable top cover easily while performing maintenance. Also, I decided to emboss the WIZnet logo, the ChatGPT logo, and the shopping cart symbol on the removable top cover to highlight the capabilities of the shopping assistant.

Since I needed to fix the position of the scanner module to get better results, I added a two-sided protrusion on the front of the main case. Also, I thought providing a small gift for a new customer trying this shopping assistant would be cool, so I decided to print a Togepi figure to demonstrate my idea, which is a Fairy-type PokΓ©mon and is considered to be a symbol of good luck and fortune :)

I designed the main case and the removable top cover in Autodesk Fusion 360. You can download their STL files below.

project-image
Figure - 91.22


project-image
Figure - 91.23


project-image
Figure - 91.24


project-image
Figure - 91.25


project-image
Figure - 91.26


project-image
Figure - 91.27


project-image
Figure - 91.28


project-image
Figure - 91.29


project-image
Figure - 91.30

For the Togepi figure added as a gift for a first-time customer trying the shopping assistant, I utilized this model from Thingiverse:

Then, I sliced all 3D models (STL files) in Ultimaker Cura.

project-image
Figure - 91.31


project-image
Figure - 91.32


project-image
Figure - 91.33


project-image
Figure - 91.34


project-image
Figure - 91.35

Since I wanted to create an attractive counter structure for the main case and apply a modern retail store theme representing a flourishing business, I utilized these PLA filaments:

Finally, I printed all parts (models) with my brand-new Anycubic Kobra 2 3D Printer.

project-image
Figure - 91.36

Since Anycubic Kobra 2 is budget-friendly and specifically designed for high-speed printing, I highly recommend Anycubic Kobra 2 if you are a maker or hobbyist needing to print multiple prototypes before finalizing a complex project.

Thanks to its upgraded direct extruder, Anycubic Kobra 2 provides 150mm/s recommended print speed (up to 250mm/s) and dual-gear filament feeding. Also, it provides a cooling fan with an optimized dissipation design to support rapid cooling complementing the fast printing experience. Since the Z-axis has a double-threaded rod structure, it flattens the building platform and reduces the printing layers, even at a higher speed.

Furthermore, Anycubic Kobra 2 provides a magnetic suction platform on the heated bed for the scratch-resistant spring steel build plate allowing the user to remove prints without any struggle. Most importantly, you can level the bed automatically via its user-friendly LeviQ 2.0 automatic bed leveling system. Also, it has a smart filament runout sensor and the resume printing function for power failures.

#️⃣ First of all, install the gantry and the spring steel build plate.

project-image
Figure - 91.37


project-image
Figure - 91.38

#️⃣ Install the print head, the touch screen, and the filament runout sensor.

project-image
Figure - 91.39


project-image
Figure - 91.40

#️⃣ Connect the stepper, switch, screen, and print head cables. Then, attach the filament tube.

project-image
Figure - 91.41

#️⃣ If the print head is shaking, adjust the hexagonal isolation column under the print head.

project-image
Figure - 91.42

#️⃣ Go to Prepare➑ Leveling ➑ Auto-leveling to initiate the LeviQ 2.0 automatic bed leveling system.

#️⃣ After preheating and wiping the nozzle, Anycubic Kobra 2 probes the predefined points to level the bed.

project-image
Figure - 91.43


project-image
Figure - 91.44


project-image
Figure - 91.45


project-image
Figure - 91.46


project-image
Figure - 91.47

#️⃣ Finally, fix the filament tube with the cable clips, install the filament holder, and insert the filament into the extruder.

project-image
Figure - 91.48

#️⃣ Since Anycubic Kobra 2 is not officially supported by Cura yet, download the latest PrusaSlicer version and import the printer profile (configuration) file provided by Anycubic.

#️⃣ Then, create a custom printer profile on Cura for Anycubic Kobra 2 and change Start G-code and End G-code.

#️⃣ Based on the provided Start G-code and End G-code in the configuration file, I modified new Start G-code and End G-code compatible with Cura.


Start G-code:

G90 ; use absolute coordinates
M83 ; extruder relative mode
G28 ; move X/Y/Z to min endstops
G1 Z2.0 F3000 ; lift nozzle a bit 
G92 E0 ; Reset Extruder
G1 X10.1 Y20 Z0.28 F5000.0 ; Move to start position
G1 X10.1 Y200.0 Z0.28 F1500.0 E15 ; Draw the first line
G1 X10.4 Y200.0 Z0.28 F5000.0 ; Move to side a little
G1 X10.4 Y20 Z0.28 F1500.0 E30 ; Draw the second line
G92 E0 ; zero the extruded length again 
G1 E-2 F500 ; Retract a little 
M117
G21 ; set units to millimeters
G90 ; use absolute coordinates
M82 ; use absolute distances for extrusion
G92 E0
M107

End G-code:

M104 S0 ; Extruder off 
M140 S0 ; Heatbed off 
M107    ; Fan off 
G91     ; relative positioning 
G1 E-5 F3000  
G1 Z+0.3 F3000 ; lift print head 
G28 X0  F3000
M84            ; disable stepper motors

#️⃣ Finally, adjust the official printer settings depending on the filament type while copying them from PrusaSlicer to Cura.

project-image
Figure - 91.49


project-image
Figure - 91.50


project-image
Figure - 91.51


project-image
Figure - 91.52


project-image
Figure - 91.53


project-image
Figure - 91.54


project-image
Figure - 91.55

Step 1.1: Assembling the case and making connections & adjustments


// Connections
// W5300 TOE SHIELD :
//                                GM77 Barcode and QR Code Scanner
// PA3  --------------------------- TX
// PA2  --------------------------- RX
//                                Tiny (Embedded) Thermal Printer
// PG9  --------------------------- TX
// PG14 --------------------------- RX
//                                SSD1309 OLED Transparent Display
// PA7  --------------------------- MOSI
// PA5  --------------------------- SCLK
// PA4  --------------------------- DC
// PC7  --------------------------- RES
// PC6  --------------------------- CS
//                                Control Button (A)
// PC0  --------------------------- +
//                                Control Button (B)
// PC3  --------------------------- +
//                                Control Button (C)
// PC2  --------------------------- +
//                                Keyes 10mm RGB LED Module (140C05)
// PA1  --------------------------- R
// PF9  --------------------------- G
// PA0  --------------------------- B

Since W5300 TOE SHIELD cannot perform without an STM32 Nucleo-144 board, I needed to attach W5300 TOE SHIELD to an STM32 development board supporting W5300 MicroPython software. As of now, W5300 TOE SHIELD is compatible with these Nucleo-144 development boards for programming in MicroPython:

Since NUCLEO-F429ZI is not available in my country, I decided to utilize NUCLEO-F439ZI, which shares the same pin assignments and structure with NUCLEO-F429ZI.

To utilize the ST-LINK features, WIZnet states that the ST-LINK pin connections of the STM32 Nucleo-144 board must be manually altered due to the fact that the FMC (Flexible Memory Controller) data pin to control the W5300 chip overlaps with the default ST-LINK pin.

#️⃣ First of all, remove the SB5 and SB6 resistors, which are the solder bridges for the ST-LINK-USART, from the top of the STM32 Nucleo-144 board with a soldering iron. Also, solder double-row female pin headers to the top of the STM32 board in order to attach W5300 TOE SHIELD.

project-image
Figure - 91.56


project-image
Figure - 91.57

#️⃣ Then, after attaching W5300 TOE SHIELD to the STM32 Nucleo-144 board, connect the PC10 and PC11 pins of W5300 TOE SHIELD to the RX and TX pins of the CN5 connector on the STM32 board via jumper wires.

project-image
Figure - 91.58


project-image
Figure - 91.59

After attaching W5300 TOE SHIELD to NUCLEO-F439ZI successfully by making manual adjustments on the board, I proceeded to connect the remaining components to W5300 TOE SHIELD via its Arduino-compatible pin layout on the top of the shield.

To scan product barcodes and the QR codes generated by the e-commerce web application, I connected a GM77 barcode and QR code scanner to W5300 TOE SHIELD. Since W5300 TOE SHIELD cannot directly supply the GM77 barcode and QR code scanner due to its demanding current loads, I connected a USB buck-boost converter board to my Xiaomi power bank to obtain stable 5V to power the scanner module.

As mentioned in the user manual of the GM77 scanner module, it does not transfer data with the built-in UART (serial) communication channel out of the box. Therefore, I needed to scan a configuration QR code provided by the manual to activate the built-in TTL 232 interface.

project-image
Figure - 91.60

After activating the built-in UART (serial) interface of the scanner module, it starts to transfer data packets via serial communication immediately:

Then, I connected a tiny (embedded) thermal printer to directly inform the customer when the customer requests a device status report or scans the payment confirmation QR code. Since W5300 TOE SHIELD cannot power the tiny (embedded) thermal printer due to its operating voltage, I connected a USB buck-boost converter board to my Xiaomi power bank to obtain stable 12V to supply the thermal printer. The higher input voltage (voltage range is 5~9V) means faster printing speed and more clear printed records. I utilized 12V above the recommended 9V threshold due to my power bank's current limitation.

To inform the customer of the device status, I added an SSD1309 OLED transparent screen and a 10mm common anode RGB LED module (Keyes). Since the SSD1309 screen provides a connector for the GDI display interface, I was able to connect it effortlessly.

Finally, I added three control buttons to let the customer send commands to the e-commerce web application and request a device status report printed by the thermal printer.

After completing breadboard connections and adjustments successfully, I made the breadboard connection points rigid by utilizing a hot glue gun.

project-image
Figure - 91.61


project-image
Figure - 91.62

After printing all parts (models), I fastened the components to their corresponding slots on the main case.

I placed the W5300 TOE SHIELD attached to NUCLEO-F439ZI and connected an Ethernet cable to the shield via the built-in RJ-45 connector. Then, I affixed the tiny thermal printer to the top of the removable cover.

Finally, I attached the removable top cover to the main case via the snap-fit joints.

project-image
Figure - 91.63


project-image
Figure - 91.64


project-image
Figure - 91.65


project-image
Figure - 91.66


project-image
Figure - 91.67


project-image
Figure - 91.68


project-image
Figure - 91.69


project-image
Figure - 91.70


project-image
Figure - 91.71


project-image
Figure - 91.72


project-image
Figure - 91.73


project-image
Figure - 91.74


project-image
Figure - 91.75


project-image
Figure - 91.76


project-image
Figure - 91.77


project-image
Figure - 91.78


project-image
Figure - 91.79


project-image
Figure - 91.80


project-image
Figure - 91.81


project-image
Figure - 91.82

As mentioned earlier, I also printed a Togepi figure, representing good luck and fortune, as a small gift for the first-time customers trying the shopping assistant :)

project-image
Figure - 91.83

Step 2: Creating a Brevo account to send HTML emails from localhost

Since I wanted to provide a wholesome e-commerce customer experience with this shopping assistant, I decided to make it able to send account verification and payment confirmation HTML emails, including the generated unique QR codes, to the customer's registered email address on the database. Nonetheless, I did not want to make this feature dependent on a paid email forwarder or cloud service since I built this budget-friendly device for local businesses. Therefore, I decided to send HTML emails directly from localhost via Brevo's Email API.

Brevo's Email API can handle up to 120,000 emails a minute with 99.8% successful API calls and has free of charge plan for relatively small projects like this. Even though Brevo provides official libraries (packages) for the most common programming languages, I decided to make cURL calls (HTTP requests) to Brevo's Email API in PHP. In that regard, I had the flexibility to utilize only the supported functions I needed.

#️⃣ First of all, sign up for Brevo and create a new account with the free plan. If required, you can add your company name and website, as did I.

project-image
Figure - 91.84


project-image
Figure - 91.85


project-image
Figure - 91.86

#️⃣ After verifying the email address with which you signed in, go to Senders & IP ➑ Senders. Then, create a new sender with the selected email address.

#️⃣ To verify the new sender, click the verification link sent to the registered account email.

project-image
Figure - 91.87


project-image
Figure - 91.88


project-image
Figure - 91.89

#️⃣ Finally, go to SMTP & API ➑ API Keys and click the Generate a new API Key button to create an API key for utilizing Brevo's Email API.

project-image
Figure - 91.90


project-image
Figure - 91.91

Step 3: Creating an OpenAI account to generate credentials for ChatGPT

As mentioned earlier, I focused on creating a low-budget but state-of-the-art AIoT shopping assistant. Therefore, I did not want to inundate shop owners to construct valid data sets for their products to generate AI-powered suggestions. After inspecting recent developments in AI-based recommendation systems, I decided to employ the OpenAI API to generate ChatGPT-powered recommendations related to the given product.

Since ChatGPT is the centerpiece of this shopping assistant, I needed to integrate ChatGPT into my e-commerce web application via the OpenAI API to minimize latency and provide a better user experience.

Although OpenAI provides official libraries in different programming languages for the OpenAI API, I decided to make cURL calls (HTTP requests) to utilize the API functions since I only needed to employ one of the built-in algorithms β€” gpt-3.5-turbo.

#️⃣ First of all, sign up for OpenAI to create a free account.

#️⃣ Even though a free account has limited tokens for API calls, it is more than enough for generating insightful product recommendations.

#️⃣ Then, go to API keys and click the Create new secret key button.

project-image
Figure - 91.92


project-image
Figure - 91.93


project-image
Figure - 91.94


project-image
Figure - 91.95

#️⃣ After creating the secret key, you can make direct API requests to the OpenAI API so as to generate ChatGPT-powered product recommendations.

project-image
Figure - 91.96

Step 4: Developing a full-fledged e-commerce web application in PHP, JavaScript, CSS, and MySQL

To provide an exceptional e-commerce customer experience while physical store shopping, I developed a full-fledged web application from scratch in PHP, HTML, JavaScript, CSS, and MySQL.

First of all, the e-commerce web application allows the customer to create a user account by entering the required information on the home page via the sign-up form. Then, the web application generates a 12-digit unique account (user) token, creates a QR code with the generated user token that activates the user's cart on the shopping assistant (device), and sends an HTML email, including the account verification QR code, to the customer's registered email address. After storing the given account information in a particular MySQL database table, the web application creates a unique database table for the new user by utilizing the unique user token.

After activating the user cart via the verification QR code, the web application can receive the scanned product barcode via an HTTP GET request from the shopping assistant (device) and obtain the product information by barcode via the Open Food Facts JSON API. Depending on the selected command by the customer, the web application changes the product list in the cart by updating the product information stored in the user's database table. Also, the web application updates the user dashboard (interface) automatically to display the latest product information stored in the user's database table as an HTML table. On the user dashboard, the web application lets the customer inspect the current product list in the cart with the total cart price, generate ChatGPT-powered recommendations for each product, go to the checkout page, display the account verification QR code, and examine the previous orders.

After placing an order successfully by paying the cart amount on the checkout page, the web application generates a payment confirmation QR code with the user token and sends it via an HTML email to the customer's registered email address.

As shown below, the e-commerce web application consists of two folders and thirteen code files:

project-image
Figure - 91.97


project-image
Figure - 91.98


project-image
Figure - 91.99

For each section of the web application, I will talk about the related code files in the following steps.

πŸ“ class.php

In the class.php file, I created a class named user and a child class named product to bundle the following functions under a specific structure.

⭐ Define the user class and its functions.


class user {
	public $conn;
	
	private $Brevo_API_URL = "https://api.brevo.com/v3/smtp/email";
	private $Brevo_API_Key = '<_API_Key_>';
	private $Brevo_email = 'freelance@theamplituhedron.com';
	private $Brevo_email_name = 'AIoT Shopping Assistant';
	
	public function __init__($conn){
		$this->conn = $conn;
	}

⭐ In the add_new_account function:

⭐ Check if the provided user information corresponds to an existing account stored in the users MySQL database table.

⭐ Confirm the given account password by comparing provided form parameters.

⭐ Obtain the unique account (user) token β€” 12 digits.

⭐ Generate an account verification QR code from the generated user token by merely making a URL GET request to Google Charts.

user%<_user_token_>

⭐ Insert new user information into the users MySQL database table.

⭐ Create a unique MySQL database table with the unique user token.

⭐ Send a verification email to the user, including the account verification QR code.

⭐ Set the required session variables.

⭐ If there is no error, redirect the customer to the user interface (dashboard).


	public function add_new_account($firstname, $lastname, $email, $username, $password, $c_password){
		// Check for existing users.
		$existing_sql = "SELECT * FROM `users` WHERE `username`='$username' OR `email`='$email'";
		$existing_sql_result = mysqli_query($this->conn, $existing_sql);
		$existing_sql_check = mysqli_num_rows($existing_sql_result);
		if($existing_sql_check > 0){
			header('Location: ../?userAlreadyExists');
			exit();
		}
		// Confirm the given account password.
		if($password != $c_password){
			header('Location: ../?wrongPassword');
			exit();
		}
		
		// Obtain the unique user token β€” 12 digits.
		$user_token = $this->generate_token(12, $username);

		// Create a QR code from the given username and the generated user token.
		$qr_code = "https://chart.googleapis.com/chart?cht=qr&chs=450x450&chl=user%".$user_token."&choe=UTF-8";
		
		// Insert new user information into the users MySQL database table.
		$insert_sql = "INSERT INTO `users` (`firstname`, `lastname`, `username`, `password`, `email`, `token`, `qr_code`, `successful_order`)
		               VALUES ('$firstname', '$lastname', '$username', '$password', '$email', '$user_token', '$qr_code', 1)";
		mysqli_query($this->conn, $insert_sql);
		
		// Create a unique MySQL database table for the registered account.
		$new_table = $this->create_products_table($user_token);
		if(!$new_table){ header('Location: ../?mysqlServerFailed'); exit(); }
		
		// Send a confirmation email to the user, including the verification QR code.
		$this->send_confirmation_email($email, "Verify Your Account", $firstname.' '.$lastname, $qr_code);
		
		// Set the required session variables.
		$_SESSION["name"] = $firstname.' '.$lastname;
		$_SESSION["username"] = $username;
		$_SESSION["email"] = $email;
		$_SESSION["user_token"] = $user_token;
		$_SESSION["qr_code"] = $qr_code;
		
		// If there is no error, go to the user interface (dashboard).
		header('Location: ../dashboard.php');
		exit();
	}

⭐ In the user_login_request function:

⭐ Check whether the given account information is accurate when the user requests to log into an existing account.

⭐ After logging into the account successfully, set the required session variables.

⭐ If there is no error, redirect the customer to the user interface (dashboard).


	public function user_login_request($u_username, $u_password){
		// Check whether the given account information is accurate.
	    $account_sql = "SELECT * FROM `users` WHERE `username`='$u_username' AND `password`='$u_password'";
		$account_sql_result = mysqli_query($this->conn, $account_sql);
		$account_sql_check = mysqli_num_rows($account_sql_result);
		if($account_sql_check > 0){
			if($row = mysqli_fetch_assoc($account_sql_result)){				
			    // Set the required session variables.
				$_SESSION["name"] = $row['firstname'].' '.$row['lastname'];
				$_SESSION["username"] = $row['username'];
				$_SESSION["email"] = $row['email'];
				$_SESSION["user_token"] = $row['token'];
				$_SESSION["qr_code"] = $row['qr_code'];
				// If there is no error, go to the user interface (dashboard).
				header('Location: ../dashboard.php');
				exit();
			}
		}else{
			header('Location: ../login.php?noAccountFound');
			exit();
		}
	}

⭐ In the generate_token function:

⭐ Define the lowercase, uppercase, number, and symbol characters to create the main string.

⭐ Derive a unique key from the main string by utilizing the random_int function and add the given username to the generated key to create the unique user token.


	private function generate_token($len, $username){
		// Define the main string.
		$lowercase = "abcdefghijklmnopqrstuvwxyz"; $uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $number = "0123456789"; $symbol = "*()[]{}#$?!";
		$main = $lowercase.$uppercase.$number.$symbol;
		// Derive the user token from the main string.
		$token = "";
		for ($i=0; $i<$len; $i++){ $token .= $main[random_int(0, (strlen($main)-1))]; }
        return $username."_".$token;
	}

⭐ In the create_products_table, create a unique MySQL database table by using the user token for the newly registered user.


	private function create_products_table($table){
		// Create a new database table.
		$sql_create = "CREATE TABLE `$table`(		
							id int AUTO_INCREMENT PRIMARY KEY NOT NULL,
							product_barcode varchar(255) NOT NULL,
							product_name varchar(255) NOT NULL,
							product_ingredients varchar(255) NOT NULL,
							product_price int NOT NULL,
							cart_number int NOT NULL,
							order_number int NOT NULL
					   );";
		if(mysqli_query($this->conn, $sql_create)){ return true; } else{ return false; }		
	}

⭐ In the send_Brevo_email function:

⭐ Define the POST data parameters required by Brevo's Email API in the JSON format.

⭐ Define the required HTML headers, including the API key, for authentification.

⭐ Send an HTML email with the given content by making a cURL call (HTTP request) to Brevo's Email API.

⭐ Execute the defined cURL call with the given parameters.


	public function send_Brevo_email($to_email, $subject, $name, $html_content){
		// Define POST data parameters in the JSON format. 
		$data = '{  
					"sender":{  
								"name":"'.$this->Brevo_email_name.'",
								"email":"'.$this->Brevo_email.'"
							 },
					"to":[  
							 {  
								"email":"'.$to_email.'",
								"name":"'.$name.'"
							 }
						 ],
					"subject":"'.$subject.'",
					"htmlContent":"'.$html_content.'"
				 }';
		// Define the required HTML headers.
		$headers = array('accept: application/json', 'api-key:'.$this->Brevo_API_Key, 'content-type: application/json');
		// Send an HTML email via Brevo's Email API by making a cURL call.
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($curl, CURLOPT_URL, $this->Brevo_API_URL);
		curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
		// Execute the defined cURL call.
		$result = curl_exec($curl);
		if(!$result){ header('Location: ../?emailServerFailed'); exit(); }
        curl_close($curl);
	}

⭐ In the send_confirmation_email function:

⭐ Define the HTML content of the account verification email, including the unique account verification QR code.

⭐ Transfer the given HTML email to the user's registered email address via Brevo's Email API.


	private function send_confirmation_email($to_email, $subject, $name, $qr_code){
		// Define the HTML message (content) of the email.
		$html_content = '<html><head><style>h1{text-align:center;font-weight:bold;color:#505F4E;font-size:40px;}a{text-decoration:none;color:#9BB5CE;font-size:18px;font-weight:bold;}div{display:block;background-color:#F9E5C9;text-align:center;border:50px solid #5C5B57;}div p{font-size:25px;color:#505F4E;font-weight:bold;}@media only screen and (max-width: 600px){h1{font-size:20px;}a{font-size:9px;}div{border:10px solid #5C5B57;}div p{font-size:12px;}}</style></head><body><div><h1>Thanks for trying AIoT Shopping Assistant 😊</h1><img src=\"'.$qr_code.'\" alt=\"QR_CODE\" /><p>Please scan the account QR code with the shopping assistant to activate your cart πŸ›οΈ</p><a href=\"http://192.168.1.22/AIoT_Shopping_Assistant/\">➑️ Go to your Dashboard<br><br></a></div></body></html>';
		// Transfer the HTML email.
		$this->send_Brevo_email($to_email, $subject, $name, $html_content);
	}

⭐ Define the product class and its functions, extending the user class.


	private $OPENAI_API_KEY = "<_OPENAI_API_KEY_>";
	private $OPENAI_ENDPOINT = "https://api.openai.com/v1/chat/completions";

⭐ In the get_product_info function:

⭐ Make an HTTP GET request to the Open Food Facts JSON API with the given barcode.

⭐ Then, decode the received JSON object to obtain the product name, ingredients, and price.


	public function get_product_info($barcode){
		// Make an HTTP GET request to the Open Food Facts JSON API.
		// Then, decode the received JSON object.
		$data = json_decode(file_get_contents("https://world.openfoodfacts.org/api/v0/product/".$barcode.".json", TRUE));
		$product_info = array(
								"name" => $data->product->product_name,
								"ingredients" => (is_null($data->product->ingredients_text_en) || $data->product->ingredients_text_en == "") ? "Not Found" : $data->product->ingredients_text_en,
								"price" => (int)$data->product->product_quantity / 100
							 );
		return $product_info;
	}

⭐ In the get_current_products function:

⭐ Obtain the current order tag (number) from the users database table.

⭐ Fetch all registered product information as the current cart list, sharing the latest order number.

⭐ Store the fetched product information as arrays.

⭐ Calculate the total cart price (amount).

⭐ Then, return the generated arrays and the estimated total cart price as a list.


	public function get_current_products($table){
		$total_price = 0;
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// Obtain all registered product information of the current cart as a list.
		$p_barcode = []; $p_name = []; $p_ingredients = []; $p_price = []; $p_number = [];
		$sql_list = "SELECT * FROM `$table` WHERE `order_number`='$order_number' ORDER BY `id` ASC";
		$result = mysqli_query($this->conn, $sql_list);
		$check = mysqli_num_rows($result);
		if($check > 0){
			while($row = mysqli_fetch_assoc($result)){
				// Store the fetched product information as arrays.
				array_push($p_barcode, $row["product_barcode"]);
				array_push($p_name, $row["product_name"]);
				array_push($p_ingredients, $row["product_ingredients"]);
				array_push($p_price, $row["product_price"]);
				array_push($p_number, $row["cart_number"]);
				// Calculate the total cart price (amount).
				$price = $row["product_price"] * $row["cart_number"];
				$total_price+=$price;
			}
			return array($p_barcode, $p_name, $p_ingredients, $p_price, $p_number, array("total_price" => $total_price));
		}else{
			return array(["Not Found!"], ["Not Found!"], ["Not Found!"], ["Not Found!"], ["Not Found!"], array("total_price" => 0));
		}
	}

⭐ In the get_previous_orders function:

⭐ Obtain the current order tag (number) from the users database table.

⭐ If there are any previous orders, return the purchased products as an HTML list for each order by utilizing the given order numbers.


	public function get_previous_orders($table){
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// If there are any previous orders, return the purchased products as an HTML list for each order.
		if($order_number == 1){
			echo '<h1><i class="fa-solid fa-circle-xmark"></i> No previous order was found!</h1>';
		}else{
			$list = "";
			for($i=1;$i<$order_number;$i++){
				$sql = "SELECT * FROM `$table` WHERE `order_number`='$i' ORDER BY `id` ASC";
				$result = mysqli_query($this->conn, $sql);
				$check = mysqli_num_rows($result);
				if($check > 0){
					while($row = mysqli_fetch_assoc($result)){
						$line = '<li>'.$row["product_name"].' ['.$row["product_barcode"].'] <span><i class="fa-solid fa-xmark"></i></span>'.$row["cart_number"].'</li>';
						$list.=$line;
					}
				}
				echo '<div class="orders"><h2><i class="fa-solid fa-cash-register"></i> Order ['.$i.']</h2><ul>'.$list.'</ul></div>';
				$list = "";			
			}
		}
	}

⭐ In the user_checkout function:

⭐ Generate a payment confirmation QR code from the generated user token by merely making a URL GET request to Google Charts.

finished<_user_token_>

⭐ Update the successful order number for the customer in the users database table after the checkout process.

⭐ Send a confirmation email to the user, including the payment confirmation QR code.

⭐ If there is no error, redirect the customer to the user interface (dashboard).


	public function user_checkout($table, $email, $name){
		// Create a QR code from the user token and the given command.
		$qr_text = 'finished%'.$table;
		$qr_code = "https://chart.googleapis.com/chart?cht=qr&chs=450x450&chl=".$qr_text."&choe=UTF-8";
		
		// Update the successful order number after the checkout process.
		$sql = "UPDATE `users` SET `successful_order`=`successful_order`+1 WHERE `token` = '$table'";
		mysqli_query($this->conn, $sql);
		
		// Send a notification email to the user, including the unique payment QR code.
		$this->send_payment_email($email, "Order Successful", $name, $qr_code);
		
		// If there is no error, go to the user interface (dashboard).
		header('Location: ./dashboard.php?paymentCompleted');
		exit();	
	}

⭐ In the send_payment_email function:

⭐ Define the HTML content of the payment confirmation email, including the unique payment confirmation QR code.

⭐ Transfer the given HTML email to the user's registered email address via Brevo's Email API.


	private function send_payment_email($to_email, $subject, $name, $qr_code){
		// Define the HTML message (content) of the email.
		$html_content = '<html><head><style>h1{text-align:center;font-weight:bold;color:#505F4E;font-size:40px;}a{text-decoration:none;color:#9BB5CE;font-size:18px;font-weight:bold;}div{display:block;background-color:#F9E5C9;text-align:center;border:50px solid #5C5B57;}div p{font-size:25px;color:#505F4E;font-weight:bold;}@media only screen and (max-width: 600px){h1{font-size:20px;}a{font-size:9px;}div{border:10px solid #5C5B57;}div p{font-size:12px;}}</style></head><body><div><h1>Thanks for your order πŸ˜ŠπŸ‘</h1><img src=\"'.$qr_code.'\" alt=\"QR_CODE\" /><p>Please scan your payment QR code with the shopping assistant to complete your order πŸ’²βœ…</p><a href=\"http://192.168.1.22/AIoT_Shopping_Assistant/\">➑️ Go to your Dashboard<br><br></a></div></body></html>';
		// Transfer the HTML email.
		$this->send_Brevo_email($to_email, $subject, $name, $html_content);
	}

⭐ In the chatgpt_get_suggestion function:

⭐ Define the questions related to the given product information.

⭐ Define the POST data parameters required by the OpenAI API in the JSON format.

⭐ Define the required HTML headers, including the OpenAI API key, for authentification.

⭐ Obtain ChatGPT-powered product suggestions by making a cURL call (HTTP request) to the OpenAI API.

⭐ Execute the defined cURL call with the given parameters.

⭐ After getting the response from the OpenAI API, decode the received JSON object to obtain the recommendations generated by ChatGPT β€” gpt-3.5-turbo.

⭐ Then, modify the obtained suggestions to add line breaks for each answer by utilizing the added question numbers.

⭐ Return the modified suggestions and the defined product questions.


	public function chatgpt_get_suggestion($product){
		// Define the questions related to the given product.
		$questions = array(
							"What is the nutritional value of ".$product."?",
							"What should I purchase with ".$product."?",
							"Can you teach me a recipe with ".$product."?",
							"How should I serve ".$product."?",
							"Is there a more affordable and healthy option than ".$product."?"
		                  );
		// Define POST data parameters in the JSON format. 
		$data = '{  
					"model": "gpt-3.5-turbo",
					"messages": [
									{"role": "user", "content": "'.$questions[0].'"},
									{"role": "user", "content": "'.$questions[1].'"},
									{"role": "user", "content": "'.$questions[2].'"},
									{"role": "user", "content": "'.$questions[3].'"},
									{"role": "user", "content": "'.$questions[4].'"},
									{"role": "user", "content": "Please add the exact question at the beginning of the answer with the question number."}
								],
					"temperature": 0.7
				 }';
		// Define the required HTML headers.
		$headers = array('Authorization: Bearer '.$this->OPENAI_API_KEY, 'Content-Type: application/json');
		// Obtain product suggestions from ChatGPT by making a cURL call to the OpenAI API.
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($curl, CURLOPT_URL, $this->OPENAI_ENDPOINT);
		curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
		// Execute the defined cURL call.
		$result = curl_exec($curl);
		if(!$result){ header('Location: ../?ChatGPTServerFailed'); exit(); }
        curl_close($curl);
        // Decode the received JSON object to obtain suggestions generated by ChatGPT.
		$res = json_decode($result);
		$suggestions = $res->choices[0]->message->content;
		// Modify the obtained suggestions to add line breaks.
		$modified_suggestions = $suggestions;
		$modified_suggestions = str_replace('1. '.$questions[0], "<h2>Suggestions</h2>", $modified_suggestions);
		for($i=1;$i<count($questions);$i++){
			$modified_suggestions = str_replace(strval($i+1).'. '.$questions[$i], "<br><br>", $modified_suggestions);
		} 
		// Return the modified suggestions and the defined product questions.
		return array($modified_suggestions, $questions);
	}

⭐ In the insert_product function:

⭐ Obtain the current order tag (number) from the users database table.

⭐ Check whether the given product barcode is in the user's database table with the recent order tag.

⭐ If the given product is already in the current cart (table), update the product amount (cart number).

⭐ If not, insert the new product information into the user's database table with the recent order tag.


	public function insert_product($table, $barcode, $name, $ingredients, $price){
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// Check whether the given product is in the user's database table or not.
		if($this->check_product($table, $barcode, $order_number)){
			// If the given product is already in the cart (table), update the product amount (cart number).
			$sql_update = "UPDATE `$table` SET `cart_number`=cart_number+1 WHERE `product_barcode` = '$barcode'";
			if(mysqli_query($this->conn, $sql_update)){ return true; } else{ return false; }
		}else{
			// If not, insert the new product information into the user's database table.
			$sql_insert = "INSERT INTO `$table` (`product_barcode`, `product_name`, `product_ingredients`, `product_price`, `cart_number`, `order_number`)
		                   VALUES('$barcode', '$name', '$ingredients', '$price', 1, '$order_number');
					      ";
		    if(mysqli_query($this->conn, $sql_insert)){ return true; } else{ return false; }
		}
	}

⭐ In the delete_product function:

⭐ Obtain the current order tag (number) from the users database table.

⭐ Check whether the given product barcode is in the user's database table with the recent order tag.

⭐ If so, remove the given product from the current cart (table).


	public function delete_product($table, $barcode){
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// Check whether the given product is in the user's database table or not.
		if($this->check_product($table, $barcode, $order_number)){
			// Remove the given product from the cart (table).
			$sql_delete = "DELETE FROM `$table` WHERE `product_barcode`='$barcode' AND `order_number` = '$order_number'";
			if(mysqli_query($this->conn, $sql_delete)){ return true; } else{ return false; }
		}
	}

⭐ In the check_table function, check if there is an existing user database table named with the given user token.


	public function check_table($table){
		$sql = "SELECT * FROM `information_schema`.`TABLES` WHERE `table_name` = '$table' limit 1";
		$sql_result = mysqli_query($this->conn, $sql);
		$sql_check = mysqli_num_rows($sql_result);
		if($sql_check > 0){ return true; } else{ return false; }
	}

⭐ In the check_product function, check whether the given product barcode is in the user's database table with the given order tag.


	private function check_product($table, $barcode, $order_number){
		$sql = "SELECT * FROM `$table` WHERE `product_barcode` = '$barcode' AND `order_number` = '$order_number'";
		$sql_result = mysqli_query($this->conn, $sql);
		$sql_check = mysqli_num_rows($sql_result);
		if($sql_check > 0){ return true; } else{ return false; }
	}

⭐ In the get_order_number function, obtain the current order tag (number) from the users database table via the given user token.


	private function get_order_number($token){
		$order_number = 0;
		$sql = "SELECT * FROM `users` WHERE `token` = '$token'";
		$sql_result = mysqli_query($this->conn, $sql);
		$sql_check = mysqli_num_rows($sql_result);
		if($sql_check > 0){
			if($row = mysqli_fetch_assoc($sql_result)){
				$order_number = $row["successful_order"];
			}
			return $order_number;
		}
	}

⭐ Define the required MariaDB database connection settings for LattePanda 3 Delta 864.


$server = array(
	"name" => "localhost",
	"username" => "root",
	"password" => "",
	"database" => "shopping_assistant_users"
);

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

project-image
Figure - 91.100


project-image
Figure - 91.101


project-image
Figure - 91.102


project-image
Figure - 91.103


project-image
Figure - 91.104


project-image
Figure - 91.105

Step 4.0: Using the Open Food Facts JSON API to get product information by barcode

Open Food Facts is created by a non-profit association and provides a worldwide food products database. Also, the Open Food Facts JSON API lets the user search a product by barcode and obtain provided product characteristics by making an HTTP GET request.

Although Open Food Facts only covers food products, it has a vast database, including products worldwide. Therefore, I decided to utilize the Open Food Facts JSON API to obtain product information by barcode to demonstrate the features of my shopping assistant.

If you want to obtain product information by barcodes of products other than foods, you can utilize some overall product database services with paid subscription plans, such as Barcode Lookup.

project-image
Figure - 91.106


project-image
Figure - 91.107

πŸ“ barcode.php

⭐ Include the class.php file.


include_once "class.php";

⭐ Define the _product object of the product class extending the user class.


$_product = new product();
$_product->__init__($conn);

⭐ If all required HTML (GET) parameters are received:

⭐ Check whether the given table is in the MariaDB (MySQL) database.

⭐ If so, make an HTTP GET request to the Open Food Facts JSON API to obtain the product information with the given barcode.

⭐ According to the selected command by the user, add or remove the given product information to/from the user's database table.


if(isset($_GET["table"]) && isset($_GET["barcode"]) && isset($_GET["com"])){	
	// Check whether the given table is in the MySQL database.
	if($_product->check_table($_GET["table"])){
		// Make an HTTP GET request to the Open Food Facts JSON API to obtain the product information with the given barcode.
		$product_info = $_product->get_product_info($_GET["barcode"]);
		// According to the selected command by the user, add or remove the given product to/from the user's database table.
		if($_GET["com"] == "add"){
			$_product->insert_product($_GET["table"], $_GET["barcode"], $product_info["name"], $product_info["ingredients"], $product_info["price"]);
			echo "Given Product Added to the Cart Successfully!";
		}else if($_GET["com"] == "remove"){
			$_product->delete_product($_GET["table"], $_GET["barcode"]);
			echo "Given Product Removed from the Cart Successfully!";
		}
	}else{
		echo "No Table Found!";
	}
}

project-image
Figure - 91.108

Step 4.1: Designing the home page and the sign-in page

πŸ“ new_account.php

⭐ Include the class.php file.


include_once "class.php";

⭐ Define the _user object of the user class.


$_user = new user();
$_user->__init__($conn);

⭐ Create a new user account if the customer fills out the sign-up form accurately.


if(isset($_GET['firstname']) && isset($_GET['lastname']) && isset($_GET['email']) && isset($_GET['username']) && isset($_GET['password']) && isset($_GET['c_password'])){
	$_user->add_new_account($_GET['firstname'], $_GET['lastname'], $_GET['email'], $_GET['username'], $_GET['password'], $_GET['c_password']);
}

⭐ Sign into an existing account if the user fills out the sign-in form correctly.


if(isset($_GET['u_username']) && isset($_GET['u_password'])){
	$_user->user_login_request($_GET['u_username'], $_GET['u_password']);
}

πŸ“ index.php

⭐ If the user has already signed in, redirect the customer to the user interface (dashboard).


if(isset($_SESSION["username"]) && isset($_SESSION["email"])){
	header('Location: ./dashboard.php');
	exit();
}

⭐ Design the sign-up form and the home page of the e-commerce web application.

You can inspect and download the index.php file below.

πŸ“ login.php

⭐ If the user has already signed in, redirect the customer to the user interface (dashboard).


if(isset($_SESSION["username"]) && isset($_SESSION["email"])){
	header('Location: ./dashboard.php');
	exit();
}

⭐ Design the sign-in page for returning customers.

πŸ“ index.css

You can inspect and download the index.css file below.

project-image
Figure - 91.109


project-image
Figure - 91.110


project-image
Figure - 91.111


project-image
Figure - 91.112

Step 4.2: Designing the user interface (dashboard) showing the account QR code

πŸ“ dashboard.php

⭐ If the user signed into an existing account successfully, proceed to load the user dashboard.


if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

⭐ If the user requests to log out, remove and destroy all session variables.

⭐ Then, redirect the user to the home page.


if(isset($_GET["logout"])){
	// Remove and destroy all session variables.
	session_unset();
	session_destroy();
	// Go to the home page.
	header('Location: ./');
	exit();
}

⭐ Design the user dashboard (interface) consists of:

⭐ Design the hidden checkout form to pass the estimated total cart amount to the checkout page.


<form id="hidden_checkout" action="checkout.php" method="post" target="_blank" style="display:none;">
<input id="checkout_price" name="checkout_price" type="hidden" value="$0">
</form>

πŸ“ dashboard_updates.php

⭐ Include the class.php file.

⭐ If the user did not sign into an existing account, redirect the user to the home page.


include_once "class.php";

if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

⭐ Define the _product object of the product class extending the user class.


$_product = new product();
$_product->__init__($conn);

⭐ If the update URL (GET) parameter is received:

⭐ Retrieve the product information of the current cart from the user's database table as a list.

⭐ Generate HTML table rows consisting of the retrieved product information and the Ask ChatGPT button redirecting to the ChatGPT recommendations page of the product.

⭐ Then, create a JSON object from the generated HTML table rows and the evaluated total cart price.

⭐ Print the created JSON object.


if(isset($_GET["update"])){
	$p_barcode = []; $p_name = []; $p_ingredients = []; $p_price = []; $p_number = []; $total_price = [];
	list($p_barcode, $p_name, $p_ingredients, $p_price, $p_number, $total_price) = $_product->get_current_products($_SESSION["user_token"]);
	$list = "<tr><th>Product</th><th>Barcode</th><th>Ingredients</th><th>Price($)</th><th>Unit</th><th>Counsel</th></tr>";
	for($i=0; $i<count($p_barcode); $i++){
		$list .= '<tr>
					<td>'.$p_name[$i].'</td>
					<td>'.$p_barcode[$i].'</td>
					<td>'.$p_ingredients[$i].'</td>
					<td>'.$p_price[$i].'</td>
					<td>'.$p_number[$i].'</td>
					<td><a href="ChatGPT/?product='.$p_name[$i].'" target="_blank"><button><i class="fa-solid fa-comment-dots"></i> Ask ChatGPT</button></a></td>
				  </tr>
				 ';   
	}
	
	// Create a JSON object from the generated HTML table rows consisting of the product information and the evaluated total cart price.
	$data = array("list" => $list, "total_price" => "$".$total_price["total_price"]);
	$j_data = json_encode($data);
    
	// Return the recently generated JSON object.
	echo($j_data);
}

πŸ“ index.js (jQuery and AJAX)

⭐ Every 5 seconds, make an HTTP GET request to the dashboard_updates.php file.

⭐ Then, decode the received JSON object to obtain the HTML table rows consisting of the product information of the current cart from the user's database table and the estimated total cart price.

⭐ Assign the obtained table rows and the total cart price to the corresponding HTML elements on the user dashboard so as to showcase the recent products added to the cart and inform the customer of the calculated total cart price (amount).


setInterval(function(){
	$.ajax({
		url: "./assets/dashboard_updates.php?update",
		type: "GET",
		success: (response) => {
			// Decode the obtained JSON object.
			const data = JSON.parse(response);
			// Assign the HTML table rows as the current product list in the cart. 
			$(".products table").html(data.list);
			// Assign the evaluated total cart price (amount).
			$("#total_price").html(data.total_price);
		}
	});
}, 5000);

⭐ When the user clicks the Checkout button, redirect the customer to the checkout page and transfer the evaluated total cart price via the hidden HTML form.


$(".info").on("click", "#checkout", () => {
	var total_price = $("#total_price").text();
	$("#checkout_price").val(total_price);
	$("#hidden_checkout").submit();
});

project-image
Figure - 91.113


project-image
Figure - 91.114


project-image
Figure - 91.115

Step 4.3: Displaying the previous orders when requested

πŸ“ previous_orders.php

⭐ Include the class.php file.


include_once "assets/class.php";

⭐ Define the _product object of the product class extending the user class.


$_product = new product();
$_product->__init__($conn);

⭐ If the user signed into an existing account successfully, proceed to load the previous orders page.


if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

⭐ Design the previous orders page to display the purchased product list of each previous order as an HTML list.


$_product->get_previous_orders($_SESSION["user_token"]);

project-image
Figure - 91.116

Step 4.4: Designing the checkout (payment) page

After creating the checkout page and HTML form to get the payment information (credit/debit card), I did not integrate a payment processing service since I do not have partner access as a business owner to general payment services, such as Visa.

Nonetheless, it is easy to send the received payment information to a payment processing service if you have a local business with partner access, as explained at Visa Checkout.

πŸ“ checkout.php

⭐ Include the class.php file.


include_once "assets/class.php";

⭐ Define the _product object of the product class extending the user class.


$_product = new product();
$_product->__init__($conn);

⭐ If the user signed into an existing account successfully, proceed to load the checkout page.


if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

⭐ If the user places an order by entering the requested credit/debit card information, execute the user_checkout function, explained in the previous steps.


if(isset($_GET["c_number"]) && isset($_GET["c_name"]) && isset($_GET["c_date"]) && isset($_GET["c_verify"])){
	$_product->user_checkout($_SESSION["user_token"], $_SESSION["email"], $_SESSION["name"]);
}

⭐ Design the checkout page and the checkout HTML form.

⭐ Check whether the total cart price variable is received via the hidden HTML form on the dashboard page.


if(isset($_POST["checkout_price"])){
	echo $_POST["checkout_price"];
}else{
	echo "$0";
}

project-image
Figure - 91.117

Step 4.5: Integrating ChatGPT into the web application via the OpenAI API

As explained in the previous steps, I employed the OpenAI API to generate ChatGPT-powered recommendations for the given product instead of coercing local businesses to construct large data sets of their products.

In that regard, I was able to provide insightful product suggestions by merely making API calls to the OpenAI API.

While making a cURL call (HTTP request) to the OpenAI API, the web application passes five questions for the gpt-3.5-turbo model:

Since ChatGPT accepts system commands in the text format to alter the generated response structure, I added a simple command to add the exact question with the question number before each answer. In that regard, I was able to modify the retrieved response from ChatGPT to add line breaks between answers.


{"role": "user", "content": "Please add the exact question at the beginning of the answer with the question number."}

πŸ“ index.php

⭐ Include the class.php file.


include_once "../assets/class.php";

⭐ Define the _product object of the product class extending the user class.


$_product = new product();
$_product->__init__($conn);

⭐ If the user signed into an existing account successfully, proceed to load the ChatGPT recommendations page.


if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

⭐ If the customer requests to get ChatGPT-powered recommendations for an eligible product, execute the chatgpt_get_suggestion function so as to make a cURL call (HTTP request) to the OpenAI API in order to get suggestions regarding the given product from ChatGPT.


$suggestions = "<h2>Please enter a product name.</h2>";
$questions = ["Please enter a product name."];
if(isset($_GET["product"]) && $_GET["product"] != "Not Found!"){
	list($suggestions, $questions) = $_product->chatgpt_get_suggestion($_GET["product"]);
}

⭐ Design the ChatGPT recommendations page by utilizing the ChatGPT theme.

⭐ Print the defined questions and the modified ChatGPT-powered answers to inform the customer.


for($i=0;$i<count($questions);$i++){
	echo '<h2><i class="fa-regular fa-comment-dots"></i> '.$questions[$i].'</h2>';
}

...

echo $suggestions;

πŸ“ index.css

You can download and inspect the index.css file of the ChatGPT folder below.

project-image
Figure - 91.118


project-image
Figure - 91.119

Step 4.6: Setting and running the e-commerce web application on LattePanda 3 Delta

Since I wanted to build a budget-friendly AIoT shopping assistant not dependent on cloud or hosting services, I decided to host my web application on LattePanda 3 Delta 864. Therefore, I needed to set up a LAMP web server.

LattePanda 3 Delta is a pocket-sized hackable computer that provides ultra performance with the Intel 11th-generation Celeron N5105 processor.

Plausibly, LattePanda 3 Delta can run the XAMPP application. So, it is effortless to create a server with a MariaDB database on LattePanda 3 Delta.

Also, I utilized a DFRobot 8.9" IPS touch display to demonstrate my e-commerce web application features.

project-image
Figure - 91.120

#️⃣ First of all, install and set up the XAMPP application.

#️⃣ Then, go to the XAMPP Control Panel and click the MySQL Admin button.

#️⃣ Once the phpMyAdmin tool pops up, create a new database named shopping_assistant_users.

project-image
Figure - 91.121


project-image
Figure - 91.122

#️⃣ After adding the database successfully, go to the SQL section to create a MySQL database table named users with the required data fields.


CREATE TABLE `users`(		
	id int AUTO_INCREMENT PRIMARY KEY NOT NULL,
	firstname varchar(255) NOT NULL,
	lastname varchar(255) NOT NULL,
	username varchar(255) NOT NULL,
    `password` varchar(255) NOT NULL,
	email varchar(255) NOT NULL,
    token varchar(255) NOT NULL,
	qr_code varchar(255) NOT NULL,
    successful_order int NOT NULL
);

project-image
Figure - 91.123


project-image
Figure - 91.124


project-image
Figure - 91.125

Step 5: Configuring NUCLEO-F439ZI with the modified W5300 TOE SHIELD firmware

Since the STM32 Nucleo-144 boards need to be flashed with the compatible Wiznet5K firmware to control W5300 TOE SHIELD and program it in MicroPython, I had to upload the official Wiznet5K firmware to the NUCLEO-F439ZI development board.

Plausibly, WIZnet provides official pre-built firmwares for these Nucleo-144 boards:

Although there is a pre-built firmware for NUCLEO-F439ZI, I decided to compile my Wiznet5K MicroPython firmware in order to change some minor pin settings due to overlapping.

You can download my updated firmware below β€” firmware_updated.hex.

Since I have a secondary operating system (Ubuntu) on my LattePanda 3 Delta, I was able to use it to build my Wiznet5K firmware.

#️⃣ First, open the terminal to install the required Python modules.


    sudo apt-get install git
    sudo apt-get install make
    sudo apt-get install gcc
	sudo apt-get install gcc-arm-none-eabi

#️⃣ Then, clone the official Wiznet5K MicroPython project from GitHub by entering the following Git command.


	git clone https://github.com/Wiznet/W5300-TOE-MicroPython.git

project-image
Figure - 91.126

#️⃣ After installing the MicroPython project, I modified some pin settings for NUCLEO-F439ZI in the mpconfigboard.h file.

/libraries/ports/stm32/boards/NUCLEO_F439ZI/mpconfigboard.h

project-image
Figure - 91.127


project-image
Figure - 91.128

#️⃣ Finally, I built my modified firmware for the NUCLEO-F439ZI development board attached to W5300 TOE SHIELD. Do not forget to change the board name and the chip model for different combinations.


	cd W5300-TOE-MicroPython/libraries/mpy-cross
  
	make

	cd ../ports/stm32
	
	make BOARD=NUCLEO-F439ZI MICROPY_PY_WIZNET5K=5300

project-image
Figure - 91.129


project-image
Figure - 91.130


project-image
Figure - 91.131

#️⃣ Navigate to the build-NUCLEO-F439ZI folder available in /libraries/ports/stm32, and the compiled firmware file should be there β€” firmware.hex.

project-image
Figure - 91.132

After building my firmware version successfully, I employed the STM32CubeProgrammer on Windows to upload the modified firmware to NUCLEO-F439ZI.

#️⃣ First, click the Open File button to open the modified firmware file β€” firmware_updated.hex.

project-image
Figure - 91.133


project-image
Figure - 91.134

#️⃣ Then, connect NUCLEO-F439ZI to the computer via a USB cable and click the Connect button.

project-image
Figure - 91.135

#️⃣ Finally, click the Download button to flash NUCLEO-F439ZI with the given firmware.

project-image
Figure - 91.136

Step 6: Installing and modifying MicroPython libraries on NUCLEO-F439ZI

After flashing my Wiznet5K firmware to the NUCLEO-F439ZI development board, I utilized Thonny to program NUCLEO-F439ZI attached to W5300 TOE SHIELD.

However, since the Wiznet5K firmware does not include the required modules for some components, I needed to upload modules to NUCLEO-F439ZI and modify them if the default settings are not compatible with W5300 TOE SHIELD.

⚠️ Notice:

Even though the W5300 TOE SHIELD datasheet mentions one available hardware serial port (USART6), NUCLEO-F439ZI has two eligible hardware serial ports (USART2 and USART6), as shown in the STM32 Nucleo-144 user manual. Therefore, I was able to connect two components (the scanner module and the thermal printer), requiring serial communication.

#️⃣ To program NUCLEO-F439ZI on Thonny, go to Tools ➑ Select interpreter... and choose MicroPython (generic) with the given STLink Virtual COM Port.

#️⃣ Then, via Thonny's file explorer, select the MicroPython device's flash folder to upload or update files.

project-image
Figure - 91.137


project-image
Figure - 91.138


project-image
Figure - 91.139


project-image
Figure - 91.140

#️⃣ To make HTTP requests easily, upload the urequests.py file (module) to NUCLEO-F439ZI via Thonny's file explorer.

#️⃣ To get the pre-defined functions for the W5300 chip, upload the wiznet_conf.py file.

#️⃣ To control the SSD1309 transparent display, upload the ssd1309.py file.

#️⃣ To display fonts provided by the ssd1309 module on the transparent screen, upload the xglcd_font.py file.

project-image
Figure - 91.141


project-image
Figure - 91.142


project-image
Figure - 91.143


project-image
Figure - 91.144

#️⃣ To control the tiny (embedded) thermal printer, upload the Adafruit_Thermal.py file.

Since this module utilizes USART1 as the default hardware serial port and it is not available on NUCLEO-F439ZI, I needed to modify the Adafruit_Thermal.py file to utilize USART6 instead. I also added the required port settings for the thermal printer.


self.uart = UART(6, 9600, bits=8, parity=None, stop=1)

I also modified the data_end parameter for the printed bitmap images to align them perfectly.


data_end = header.file_size - 0 

project-image
Figure - 91.145


project-image
Figure - 91.146


project-image
Figure - 91.147

#️⃣ Finally, create a folder named assets on the device to upload the required image and font files.

You can download the assets folder with the added files below.

project-image
Figure - 91.148


project-image
Figure - 91.149

Step 7: Programming NUCLEO-F439ZI to scan data and communicate with the e-commerce application

After uploading all required MicroPython modules and files to NUCLEO-F439ZI, I created the main.py file to program W5300 TOE SHIELD. Since NUCLEO-F439ZI runs the main.py file automatically, I did not encounter any problems while initiating the shopping assistant (device).

project-image
Figure - 91.150

⭐ Include the required modules.

⭐ Define the shopping_assistant class and its functions.

⭐ In the __init__ function:

⭐ Define the W5300 object and the static IP address settings depending on your network settings.

⭐ Define the required print and hardware serial port settings (USART6) for the tiny (embedded) thermal printer.

⭐ Define the required hardware serial port settings (USART2) for the GM77 barcode and QR code scanner.

⭐ Define the required SPI settings for the SSD1309 OLED transparent display.

⭐ Get the fonts stored in the assets folder.

⭐ Define the control button pins.

⭐ Define the RGB LED pins.

⭐ Initialize the SSD1309 OLED transparent display.


class shopping_assistant:
    def __init__(self):
        # Define the W5300 object and the static IP address settings.
        self.w5300 = wiznet5k_w5300()
        self.w5300.w5300_set_ip('0.0.0.0','0.0.0.0','0.0.0.0','0.0.0.0')
        # Define the required print and hardware serial port settings for the tiny (embedded) thermal printer.
        self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
        # Define the required hardware serial port settings for the GM77 barcode and QR code scanner.
        self.scanner = UART(2, 9600, bits=8, parity=None, stop=1)
        self.user_token = ""
        self.new_product_barcode = ""
        # Define the required settings for the SSD1309 OLED transparent display. (SCK: PA5, MOSI: PA7)
        spi = SPI(1, SPI.MASTER, baudrate=10000000)
        self.display = Display(spi, dc=Pin("A4"), cs=Pin("C6"), rst=Pin("C7"))
        # Define the given fonts.
        self.bold = XglcdFont('assets/Unispace12x24.c', 12, 24)
        self.light = XglcdFont('assets/Bally5x8.c', 5, 8)
        # Define the control button pins.
        self.button_A = Pin("C0", Pin.IN, Pin.PULL_UP)
        self.button_B = Pin("C3", Pin.IN, Pin.PULL_UP)
        self.button_C = Pin("C2", Pin.IN, Pin.PULL_UP)
        # Define the RGB LED pins.
        self.red = Pin("A1", Pin.OUT_PP)
        self.blue = Pin("A0", Pin.OUT_PP)
        self.green = Pin("F9", Pin.OUT_PP)
        self.adjust_color([0,0,0])
        sleep(2)
        # Initialize the SSD1309 OLED transparent display.
        self.product_menu_activate = False
        self.change_layout("home")

⭐ In the read_QR_barcode function:

⭐ If the scanner module reads a barcode or QR code successfully, get the data packet via serial communication.

⭐ Decode and modify the received data packet to obtain the scanned information.

⭐ If the scanned information includes the user command and the unique user token, activate the cart and register the given user token.

⭐ If the scanned information includes the finished command and the received user token corresponds to the registered user token, discard the registered user token to deactivate the cart.

⭐ Then, notify the customer via the thermal printer.

⭐ If the cart is activated and the scanned information includes a product barcode, save the given product barcode and initiate the product menu (interface) to allow the customer to select a command (add or remove) before transferring the given barcode to the e-commerce web application.


    def read_QR_barcode(self):
        scanned_data = self.scanner.readline()
        # Decode and modify the received data packet to obtain the user (account) token with the given command or the new product barcode.
        if(type(scanned_data) is not type(None)):
            decoded_data = scanned_data.decode("utf_8")
            decoded_data = decoded_data.replace("\r", "")
            print("\nScanned: " + decoded_data)
            # Get the user token.
            if(decoded_data.find("user%") >= 0):
                self.user_token = decoded_data.split("%")[1]
                print("\nYour cart registered successfully!")
                print("Registered Token: " + self.user_token)
                self.change_layout("register")
                sleep(10)
                self.change_layout("scan")
            # After getting the finished command, discard the registered user token to deactivate the cart.
            elif(decoded_data.find("finished%") >= 0):
                given_token = decoded_data.split("%")[1]
                if(given_token == self.user_token):
                    print("\nPayment Received Successfully!")
                    print("Your cart discarded successfully!")
                    self.change_layout("payment")
                    sleep(2)
                    # Notify the customer via the thermal printer.
                    self.print_status(True)
                    self.change_layout("home")
                    self.user_token = ""
            # Get the product barcode.
            else:
                if(self.user_token != ""):
                    self.new_product_barcode = decoded_data
                    print("New Product Barcode: " + self.new_product_barcode)
                    self.product_menu_activate = True
                    if(self.product_menu_activate == True):
                        self.change_layout("barcode")
                        while(self.product_menu_activate == True):
                            self.product_menu()
                else:
                    print("\nPlease scan your unique account QR code to register your cart.") 
        sleep(1)

⭐ In the make_get_request function:

⭐ Make an HTTP GET request to the e-commerce web application by utilizing the registered user token, the given barcode, and the selected command by the customer.

http://192.168.1.22/AIoT_Shopping_Assistant/assets/barcode.php?table=kutlu123_!t3*b1450zKD&barcode=80135876&com=add

http://192.168.1.22/AIoT_Shopping_Assistant/assets/barcode.php?table=kutlu123_!t3*b1450zKD&barcode=80135876&com=remove

⭐ Then, print the response of the web application.


    def make_get_request(self, barcode, com="add"):
        path = "http://192.168.1.22/AIoT_Shopping_Assistant/assets/barcode.php?table={}&barcode={}&com={}".format(self.user_token, barcode, com)
        # Make an HTTP GET request.
        request = urequests.get(path)
        print("\nURL => "+path)
        print("App Response => ")
        print(request.text)
        sleep(1)

⭐ In the product_menu function:

⭐ If the user presses the control button (A), make an HTTP GET request to the web application by adding the add command.

⭐ If the user presses the control button (B), make an HTTP GET request to the web application by adding the remove command.

⭐ After making the request, close the product menu (interface).


    def product_menu(self):
        print("Press: Button (A) -> Add | Button (B) -> Remove")
        sleep(1)
        if(self.button_A.value() == False):
            self.make_get_request(self.new_product_barcode, "add")
            self.change_layout("add")
            sleep(10)
            self.change_layout("scan")
            self.product_menu_activate = False
        if(self.button_B.value() == False):
            self.make_get_request(self.new_product_barcode, "remove")
            self.change_layout("remove")
            sleep(10)
            self.change_layout("scan")
            self.product_menu_activate = False

⭐ In the print_status function:

⭐ If the user presses the control button (C), inform the user of the current device status by printing a receipt via the tiny (embedded) thermal printer.

⭐ Change the thermal printer hardware settings (heat time, heat dots, heat interval) to obtain smoother prints depending on the targeted print feature (image, text, inverted, etc.).

⭐ Also, if the customer places an order successfully and scans the payment confirmation QR code, notify the user by printing an invoice via the thermal printer.


    def print_status(self, payment=False):
        if(self.button_C.value() == False):
            print("\nPrinting the device status...")
            # Change the thermal printer hardware settings to obtain smoother prints depending on the targeted feature.
            if(self.user_token == ""):
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_chatgpt.bmp')
                self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
                self.printer.justify('C')
                self.printer.setSize('L')
                self.printer.println("AIoT")
                self.printer.println("Shopping")
                self.printer.println("Assistant")
                self.printer.println("Status\n\n")
                self.printer.justify('L')
                self.printer.setSize('S')
                self.printer.boldOn()
                self.printer.println("Please scan")
                self.printer.println("your unique")
                self.printer.println("account QR code")
                self.printer.println("to activate your")
                self.printer.println("cart and start")
                self.printer.println("shopping!\n\n")
                self.printer.boldOff()
                self.printer.justify('R')
                self.printer.setSize('M')
                self.printer.inverseOn()
                self.printer.println(" Have a ")
                self.printer.println(" Great ")
                self.printer.println(" Day :) \n")
                self.printer.inverseOff()
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_scan.bmp')
                self.printer.feed(5)
            elif(self.user_token != ""):
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_chatgpt.bmp')
                self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
                self.printer.justify('C')
                self.printer.setSize('L')
                self.printer.println("AIoT")
                self.printer.println("Shopping")
                self.printer.println("Assistant")
                self.printer.println("Status\n\n")
                self.printer.justify('L')
                self.printer.setSize('S')
                self.printer.boldOn()
                self.printer.println("Your cart is")
                self.printer.println("registered and")
                self.printer.println("initialized")
                self.printer.println("successfully.")
                self.printer.println("Please scan a")
                self.printer.println("product barcode")
                self.printer.println("to change")
                self.printer.println("the cart")
                self.printer.println("items!\n\n")
                self.printer.println("Registered Token:")
                self.printer.doubleHeightOn()
                self.printer.println(self.user_token)
                self.printer.println("\n")
                self.printer.doubleHeightOff()
                self.printer.boldOff()
                self.printer.justify('R')
                self.printer.setSize('M')
                self.printer.inverseOn()
                self.printer.println(" Have a ")
                self.printer.println(" Great ")
                self.printer.println(" Day :) \n")
                self.printer.inverseOff()
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_cart.bmp')
                self.printer.feed(5)
        # If the customer places an order successfully:
        if(payment == True):
            self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
            self.printer.printBMPImage('assets/printer_chatgpt.bmp')
            self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
            self.printer.justify('C')
            self.printer.setSize('L')
            self.printer.println("AIoT")
            self.printer.println("Shopping")
            self.printer.println("Assistant")
            self.printer.println("Status\n\n")
            self.printer.justify('L')
            self.printer.setSize('S')
            self.printer.boldOn()
            self.printer.println("Payment for")
            self.printer.println("your latest")
            self.printer.println("order is received")
            self.printer.println("successfully via")
            self.printer.println("your dashboard")
            self.printer.println("on the web")
            self.printer.println("application.")
            self.printer.println("Please scan")
            self.printer.println("your account")
            self.printer.println("QR code to")
            self.printer.println("activate your")
            self.printer.println("cart for")
            self.printer.println("your next")
            self.printer.println("order!\n\n")
            self.printer.println("Removed Token:")
            self.printer.doubleHeightOn()
            self.printer.println(self.user_token)
            self.printer.println("\n")
            self.printer.doubleHeightOff()
            self.printer.boldOff()
            self.printer.justify('R')
            self.printer.setSize('M')
            self.printer.inverseOn()
            self.printer.println(" Have a ")
            self.printer.println(" Great ")
            self.printer.println(" Day :) \n")
            self.printer.inverseOff()
            self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
            self.printer.printBMPImage('assets/printer_payment.bmp')
            self.printer.feed(5)

⭐ In the change_layout function:

⭐ Change the layout on the SSD1309 OLED transparent display depending on the ongoing operation.

⭐ Display notifications with the assigned monochrome images.

⭐ Adjust the RGB LED color for each layout differently.


    def change_layout(self, activated):
        self.display.clear_buffers()
        if(activated == "home"):
            self.adjust_color([0,1,0])
            self.display.draw_bitmap("assets/cart.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Please scan", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "your unique", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "account QR ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "code to    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "activate   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "your cart! ", self.light, invert=True)
        elif(activated == "scan"):
            self.adjust_color([0,1,1])
            self.display.draw_bitmap("assets/chatgpt.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Please scan", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "a product  ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "barcode to ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "change     ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "the cart   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "items!     ", self.light, invert=True)
        elif(activated == "register"):
            self.adjust_color([0,0,1])
            self.display.draw_bitmap("assets/registered.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Account QR ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "code       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "registered ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "and your   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "cart       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "is ready!  ", self.light, invert=True)        
        elif(activated == "barcode"):
            self.adjust_color([1,0,1])
            self.display.draw_bitmap("assets/barcode.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Press:     ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "           ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "Button (A) ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "-> Add     ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "Button (B) ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "-> Remove  ", self.light, invert=True)
        elif(activated == "add"):
            self.adjust_color([0,0,1])
            self.display.draw_bitmap("assets/add.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Given      ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "product    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "added to   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "your       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "registered ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "cart!      ", self.light, invert=True)
        elif(activated == "remove"):
            self.adjust_color([1,0,0])
            self.display.draw_bitmap("assets/remove.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Given      ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "product    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "removed    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "from your  ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "registered ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "cart!      ", self.light, invert=True)
        elif(activated == "payment"):
            self.adjust_color([1,1,1])
            self.display.draw_bitmap("assets/payment.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Order      ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "payment    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "received   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "and your   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "cart       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "deactivated", self.light, invert=True)            
        self.display.present()
        sleep(1)

⭐ Define the assistant object of the shopping_assistant class.

⭐ Finally, initiate the AIoT shopping assistant (device).


assistant = shopping_assistant()

while True:
    assistant.start()

project-image
Figure - 91.151


project-image
Figure - 91.152


project-image
Figure - 91.153


project-image
Figure - 91.154


project-image
Figure - 91.155


project-image
Figure - 91.156

⭐ Furthermore, I modified the w5300_set_ip function in the wiznet_conf.py file so as to print the Ethernet connection status on the shell.


    def w5300_set_ip (self, ip_addr, gw_addr, netmask, dns_svr):
        self.nic.ifconfig((ip_addr, gw_addr, netmask, dns_svr))
        print("W5300 STATIC IP: ", self.nic.ifconfig())
        if(self.nic.isconnected() == False):
            print("\nW5300: Ethernet connection problem!")
        else:
            print("\nW5300: Connected successfully!")
        pyb.delay(500)

project-image
Figure - 91.157

Step 8: Providing a wholesome e-commerce customer experience with AI-based suggestions

After programming W5300 TOE SHIELD attached to NUCLEO-F439ZI in MicroPython and hosting the e-commerce web application on LattePanda 3 Delta, the AIoT shopping assistant is ready to help customers while physical store shopping.

πŸ›οΈπŸ“©πŸ“² When the customer provides the required information on the sign-up form on the home page, the e-commerce web application generates a unique account (user) token, creates a unique database table for the new user, and adds the provided information to the MariaDB database.

project-image
Figure - 91.158


project-image
Figure - 91.159

πŸ›οΈπŸ“©πŸ“² After saving new user information to the MariaDB database, the e-commerce application generates an account verification QR code and sends an HTML email to the customer's registered email address via Brevo's Email API, including the account verification QR code.

project-image
Figure - 91.160


project-image
Figure - 91.161


project-image
Figure - 91.162


project-image
Figure - 91.163


project-image
Figure - 91.164

πŸ›οΈπŸ“©πŸ“² Then, the e-commerce application redirects the customer to the user dashboard.

project-image
Figure - 91.165

πŸ›οΈπŸ“©πŸ“² If there is no product in the cart, the e-commerce application informs the customer on the user dashboard and the ChatGPT recommendations page.

project-image
Figure - 91.166


project-image
Figure - 91.167

πŸ›οΈπŸ“©πŸ“² The shopping assistant (device) waits for the customer to scan the unique account verification QR code to activate the cart. Also, it turns the RGB LED to blue to inform the user the fact that the cart is not activated yet.

project-image
Figure - 91.168


project-image
Figure - 91.169

πŸ›οΈπŸ“©πŸ“² After scanning the account verification QR code, the shopping assistant registers the obtained user token and turns the RGB LED to green.

project-image
Figure - 91.170


project-image
Figure - 91.171

πŸ›οΈπŸ“©πŸ“² After activating the cart, the shopping assistant waits for the customer to scan a product barcode and turns the RGB LED to cyan.

project-image
Figure - 91.172


project-image
Figure - 91.173

πŸ›οΈπŸ“©πŸ“² When the customer scans a product barcode, the shopping assistant initiates the product menu (interface) to allow the customer to select a command before sending the scanned product barcode to the e-commerce application. Also, it turns the RGB LED to yellow until the customer chooses a command.

project-image
Figure - 91.174


project-image
Figure - 91.175

πŸ›οΈπŸ“©πŸ“² After scanning a product barcode, if the user presses the control button (A), the shopping assistant turns the RGB LED to green and makes an HTTP GET request to the e-commerce application by adding the add command.

πŸ›οΈπŸ“©πŸ“² Then, the e-commerce application obtains the product information of the given barcode by employing the Open Food Facts JSON API and saves the obtained product information to the user's unique database table.

πŸ›οΈπŸ“©πŸ“² If the product is already added to the cart, the application increments the given product's cart number by 1.

project-image
Figure - 91.176


project-image
Figure - 91.177

πŸ›οΈπŸ“©πŸ“² After scanning a product barcode, if the user presses the control button (B), the shopping assistant turns the RGB LED to red and makes an HTTP GET request to the e-commerce application by adding the remove command.

πŸ›οΈπŸ“©πŸ“² Then, the e-commerce application discards the given product from the user's database table.

project-image
Figure - 91.178


project-image
Figure - 91.179

πŸ›οΈπŸ“©πŸ“² According to the received HTTP GET requests from the shopping assistant, the e-commerce application changes the product information in the user's database table.

project-image
Figure - 91.180

πŸ›οΈπŸ“©πŸ“² Every 5 seconds, the e-commerce application updates the HTML table rows consisting of the product information of the current cart in the user's database table and the estimated total cart price to inform the user automatically.

project-image
Figure - 91.181


project-image
Figure - 91.182

πŸ›οΈπŸ“©πŸ“² After adding products to the cart, the e-commerce application allows the customer to generate ChatGPT-powered suggestions for each product.

πŸ›οΈπŸ“©πŸ“² When the customer clicks the Ask ChatGPT button, the e-commerce application makes a cURL call to the OpenAI API to generate AI-based recommendations for the given product.

πŸ›οΈπŸ“©πŸ“² After decoding the API response, the e-commerce application redirects the customer to the ChatGPT recommendations page consisting of the product questions and the provided ChatGPT suggestions.

πŸ“Œ ChatGPT Recommendations Page for Nutella

➑️ Questions

➑️ Suggestions

project-image
Figure - 91.183


project-image
Figure - 91.184


project-image
Figure - 91.185


project-image
Figure - 91.186


project-image
Figure - 91.187

πŸ›οΈπŸ“©πŸ“² When the customer clicks the Checkout button, the e-commerce application redirects the user to the checkout page and transfers the evaluated total cart price (amount).

project-image
Figure - 91.188

πŸ›οΈπŸ“©πŸ“² Then, if the customer provides the required credit/debit card information correctly, the e-commerce application places the order, generates a unique payment confirmation QR code, updates the order number in the database, and redirects the customer to the user dashboard.

πŸ›οΈπŸ“©πŸ“² Since the order number is updated, the e-commerce application is ready to add products to the cart again for the next order.

project-image
Figure - 91.189


project-image
Figure - 91.190


project-image
Figure - 91.191

πŸ›οΈπŸ“©πŸ“² Then, the application sends an HTML email to the customer's registered email address, including the payment confirmation QR code.

project-image
Figure - 91.192


project-image
Figure - 91.193


project-image
Figure - 91.194


project-image
Figure - 91.195

πŸ›οΈπŸ“©πŸ“² When the customer scans the payment confirmation QR code, the shopping assistant confirms the payment, deactivates the cart, turns the RGB LED to white, and prints an invoice via the tiny thermal printer to inform the user.

project-image
Figure - 91.196


project-image
Figure - 91.197


project-image
Figure - 91.198


project-image
Figure - 91.199

πŸ›οΈπŸ“©πŸ“² When the customer clicks the Previous Orders button, the e-commerce application redirects the customer to the previous orders page. The application is capable of displaying all previous orders as separate HTML lists since it saves product information with the corresponding order number.

project-image
Figure - 91.200


project-image
Figure - 91.201


project-image
Figure - 91.202


project-image
Figure - 91.203

πŸ›οΈπŸ“©πŸ“² If there are no previous orders, the application notifies the customer on the previous orders page.

project-image
Figure - 91.204

πŸ›οΈπŸ“©πŸ“² If the customer clicks the Logout button, the e-commerce application signs out of the user account and redirects the customer to the home page.

project-image
Figure - 91.205


project-image
Figure - 91.206

πŸ›οΈπŸ“©πŸ“² On the home page, the e-commerce application redirects the user to the sign-in page when the customer clicks Already have an account?

πŸ›οΈπŸ“©πŸ“² If the user provides accurate account information on the sign-in form, the e-commerce application signs into an existing account and redirects the customer to the user dashboard.

project-image
Figure - 91.207


project-image
Figure - 91.208


project-image
Figure - 91.209

πŸ›οΈπŸ“©πŸ“² While the shopping assistant operates, NUCLEO-F439ZI prints notifications, the Ethernet connection status, the scanned information, and the device status on the shell for debugging.

project-image
Figure - 91.210


project-image
Figure - 91.211


project-image
Figure - 91.212


project-image
Figure - 91.213


project-image
Figure - 91.214


project-image
Figure - 91.215


project-image
Figure - 91.216


project-image
Figure - 91.217


project-image
Figure - 91.218


project-image
Figure - 91.219

πŸ›οΈπŸ“©πŸ“² Furthermore, if the customer presses the control button (C), the shopping assistant prints a receipt via the thermal printer to inform the user of the current device status.

project-image
Figure - 91.220


project-image
Figure - 91.221

As mentioned earlier, I added a small gift for a first-time customer trying the shopping assistant. I printed a Togepi figure with my 3D printer as the gift since it represents good luck and fortune :)

project-image
Figure - 91.222


project-image
Figure - 91.223

Videos and Conclusion



Further Discussions

By applying budget-friendly shopping assistants providing AI-based product recommendations, we can achieve to:

πŸ›οΈπŸ“©πŸ“² bridge the gap between e-commerce and physical store shopping,

πŸ›οΈπŸ“©πŸ“² enrich the customer experience,

πŸ›οΈπŸ“©πŸ“² increase the profit margin,

πŸ›οΈπŸ“©πŸ“² improve local businesses' marketing strategies,

πŸ›οΈπŸ“©πŸ“² reach the target audience effortlessly.

project-image
Figure - 91.224


project-image
Figure - 91.225

Code

main.py

Download



# AI-driven IoT Shopping Assistant w/ ChatGPT
#
# STM32 Nucleo-144 NUCLEO-F439ZI
#
# By Kutluhan Aktar
#
# Activate your cart with a QR code sent via email by the web app, update product info by scanning barcodes,
# and get shopping tips from ChatGPT. 
# 
#
# For more information:
# https://www.theamplituhedron.com/projects/AI_assisted_IoT_Shopping_Assistant_w_ChatGPT

from usocket import socket
from wiznet_conf import wiznet5k_w5300
import urequests
import network
from pyb import Pin, UART, SPI
from ssd1309 import Display
from xglcd_font import XglcdFont
from Adafruit_Thermal import *
from time import sleep

# Create the shopping_assistant class and define its functions. 
class shopping_assistant:
    def __init__(self):
        # Define the W5300 object and the static IP address settings.
        self.w5300 = wiznet5k_w5300()
        self.w5300.w5300_set_ip('0.0.0.0','0.0.0.0','0.0.0.0','0.0.0.0')
        # Define the required print and hardware serial port settings for the tiny (embedded) thermal printer.
        self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
        # Define the required hardware serial port settings for the GM77 barcode and QR code scanner.
        self.scanner = UART(2, 9600, bits=8, parity=None, stop=1)
        self.user_token = ""
        self.new_product_barcode = ""
        # Define the required settings for the SSD1309 OLED transparent display. (SCK: PA5, MOSI: PA7)
        spi = SPI(1, SPI.MASTER, baudrate=10000000)
        self.display = Display(spi, dc=Pin("A4"), cs=Pin("C6"), rst=Pin("C7"))
        # Define the given fonts.
        self.bold = XglcdFont('assets/Unispace12x24.c', 12, 24)
        self.light = XglcdFont('assets/Bally5x8.c', 5, 8)
        # Define the control button pins.
        self.button_A = Pin("C0", Pin.IN, Pin.PULL_UP)
        self.button_B = Pin("C3", Pin.IN, Pin.PULL_UP)
        self.button_C = Pin("C2", Pin.IN, Pin.PULL_UP)
        # Define the RGB LED pins.
        self.red = Pin("A1", Pin.OUT_PP)
        self.blue = Pin("A0", Pin.OUT_PP)
        self.green = Pin("F9", Pin.OUT_PP)
        self.adjust_color([0,0,0])
        sleep(2)
        # Initialize the SSD1309 OLED transparent display.
        self.product_menu_activate = False
        self.change_layout("home")

    # If the scanner module reads a barcode or QR code successfully, decode the scanned data.
    def read_QR_barcode(self):
        scanned_data = self.scanner.readline()
        # Decode and modify the received data packet to obtain the user (account) token with the given command or the new product barcode.
        if(type(scanned_data) is not type(None)):
            decoded_data = scanned_data.decode("utf_8")
            decoded_data = decoded_data.replace("\r", "")
            print("\nScanned: " + decoded_data)
            # Get the user token.
            if(decoded_data.find("user%") >= 0):
                self.user_token = decoded_data.split("%")[1]
                print("\nYour cart registered successfully!")
                print("Registered Token: " + self.user_token)
                self.change_layout("register")
                sleep(10)
                self.change_layout("scan")
            # After getting the finished command, discard the registered user token to deactivate the cart.
            elif(decoded_data.find("finished%") >= 0):
                given_token = decoded_data.split("%")[1]
                if(given_token == self.user_token):
                    print("\nPayment Received Successfully!")
                    print("Your cart discarded successfully!")
                    self.change_layout("payment")
                    sleep(2)
                    # Notify the customer via the thermal printer.
                    self.print_status(True)
                    self.change_layout("home")
                    self.user_token = ""
            # Get the product barcode.
            else:
                if(self.user_token != ""):
                    self.new_product_barcode = decoded_data
                    print("New Product Barcode: " + self.new_product_barcode)
                    self.product_menu_activate = True
                    if(self.product_menu_activate == True):
                        self.change_layout("barcode")
                        while(self.product_menu_activate == True):
                            self.product_menu()
                else:
                    print("\nPlease scan your unique account QR code to register your cart.") 
        sleep(1)
        
    # Make an HTTP GET request to the AIoT Shopping Assistant web application with the obtained barcode.
    def make_get_request(self, barcode, com="add"):
        path = "http://192.168.1.22/AIoT_Shopping_Assistant/assets/barcode.php?table={}&barcode={}&com={}".format(self.user_token, barcode, com)
        # Make an HTTP GET request.
        request = urequests.get(path)
        print("\nURL => "+path)
        print("App Response => ")
        print(request.text)
        sleep(1)
    
    # Display the product menu (interface) when the scanner detects a new product barcode.
    def product_menu(self):
        print("Press: Button (A) -> Add | Button (B) -> Remove")
        sleep(1)
        if(self.button_A.value() == False):
            self.make_get_request(self.new_product_barcode, "add")
            self.change_layout("add")
            sleep(10)
            self.change_layout("scan")
            self.product_menu_activate = False
        if(self.button_B.value() == False):
            self.make_get_request(self.new_product_barcode, "remove")
            self.change_layout("remove")
            sleep(10)
            self.change_layout("scan")
            self.product_menu_activate = False
            
    # If requested, print the current device status via the tiny (embedded) thermal printer.
    def print_status(self, payment=False):
        if(self.button_C.value() == False):
            print("\nPrinting the device status...")
            # Change the thermal printer hardware settings to obtain smoother prints depending on the targeted feature.
            if(self.user_token == ""):
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_chatgpt.bmp')
                self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
                self.printer.justify('C')
                self.printer.setSize('L')
                self.printer.println("AIoT")
                self.printer.println("Shopping")
                self.printer.println("Assistant")
                self.printer.println("Status\n\n")
                self.printer.justify('L')
                self.printer.setSize('S')
                self.printer.boldOn()
                self.printer.println("Please scan")
                self.printer.println("your unique")
                self.printer.println("account QR code")
                self.printer.println("to activate your")
                self.printer.println("cart and start")
                self.printer.println("shopping!\n\n")
                self.printer.boldOff()
                self.printer.justify('R')
                self.printer.setSize('M')
                self.printer.inverseOn()
                self.printer.println(" Have a ")
                self.printer.println(" Great ")
                self.printer.println(" Day :) \n")
                self.printer.inverseOff()
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_scan.bmp')
                self.printer.feed(5)
            elif(self.user_token != ""):
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_chatgpt.bmp')
                self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
                self.printer.justify('C')
                self.printer.setSize('L')
                self.printer.println("AIoT")
                self.printer.println("Shopping")
                self.printer.println("Assistant")
                self.printer.println("Status\n\n")
                self.printer.justify('L')
                self.printer.setSize('S')
                self.printer.boldOn()
                self.printer.println("Your cart is")
                self.printer.println("registered and")
                self.printer.println("initialized")
                self.printer.println("successfully.")
                self.printer.println("Please scan a")
                self.printer.println("product barcode")
                self.printer.println("to change")
                self.printer.println("the cart")
                self.printer.println("items!\n\n")
                self.printer.println("Registered Token:")
                self.printer.doubleHeightOn()
                self.printer.println(self.user_token)
                self.printer.println("\n")
                self.printer.doubleHeightOff()
                self.printer.boldOff()
                self.printer.justify('R')
                self.printer.setSize('M')
                self.printer.inverseOn()
                self.printer.println(" Have a ")
                self.printer.println(" Great ")
                self.printer.println(" Day :) \n")
                self.printer.inverseOff()
                self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
                self.printer.printBMPImage('assets/printer_cart.bmp')
                self.printer.feed(5)
        # If the customer places an order successfully:
        if(payment == True):
            self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
            self.printer.printBMPImage('assets/printer_chatgpt.bmp')
            self.printer = Adafruit_Thermal(bus=6, heattime=120, heatdots=5, heatinterval=40)
            self.printer.justify('C')
            self.printer.setSize('L')
            self.printer.println("AIoT")
            self.printer.println("Shopping")
            self.printer.println("Assistant")
            self.printer.println("Status\n\n")
            self.printer.justify('L')
            self.printer.setSize('S')
            self.printer.boldOn()
            self.printer.println("Payment for")
            self.printer.println("your latest")
            self.printer.println("order is received")
            self.printer.println("successfully via")
            self.printer.println("your dashboard")
            self.printer.println("on the web")
            self.printer.println("application.")
            self.printer.println("Please scan")
            self.printer.println("your account")
            self.printer.println("QR code to")
            self.printer.println("activate your")
            self.printer.println("cart for")
            self.printer.println("your next")
            self.printer.println("order!\n\n")
            self.printer.println("Removed Token:")
            self.printer.doubleHeightOn()
            self.printer.println(self.user_token)
            self.printer.println("\n")
            self.printer.doubleHeightOff()
            self.printer.boldOff()
            self.printer.justify('R')
            self.printer.setSize('M')
            self.printer.inverseOn()
            self.printer.println(" Have a ")
            self.printer.println(" Great ")
            self.printer.println(" Day :) \n")
            self.printer.inverseOff()
            self.printer = Adafruit_Thermal(bus=6, heattime=155, heatdots=1, heatinterval=1)
            self.printer.printBMPImage('assets/printer_payment.bmp')
            self.printer.feed(5)
                           
    # Change the layout on the SSD1309 OLED transparent display depending on the given command.
    def change_layout(self, activated):
        self.display.clear_buffers()
        if(activated == "home"):
            self.adjust_color([0,1,0])
            self.display.draw_bitmap("assets/cart.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Please scan", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "your unique", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "account QR ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "code to    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "activate   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "your cart! ", self.light, invert=True)
        elif(activated == "scan"):
            self.adjust_color([0,1,1])
            self.display.draw_bitmap("assets/chatgpt.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Please scan", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "a product  ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "barcode to ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "change     ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "the cart   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "items!     ", self.light, invert=True)
        elif(activated == "register"):
            self.adjust_color([0,0,1])
            self.display.draw_bitmap("assets/registered.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Account QR ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "code       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "registered ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "and your   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "cart       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "is ready!  ", self.light, invert=True)        
        elif(activated == "barcode"):
            self.adjust_color([1,0,1])
            self.display.draw_bitmap("assets/barcode.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Press:     ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "           ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "Button (A) ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "-> Add     ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "Button (B) ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "-> Remove  ", self.light, invert=True)
        elif(activated == "add"):
            self.adjust_color([0,0,1])
            self.display.draw_bitmap("assets/add.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Given      ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "product    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "added to   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "your       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "registered ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "cart!      ", self.light, invert=True)
        elif(activated == "remove"):
            self.adjust_color([1,0,0])
            self.display.draw_bitmap("assets/remove.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Given      ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "product    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "removed    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "from your  ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "registered ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "cart!      ", self.light, invert=True)
        elif(activated == "payment"):
            self.adjust_color([1,1,1])
            self.display.draw_bitmap("assets/payment.mono", 5, (self.display.height-48) // 2, 48, 48, invert=True)
            self.display.draw_text(48+10, (self.display.height-48) // 2, "Order      ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*1), "payment    ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*2), "received   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*3), "and your   ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*4), "cart       ", self.light, invert=True)
            self.display.draw_text(48+10, ((self.display.height-48) // 2) + (self.light.height*5), "deactivated", self.light, invert=True)            
        self.display.present()
        sleep(1)
            
    def adjust_color(self, color):
        self.red.value(1-color[0])
        self.blue.value(1-color[1])
        self.green.value(1-color[2])
    
    def start(self):
        self.read_QR_barcode()
        self.print_status()
            

# Define the assistant object.
assistant = shopping_assistant()

while True:
    assistant.start()


class.php

Download



<?php

session_start();

// Define the user class and its functions:
class user {
	public $conn;
	
	private $Brevo_API_URL = "https://api.brevo.com/v3/smtp/email";
	private $Brevo_API_Key = '<_API_Key_>';
	private $Brevo_email = 'freelance@theamplituhedron.com';
	private $Brevo_email_name = 'AIoT Shopping Assistant';
	
	public function __init__($conn){
		$this->conn = $conn;
	}
	
	// Database -> Add new account information
	public function add_new_account($firstname, $lastname, $email, $username, $password, $c_password){
		// Check for existing users.
		$existing_sql = "SELECT * FROM `users` WHERE `username`='$username' OR `email`='$email'";
		$existing_sql_result = mysqli_query($this->conn, $existing_sql);
		$existing_sql_check = mysqli_num_rows($existing_sql_result);
		if($existing_sql_check > 0){
			header('Location: ../?userAlreadyExists');
			exit();
		}
		// Confirm the given account password.
		if($password != $c_password){
			header('Location: ../?wrongPassword');
			exit();
		}
		
		// Obtain the unique user token β€” 12 digits.
		$user_token = $this->generate_token(12, $username);

		// Create a QR code from the given username and the generated user token.
		$qr_code = "https://chart.googleapis.com/chart?cht=qr&chs=450x450&chl=user%".$user_token."&choe=UTF-8";
		
		// Insert new user information into the users MySQL database table.
		$insert_sql = "INSERT INTO `users` (`firstname`, `lastname`, `username`, `password`, `email`, `token`, `qr_code`, `successful_order`)
		               VALUES ('$firstname', '$lastname', '$username', '$password', '$email', '$user_token', '$qr_code', 1)";
		mysqli_query($this->conn, $insert_sql);
		
		// Create a unique MySQL database table for the registered account.
		$new_table = $this->create_products_table($user_token);
		if(!$new_table){ header('Location: ../?mysqlServerFailed'); exit(); }
		
		// Send a confirmation email to the user, including the verification QR code.
		$this->send_confirmation_email($email, "Verify Your Account", $firstname.' '.$lastname, $qr_code);
		
		// Set the required session variables.
		$_SESSION["name"] = $firstname.' '.$lastname;
		$_SESSION["username"] = $username;
		$_SESSION["email"] = $email;
		$_SESSION["user_token"] = $user_token;
		$_SESSION["qr_code"] = $qr_code;
		
		// If there is no error, go to the user interface (dashboard).
		header('Location: ../dashboard.php');
		exit();
	}
	
	// If the user requests to log into an existing account:
	public function user_login_request($u_username, $u_password){
		// Check whether the given account information is accurate.
	    $account_sql = "SELECT * FROM `users` WHERE `username`='$u_username' AND `password`='$u_password'";
		$account_sql_result = mysqli_query($this->conn, $account_sql);
		$account_sql_check = mysqli_num_rows($account_sql_result);
		if($account_sql_check > 0){
			if($row = mysqli_fetch_assoc($account_sql_result)){				
			    // Set the required session variables.
				$_SESSION["name"] = $row['firstname'].' '.$row['lastname'];
				$_SESSION["username"] = $row['username'];
				$_SESSION["email"] = $row['email'];
				$_SESSION["user_token"] = $row['token'];
				$_SESSION["qr_code"] = $row['qr_code'];
				// If there is no error, go to the user interface (dashboard).
				header('Location: ../dashboard.php');
				exit();
			}
		}else{
			header('Location: ../login.php?noAccountFound');
			exit();
		}
	}
	
	// Generate a unique user token.
	private function generate_token($len, $username){
		// Define the main string.
		$lowercase = "abcdefghijklmnopqrstuvwxyz"; $uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $number = "0123456789"; $symbol = "*()[]{}#$?!";
		$main = $lowercase.$uppercase.$number.$symbol;
		// Derive the user token from the main string.
		$token = "";
		for ($i=0; $i<$len; $i++){ $token .= $main[random_int(0, (strlen($main)-1))]; }
        return $username."_".$token;
	}
	
	// Create a unique MySQL database table for the new user.
	private function create_products_table($table){
		// Create a new database table.
		$sql_create = "CREATE TABLE `$table`(		
							id int AUTO_INCREMENT PRIMARY KEY NOT NULL,
							product_barcode varchar(255) NOT NULL,
							product_name varchar(255) NOT NULL,
							product_ingredients varchar(255) NOT NULL,
							product_price int NOT NULL,
							cart_number int NOT NULL,
							order_number int NOT NULL
					   );";
		if(mysqli_query($this->conn, $sql_create)){ return true; } else{ return false; }		
	}
	
	// Via Brevo's Email API, send an HTML email to the user.
	public function send_Brevo_email($to_email, $subject, $name, $html_content){
		// Define POST data parameters in the JSON format. 
		$data = '{  
					"sender":{  
								"name":"'.$this->Brevo_email_name.'",
								"email":"'.$this->Brevo_email.'"
							 },
					"to":[  
							 {  
								"email":"'.$to_email.'",
								"name":"'.$name.'"
							 }
						 ],
					"subject":"'.$subject.'",
					"htmlContent":"'.$html_content.'"
				 }';
		// Define the required HTML headers.
		$headers = array('accept: application/json', 'api-key:'.$this->Brevo_API_Key, 'content-type: application/json');
		// Send an HTML email via Brevo's Email API by making a cURL call.
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($curl, CURLOPT_URL, $this->Brevo_API_URL);
		curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
		// Execute the defined cURL call.
		$result = curl_exec($curl);
		if(!$result){ header('Location: ../?emailServerFailed'); exit(); }
        curl_close($curl);
	}
	
	// Send an account confirmation email to the new user, including the unique account verification QR code.
	private function send_confirmation_email($to_email, $subject, $name, $qr_code){
		// Define the HTML message (content) of the email.
		$html_content = '<html><head><style>h1{text-align:center;font-weight:bold;color:#505F4E;font-size:40px;}a{text-decoration:none;color:#9BB5CE;font-size:18px;font-weight:bold;}div{display:block;background-color:#F9E5C9;text-align:center;border:50px solid #5C5B57;}div p{font-size:25px;color:#505F4E;font-weight:bold;}@media only screen and (max-width: 600px){h1{font-size:20px;}a{font-size:9px;}div{border:10px solid #5C5B57;}div p{font-size:12px;}}</style></head><body><div><h1>Thanks for trying AIoT Shopping Assistant 😊</h1><img src=\"'.$qr_code.'\" alt=\"QR_CODE\" /><p>Please scan the account QR code with the shopping assistant to activate your cart πŸ›οΈ</p><a href=\"http://192.168.1.22/AIoT_Shopping_Assistant/\">➑️ Go to your Dashboard<br><br></a></div></body></html>';
		// Transfer the HTML email.
		$this->send_Brevo_email($to_email, $subject, $name, $html_content);
	}
	

}

// Define the product class and its functions:
class product extends user {
	private $OPENAI_API_KEY = "<_OPENAI_API_KEY_>";
	private $OPENAI_ENDPOINT = "https://api.openai.com/v1/chat/completions";
	
	// Obtain and decrypt the product information from the Open Food Facts JSON API by barcode.
	public function get_product_info($barcode){
		// Make an HTTP GET request to the Open Food Facts JSON API.
		// Then, decode the received JSON object.
		$data = json_decode(file_get_contents("https://world.openfoodfacts.org/api/v0/product/".$barcode.".json", TRUE));
		$product_info = array(
								"name" => $data->product->product_name,
								"ingredients" => (is_null($data->product->ingredients_text_en) || $data->product->ingredients_text_en == "") ? "Not Found" : $data->product->ingredients_text_en,
								"price" => (int)$data->product->product_quantity / 100
							 );
		return $product_info;
	}
	
	// Retrieve the current product list created by the customer.
	public function get_current_products($table){
		$total_price = 0;
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// Obtain all registered product information of the current cart as a list.
		$p_barcode = []; $p_name = []; $p_ingredients = []; $p_price = []; $p_number = [];
		$sql_list = "SELECT * FROM `$table` WHERE `order_number`='$order_number' ORDER BY `id` ASC";
		$result = mysqli_query($this->conn, $sql_list);
		$check = mysqli_num_rows($result);
		if($check > 0){
			while($row = mysqli_fetch_assoc($result)){
				// Store the fetched product information as arrays.
				array_push($p_barcode, $row["product_barcode"]);
				array_push($p_name, $row["product_name"]);
				array_push($p_ingredients, $row["product_ingredients"]);
				array_push($p_price, $row["product_price"]);
				array_push($p_number, $row["cart_number"]);
				// Calculate the total cart price (amount).
				$price = $row["product_price"] * $row["cart_number"];
				$total_price+=$price;
			}
			return array($p_barcode, $p_name, $p_ingredients, $p_price, $p_number, array("total_price" => $total_price));
		}else{
			return array(["Not Found!"], ["Not Found!"], ["Not Found!"], ["Not Found!"], ["Not Found!"], array("total_price" => 0));
		}
	}
	
	// Retrieve and print the previous order lists.  
	public function get_previous_orders($table){
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// If there are any previous orders, return the purchased products as an HTML list for each order.
		if($order_number == 1){
			echo '<h1><i class="fa-solid fa-circle-xmark"></i> No previous order was found!</h1>';
		}else{
			$list = "";
			for($i=1;$i<$order_number;$i++){
				$sql = "SELECT * FROM `$table` WHERE `order_number`='$i' ORDER BY `id` ASC";
				$result = mysqli_query($this->conn, $sql);
				$check = mysqli_num_rows($result);
				if($check > 0){
					while($row = mysqli_fetch_assoc($result)){
						$line = '<li>'.$row["product_name"].' ['.$row["product_barcode"].'] <span><i class="fa-solid fa-xmark"></i></span>'.$row["cart_number"].'</li>';
						$list.=$line;
					}
				}
				echo '<div class="orders"><h2><i class="fa-solid fa-cash-register"></i> Order ['.$i.']</h2><ul>'.$list.'</ul></div>';
				$list = "";			
			}
		}
	}
	
	// Generate the unique payment QR code and notify the user of the placed order via an HTML email.
	public function user_checkout($table, $email, $name){
		// Create a QR code from the user token and the given command.
		$qr_text = 'finished%'.$table;
		$qr_code = "https://chart.googleapis.com/chart?cht=qr&chs=450x450&chl=".$qr_text."&choe=UTF-8";
		
		// Update the successful order number after the checkout process.
		$sql = "UPDATE `users` SET `successful_order`=`successful_order`+1 WHERE `token` = '$table'";
		mysqli_query($this->conn, $sql);
		
		// Send a notification email to the user, including the unique payment QR code.
		$this->send_payment_email($email, "Order Successful", $name, $qr_code);
		
		// If there is no error, go to the user interface (dashboard).
		header('Location: ./dashboard.php?paymentCompleted');
		exit();	
	}
	
	// Send a notification email to the user after completing the checkout process, including the unique payment QR code.
	private function send_payment_email($to_email, $subject, $name, $qr_code){
		// Define the HTML message (content) of the email.
		$html_content = '<html><head><style>h1{text-align:center;font-weight:bold;color:#505F4E;font-size:40px;}a{text-decoration:none;color:#9BB5CE;font-size:18px;font-weight:bold;}div{display:block;background-color:#F9E5C9;text-align:center;border:50px solid #5C5B57;}div p{font-size:25px;color:#505F4E;font-weight:bold;}@media only screen and (max-width: 600px){h1{font-size:20px;}a{font-size:9px;}div{border:10px solid #5C5B57;}div p{font-size:12px;}}</style></head><body><div><h1>Thanks for your order πŸ˜ŠπŸ‘</h1><img src=\"'.$qr_code.'\" alt=\"QR_CODE\" /><p>Please scan your payment QR code with the shopping assistant to complete your order πŸ’²βœ…</p><a href=\"http://192.168.1.22/AIoT_Shopping_Assistant/\">➑️ Go to your Dashboard<br><br></a></div></body></html>';
		// Transfer the HTML email.
		$this->send_Brevo_email($to_email, $subject, $name, $html_content);
	}
	
	// Make a cURL call (request) to the OpenAI API in order to get suggestions regarding the given product from ChatGPT.
	public function chatgpt_get_suggestion($product){
		// Define the questions related to the given product.
		$questions = array(
							"What is the nutritional value of ".$product."?",
							"What should I purchase with ".$product."?",
							"Can you teach me a recipe with ".$product."?",
							"How should I serve ".$product."?",
							"Is there a more affordable and healthy option than ".$product."?"
		                  );
		// Define POST data parameters in the JSON format. 
		$data = '{  
					"model": "gpt-3.5-turbo",
					"messages": [
									{"role": "user", "content": "'.$questions[0].'"},
									{"role": "user", "content": "'.$questions[1].'"},
									{"role": "user", "content": "'.$questions[2].'"},
									{"role": "user", "content": "'.$questions[3].'"},
									{"role": "user", "content": "'.$questions[4].'"},
									{"role": "user", "content": "Please add the exact question at the beginning of the answer with the question number."}
								],
					"temperature": 0.7
				 }';
		// Define the required HTML headers.
		$headers = array('Authorization: Bearer '.$this->OPENAI_API_KEY, 'Content-Type: application/json');
		// Obtain product suggestions from ChatGPT by making a cURL call to the OpenAI API.
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($curl, CURLOPT_URL, $this->OPENAI_ENDPOINT);
		curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
		// Execute the defined cURL call.
		$result = curl_exec($curl);
		if(!$result){ header('Location: ../?ChatGPTServerFailed'); exit(); }
        curl_close($curl);
        // Decode the received JSON object to obtain suggestions generated by ChatGPT.
		$res = json_decode($result);
		$suggestions = $res->choices[0]->message->content;
		// Modify the obtained suggestions to add line breaks.
		$modified_suggestions = $suggestions;
		$modified_suggestions = str_replace('1. '.$questions[0], "<h2>Suggestions</h2>", $modified_suggestions);
		for($i=1;$i<count($questions);$i++){
			$modified_suggestions = str_replace(strval($i+1).'. '.$questions[$i], "<br><br>", $modified_suggestions);
		} 
		// Return the modified suggestions and the defined product questions.
		return array($modified_suggestions, $questions);
	}
	
    // Database -> Insert product data
	public function insert_product($table, $barcode, $name, $ingredients, $price){
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// Check whether the given product is in the user's database table or not.
		if($this->check_product($table, $barcode, $order_number)){
			// If the given product is already in the cart (table), update the product amount (cart number).
			$sql_update = "UPDATE `$table` SET `cart_number`=cart_number+1 WHERE `product_barcode` = '$barcode'";
			if(mysqli_query($this->conn, $sql_update)){ return true; } else{ return false; }
		}else{
			// If not, insert the new product information into the user's database table.
			$sql_insert = "INSERT INTO `$table` (`product_barcode`, `product_name`, `product_ingredients`, `product_price`, `cart_number`, `order_number`)
		                   VALUES('$barcode', '$name', '$ingredients', '$price', 1, '$order_number');
					      ";
		    if(mysqli_query($this->conn, $sql_insert)){ return true; } else{ return false; }
		}
	}

    // Database -> Delete product data
	public function delete_product($table, $barcode){
		// Obtain the current order tag (number).
		$order_number = $this->get_order_number($table);
		// Check whether the given product is in the user's database table or not.
		if($this->check_product($table, $barcode, $order_number)){
			// Remove the given product from the cart (table).
			$sql_delete = "DELETE FROM `$table` WHERE `product_barcode`='$barcode' AND `order_number` = '$order_number'";
			if(mysqli_query($this->conn, $sql_delete)){ return true; } else{ return false; }
		}
	}
	
	// Database -> Check database table
	public function check_table($table){
		$sql = "SELECT * FROM `information_schema`.`TABLES` WHERE `table_name` = '$table' limit 1";
		$sql_result = mysqli_query($this->conn, $sql);
		$sql_check = mysqli_num_rows($sql_result);
		if($sql_check > 0){ return true; } else{ return false; }
	}
	
	// Database -> Check product
	private function check_product($table, $barcode, $order_number){
		$sql = "SELECT * FROM `$table` WHERE `product_barcode` = '$barcode' AND `order_number` = '$order_number'";
		$sql_result = mysqli_query($this->conn, $sql);
		$sql_check = mysqli_num_rows($sql_result);
		if($sql_check > 0){ return true; } else{ return false; }
	}
	
    // Database -> Get order number 
	private function get_order_number($token){
		$order_number = 0;
		$sql = "SELECT * FROM `users` WHERE `token` = '$token'";
		$sql_result = mysqli_query($this->conn, $sql);
		$sql_check = mysqli_num_rows($sql_result);
		if($sql_check > 0){
			if($row = mysqli_fetch_assoc($sql_result)){
				$order_number = $row["successful_order"];
			}
			return $order_number;
		}
	}
}

// Define database and server settings:
$server = array(
	"name" => "localhost",
	"username" => "root",
	"password" => "",
	"database" => "shopping_assistant_users"
);

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

?>


barcode.php

Download



<?php

include_once "class.php";

// Define the new '_product' object:
$_product = new product();
$_product->__init__($conn);

if(isset($_GET["table"]) && isset($_GET["barcode"]) && isset($_GET["com"])){	
	// Check whether the given table is in the MySQL database.
	if($_product->check_table($_GET["table"])){
		// Make an HTTP GET request to the Open Food Facts JSON API to obtain the product information with the given barcode.
		$product_info = $_product->get_product_info($_GET["barcode"]);
		// According to the selected command by the user, add or remove the given product to/from the user's database table.
		if($_GET["com"] == "add"){
			$_product->insert_product($_GET["table"], $_GET["barcode"], $product_info["name"], $product_info["ingredients"], $product_info["price"]);
			echo "Given Product Added to the Cart Successfully!";
		}else if($_GET["com"] == "remove"){
			$_product->delete_product($_GET["table"], $_GET["barcode"]);
			echo "Given Product Removed from the Cart Successfully!";
		}
	}else{
		echo "No Table Found!";
	}
}

?>


dashboard_updates.php

Download



<?php

include_once "class.php";

if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

// Define the new '_product' object:
$_product = new product();
$_product->__init__($conn);

// Retrieve and print the current product list generated by the customer.
if(isset($_GET["update"])){
	$p_barcode = []; $p_name = []; $p_ingredients = []; $p_price = []; $p_number = []; $total_price = [];
	list($p_barcode, $p_name, $p_ingredients, $p_price, $p_number, $total_price) = $_product->get_current_products($_SESSION["user_token"]);
	$list = "<tr><th>Product</th><th>Barcode</th><th>Ingredients</th><th>Price($)</th><th>Unit</th><th>Counsel</th></tr>";
	for($i=0; $i<count($p_barcode); $i++){
		$list .= '<tr>
					<td>'.$p_name[$i].'</td>
					<td>'.$p_barcode[$i].'</td>
					<td>'.$p_ingredients[$i].'</td>
					<td>'.$p_price[$i].'</td>
					<td>'.$p_number[$i].'</td>
					<td><a href="ChatGPT/?product='.$p_name[$i].'" target="_blank"><button><i class="fa-solid fa-comment-dots"></i> Ask ChatGPT</button></a></td>
				  </tr>
				 ';   
	}
	
	// Create a JSON object from the generated HTML table rows consisting of the product information and the evaluated total cart price.
	$data = array("list" => $list, "total_price" => "$".$total_price["total_price"]);
	$j_data = json_encode($data);
    
	// Return the recently generated JSON object.
	echo($j_data);
}
?>


index.js

Download



// Every 5 seconds, retrieve the HTML table rows generated from the user's database table rows to showcase current products added to the cart
// and inform the user of the calculated total cart price (amount).
setInterval(function(){
	$.ajax({
		url: "./assets/dashboard_updates.php?update",
		type: "GET",
		success: (response) => {
			// Decode the obtained JSON object.
			const data = JSON.parse(response);
			// Assign the HTML table rows as the current product list in the cart. 
			$(".products table").html(data.list);
			// Assign the evaluated total cart price (amount).
			$("#total_price").html(data.total_price);
		}
	});
}, 5000);

// When the user clicks the checkout button, open the checkout page and transfer the evaluated total cart price via the hidden HTML form.
$(".info").on("click", "#checkout", () => {
	var total_price = $("#total_price").text();
	$("#checkout_price").val(total_price);
	$("#hidden_checkout").submit();
});


index.css

Download



html{background-image:url('background.jpg');background-repeat:no-repeat;background-attachment:fixed;background-size:100% 100%;font-family: 'Kanit', sans-serif;}
h1{text-align:center;font-weight:bold;user-select:none;background:-webkit-linear-gradient(#9BB5CE, #F9E5C9);-webkit-background-clip:text;-webkit-text-fill-color:transparent;}
input{background-image:linear-gradient(45deg, #F9E5C9, #5C5B57);color:#9BB5CE;font-size:18px;font-weight:bold;margin-right:20px;}
label{user-select:none;color:#9BB5CE;font-size:18px;font-weight:bold;padding-left:20px;}

.container{position:relative;background-image:linear-gradient(45deg, #505F4E, #F5F5F0);width:30%;height:30%;margin:auto;margin-top:10%;border:20px solid #5C5B57;border-radius:20px;padding:5px;}
.container div{position:relative;background-color:none;width:100%;height:100%;margin-top:20px;margin-bottom:20px;}
.container div input{position:absolute;right:0;width:35%;}
.container button{display:block;background-image:linear-gradient(45deg, #9BB5CE, #F9E5C9);width:75%;height:auto;margin:auto;margin-top:50px;margin-bottom:25px;font-size:25px;color:#5C5B57;font-weight:bold;border:5px solid #5C5B57;border-radius:15px;}
.container button:hover{cursor:pointer;background-image:linear-gradient(45deg, #9BB5CE, #9BB5CE);}
.container a{user-select:none;font-size:18px;background:-webkit-linear-gradient(#9BB5CE, #9BB5CE);-webkit-background-clip:text;-webkit-text-fill-color:transparent;}
.container a:hover{background:-webkit-linear-gradient(#9BB5CE, #F9E5C9);-webkit-background-clip:text;-webkit-text-fill-color:transparent;}

.products{position:fixed;bottom:5%;left:20px;width:60%;height:75%;background-color:rgba(80, 95, 78, 0.65);overflow-y:auto;border:15px solid rgba(92, 91, 87, 0.85);border-radius:20px;padding:5px;}
.products table{position:relative;width:95%;color:white;margin:auto;margin-top:20px;margin-bottom:20px;border:3px solid #F9E5C9;user-select:none;}
.products th, .products td{border:3px solid #F9E5C9;color:#5C5B57;}
.products th{background-color:#F9E5C9}
.products td{color:white;padding:10px;}
.products button{display:block;width:160px;height:auto;margin:auto;padding:5px;background-image:linear-gradient(45deg, #9BB5CE, #F9E5C9);font-size:15px;color:#5C5B57;font-weight:bold;border:5px solid #5C5B57;border-radius:10px;}
.products button:hover{cursor:pointer;background-image:linear-gradient(45deg, #9BB5CE, #9BB5CE);}
.products a, .products a:hover{text-decoration:none;}

.info{position:fixed;bottom:5%;right:20px;width:30%;height:75%;background-color:rgba(80, 95, 78, 0.65);overflow-y:auto;border:15px solid rgba(92, 91, 87, 0.85);border-radius:20px;padding:5px;}
.info section:nth-of-type(1){position:relative;width:95%;height:50%;margin:auto;background-color:transparent;}
.info section:nth-of-type(1) div{position:relative;top:0;left:0;width:50%;height:100%;background-color:#505F4E;border:10px solid #F9E5C9;border-radius:15px;}
.info section:nth-of-type(1) div button{display:block;position:absolute;left:7%;bottom:10px;width:85%;height:60px;padding:5px;background-image:linear-gradient(45deg, #9BB5CE, #F9E5C9);font-size:25px;color:#F5F5F0;font-weight:bold;border:10px solid #F5F5F0;border-radius:15px;}
.info section:nth-of-type(1) div button:hover{cursor:pointer;background-image:linear-gradient(45deg, #9BB5CE, #9BB5CE);}
.info section:nth-of-type(1) p{user-select:none;color:#F9E5C9;font-size:20px;font-weight:bold;padding-left:5px;}
.info section:nth-of-type(1) span{display:block;text-align:center;color:#9BB5CE;font-size:100px;font-weight:bold;padding-top:15%;}
.info section img{display:inline-block;position:absolute;top:0;right:0;width:250px;height:250px;border:3px solid #F9E5C9;border-radius:5px;padding:5px;transition:1s;}
.info section img:hover{cursor:crosshair;border:3px solid #9BB5CE;padding:8px;transition:1s;}
.info section:nth-of-type(2){position:relative;width:95%;height:35%;margin:auto;background-color:#505F4E;border:10px solid #F9E5C9;border-radius:15px;}
.info section:nth-of-type(2) h2{color:#F9E5C9;padding-left:10px;}
.info section:nth-of-type(2) p{color:#F5F5F0;padding-left:10px;font-weight:bold;}
.info section:nth-of-type(2) span{position:absolute;right:0;padding-right:10px;color:#9BB5CE;}
.info section:nth-of-type(2) button{display:block;position:absolute;left:7%;bottom:20px;width:85%;height:60px;padding:5px;background-image:linear-gradient(45deg, #9BB5CE, #F9E5C9);font-size:25px;color:#F5F5F0;font-weight:bold;border:8px solid #F5F5F0;border-radius:15px;}
.info section:nth-of-type(2) button:hover{cursor:pointer;background-image:linear-gradient(45deg, #9BB5CE, #9BB5CE);}
.info a, .info a:hover{text-decoration:none;}
.logout{display:block;position:absolute;right:5px;top:280px;width:250px;height:60px;padding:5px;background-image:linear-gradient(45deg, #9BB5CE, #F9E5C9);font-size:25px;color:#F5F5F0;font-weight:bold;border:10px solid #F5F5F0;border-radius:15px;}
.logout:hover{cursor:pointer;background-image:linear-gradient(45deg, #9BB5CE, #9BB5CE);}

.orders{display:block;width:55%;height:auto;margin:auto;background-image:linear-gradient(45deg, #505F4E, #F5F5F0);margin-top:35px;border:20px solid #5C5B57;border-radius:20px;padding:10px;}
.orders li{font-weight:bold;font-size:18px;color:#F9E5C9;}
.orders h2{font-weight:bold;color:#9BB5CE;user-select:none;}
.orders span{color:#9BB5CE;padding-left:10px;padding-right:10px;}

#checkout_form fieldset{border:2px solid #F9E5C9;}
#checkout_form legend{background-color:#9BB5CE;color:white;}
#checkout_form img{width:100px;height:auto;}
#checkout_form span{position:absolute;font-weight:bold;font-size:60px;color:#F9E5C9;bottom:25px;right:20px;user-select:none;}


/* Width */
::-webkit-scrollbar {width:5px;height:5px;}
/* Track */
::-webkit-scrollbar-track {background-color:#9BB5CE;}
/* Button */
::-webkit-scrollbar-button{background-color:#F9E5C9;height:5px;width:5px;}
::-webkit-scrollbar-button:hover{background-color:#F5F5F0;}
/* Handle */
::-webkit-scrollbar-thumb {background-color:#F9E5C9;}
::-webkit-scrollbar-thumb:hover {background-color:#F5F5F0;}
/* Corner */
::-webkit-scrollbar-corner{background-color:#F5F5F0;}


index.php

Download



<?php

session_start();

// If the user has already signed in, go to the user interface (dashboard).
if(isset($_SESSION["username"]) && isset($_SESSION["email"])){
	header('Location: ./dashboard.php');
	exit();
}
?>

<!DOCTYPE html>
<html>
<head>
<title>AIoT Shopping Assistant</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.2.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=Kanit&display=swap" rel="stylesheet">

</head>
<body>
<?php ini_set('display_errors',1);?> 
<h1><i class="fa-sharp fa-solid fa-network-wired"></i> <i class="fa-sharp fa-solid fa-wifi"></i> Please create an account to experience AIoT shopping <i class="fa-regular fa-face-smile"></i></h1>

<div class="container">
<form method="get" action="assets/new_account.php">
<div><label for="firstname">First name:</label><input name="firstname" placeholder="John" id="firstname"></input></div>
<div><label for="lastname">Last name:</label><input name="lastname" placeholder="Doe" id="lastname"></input></div>
<div><label for="email">Email:</label><input name="email" placeholder="johndoe@gmail.com" id="email"></input></div>
<div><label for="username">Username:</label><input name="username" placeholder="John_123" id="username"></input></div>
<div><label for="password">Password:</label><input type="password" name="password" placeholder="000_abc" id="password"></input></div>
<div><label for="c_password">Confirm Password:</label><input type="password" name="c_password" placeholder="000_abc" id="c_password"></input></div>
<button type="submit"><i class="fa-solid fa-user-check"></i> Create New Account</button>
<p style="text-align:center;"><a href="./login.php">Already have an account?</a></p>
</form>
</div>
</body>
</html>


login.php

Download



<?php

session_start();

// If the user has already signed in, go to the user interface (dashboard).
if(isset($_SESSION["username"]) && isset($_SESSION["email"])){
	header('Location: ./dashboard.php');
	exit();
}
?>

<!DOCTYPE html>
<html>
<head>
<title>Login / AIoT Shopping Assistant</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.2.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=Kanit&display=swap" rel="stylesheet">

</head>
<body>
<?php ini_set('display_errors',1);?> 
<h1><i class="fa-sharp fa-solid fa-network-wired"></i> <i class="fa-sharp fa-solid fa-wifi"></i> Please sign in to experience AIoT shopping <i class="fa-regular fa-face-smile"></i></h1>

<div class="container">
<form method="get" action="assets/new_account.php">
<div><label for="u_username">Username:</label><input name="u_username" placeholder="John_123" id="u_username"></input></div>
<div><label for="u_password">Password:</label><input type="password" name="u_password" placeholder="000_abc" id="u_password"></input></div>
<button type="submit"><i class="fa-solid fa-house-circle-check"></i> Sign In</button>
</form>
</div>
</body>
</html>


dashboard.php

Download



<?php

session_start();

// If the user signed in successfully, proceed.
if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

// If the user requests to log out:
if(isset($_GET["logout"])){
	// Remove and destroy all session variables.
	session_unset();
	session_destroy();
	// Go to the home page.
	header('Location: ./');
	exit();
}
?>

<!DOCTYPE html>
<html>
<head>
<title>Dashboard / AIoT Shopping Assistant</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.2.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=Kanit&display=swap" rel="stylesheet">

<!--link to jQuery script-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

</head>
<body>
<?php ini_set('display_errors',1);?> 
<h1 style="text-align:left;padding-left:20px;margin-top:60px;"><i class="fa-solid fa-keyboard"></i> Welcome to your Dashboard</h1>
<div class="products">
<table>
<tr><th>Product</th><th>Barcode</th><th>Ingredients</th><th>Price($)</th><th>Unit</th><th>Counsel</th></tr>
<tr><td>X</td><td>X</td><td>X</td><td>X</td><td>X</td><td><a href="ChatGPT/?product=Not Found!" target="_blank"><button><i class="fa-solid fa-comment-dots"></i> Ask ChatGPT</button></a></td></tr>
</table>
</div>

<div class="info">
<br>
<section>
<div>
<p><i class="fa-solid fa-money-check-dollar"></i> Total Price:</p>
<span id="total_price">$0</span>
<form><button id="checkout"><i class="fa-solid fa-cart-shopping"></i> Checkout</button></form>
</div>
<img src="<?php echo $_SESSION["qr_code"] ?>" alt="QR_CODE" />
<a href="?logout=OK"><button class="logout"><i class="fa-solid fa-door-open"></i> Logout</button></a>
</section>
<br>
<section>
<h2><i class="fa-solid fa-address-card"></i> Account Information</h2>
<p>Name: <span><?php echo $_SESSION["name"]; ?></span></p>
<p>Email: <span><?php echo $_SESSION["email"]; ?></span></p>
<p>Username: <span><?php echo $_SESSION["username"]; ?></span></p>
<a href="previous_orders.php" target="_blank"><button><i class="fa-solid fa-backward"> </i> Previous Orders</button></a>
</section>
</div>

<form id="hidden_checkout" action="checkout.php" method="post" target="_blank" style="display:none;">
<input id="checkout_price" name="checkout_price" type="hidden" value="$0">
</form>

<!--Add the index.js file-->
<script type="text/javascript" src="assets/index.js"></script>

</body>
</html>


checkout.php

Download



<?php

include_once "assets/class.php";

// Define the new '_product' object:
$_product = new product();
$_product->__init__($conn);

// If the user signed in successfully, proceed.
if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

// If the user places an order by entering the requested credit/debit card information:
if(isset($_GET["c_number"]) && isset($_GET["c_name"]) && isset($_GET["c_date"]) && isset($_GET["c_verify"])){
	$_product->user_checkout($_SESSION["user_token"], $_SESSION["email"], $_SESSION["name"]);
}

?>

<!DOCTYPE html>
<html>
<head>
<title>Checkout / AIoT Shopping Assistant</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.2.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=Kanit&display=swap" rel="stylesheet">

</head>
<body>
<?php ini_set('display_errors',1);?> 
<h1 style="text-align:left;padding-left:20px;margin-top:60px;"><i class="fa-solid fa-cart-shopping"></i> Checkout</h1>

<div class="container">
<form method="get" action="" id="checkout_form">
<br>
<fieldset>
<legend> Credit or Debit Card </legend>
<div><label for="c_number">Card Number:</label><input name="c_number" placeholder="4111 1111 1111 1111" id="c_number"></input></div>
<div><label for="c_name">Name:</label><input name="c_name" placeholder="John Doe" id="c_name"></input></div>
<div><label for="c_date">Expiration Date:</label><input name="c_date" placeholder="12/2023" id="c_date"></input></div>
<div><label for="c_verify">Card Verification Number:</label><input name="c_verify" placeholder="123" id="c_verify"></input></div>
<button type="submit"><i class="fa-solid fa-money-check-dollar"></i> Pay</button>
<img src="assets/credit.jpg" alt="credit" />
<span>
<?php
// Check whether the total price variable is received.
if(isset($_POST["checkout_price"])){
	echo $_POST["checkout_price"];
}else{
	echo "$0";
}
?>
</span>
</fieldset>
<br>
</form>
</div>
</body>
</html>


previous_orders.php

Download



<?php

include_once "assets/class.php";

// Define the new '_product' object:
$_product = new product();
$_product->__init__($conn);

// If the user signed in successfully, proceed.
if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

?>

<!DOCTYPE html>
<html>
<head>
<title>Orders / AIoT Shopping Assistant</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.2.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=Kanit&display=swap" rel="stylesheet">

</head>
<body>
<?php ini_set('display_errors',1);?> 

<?php

$_product->get_previous_orders($_SESSION["user_token"]);

?>

</body>
</html>


index.php (ChatGPT)

Download



<?php

include_once "../assets/class.php";

// Define the new '_product' object:
$_product = new product();
$_product->__init__($conn);

if(!isset($_SESSION["username"]) && !isset($_SESSION["email"])){
	header('Location: ./');
	exit();
}

// Make a cURL call (request) to the OpenAI API in order to get suggestions regarding the given product from ChatGPT.
$suggestions = "<h2>Please enter a product name.</h2>";
$questions = ["Please enter a product name."];
if(isset($_GET["product"]) && $_GET["product"] != "Not Found!"){
	list($suggestions, $questions) = $_product->chatgpt_get_suggestion($_GET["product"]);
}


?>

<!DOCTYPE html>
<html>
<head>
<title>ChatGPT / AIoT Shopping Assistant</title>

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

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

<!-- link to FontAwesome-->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.2.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=Kanit&display=swap" rel="stylesheet">

</head>
<body>
<?php ini_set('display_errors',1);?> 
<h1><i class="fa-solid fa-network-wired"></i> Powered by ChatGPT</h1>
<div class="question">
<?php

for($i=0;$i<count($questions);$i++){
	echo '<h2><i class="fa-regular fa-comment-dots"></i> '.$questions[$i].'</h2>';
}

?>
</div>
<br>
<div class="reply">
<p><img src="icon.png" alt="ChatGPT" />
<?php

echo $suggestions;

?>
</p>
</div>

</body>
</html>


index.css (ChatGPT)

Download



html{background-color:#00A67E;font-family: 'Kanit', sans-serif;}
h1{text-align:left;font-weight:bold;user-select:none;background:-webkit-linear-gradient(#5C5B57, #505F4E);-webkit-background-clip:text;-webkit-text-fill-color:transparent;padding-left:2%;}

.question{background-image:linear-gradient(45deg, #5C5B57, #505F4E);margin-left:2%;width:40%;height:auto;min-height:200px;border:12px solid #9BB5CE;border-radius:20px;padding-left:30px;padding-right:30px;}
.question h2{color:#9BB5CE;padding-left:5px;}

.reply{background-image:linear-gradient(45deg, #5C5B57, #505F4E);margin-left:20%;width:60%;height:auto;min-height:200px;border:12px solid #F9E5C9;border-radius:20px;padding-left:30px;padding-right:30px;color:#F5F5F0;font-weight:bold;}
.reply img{float:left;width:150px;height:150px;border-radius:50%;margin-right:20px;}
.reply h2{color:#F9E5C9;font-weight:bold;}


/* Width */
::-webkit-scrollbar {width:5px;height:5px;}
/* Track */
::-webkit-scrollbar-track {background-color:#9BB5CE;}
/* Button */
::-webkit-scrollbar-button{background-color:#F9E5C9;height:5px;width:5px;}
::-webkit-scrollbar-button:hover{background-color:#F5F5F0;}
/* Handle */
::-webkit-scrollbar-thumb {background-color:#F9E5C9;}
::-webkit-scrollbar-thumb:hover {background-color:#F5F5F0;}
/* Corner */
::-webkit-scrollbar-corner{background-color:#F5F5F0;}


Schematics

project-image
Schematic - 91.1


Downloads

firmware_updated.hex

Download


assets.zip

Download


AIoT_Shopping_Assistant_main_case_bottom.stl

Download


AIoT_Shopping_Assistant_main_case_top.stl

Download