Week 14. Interface and Application Programming¶
Group Assignment
¶
This week, we learned how to connect Unity with an Arduino using serial communication. We created a simple 3D scene in Unity and added a plane that we could move and rotate. We used an IMU sensor to send rotation data to Unity.
At first, it was hard because the serial data was sometimes broken or incomplete. We learned how to fix this by using a buffer and writing code that reads and splits the data correctly. Then we used the rotation values to control the plane in Unity.
The final result was a small demo where the plane moves and turns with the sensor. It was not perfect, but we were happy to see the connection between hardware and software working in real time.
Individual Assignment¶
Getting Ready Tools¶
This week, I tried to create a Windows Form application to connect my board with WiFi, which I planned to put in my submarine. I also tried to connect a temperature sensor to my board, read the data, and receive it in my application.
Before creating the Windows Form application, I connected a digital temperature sensor (DS18B20) to my board with a 4.7kΩ pull-up resistor to VCC.
Creating Windows Form Application¶
Installing Visual Studio¶
First, I downloaded and installed Microsoft Visual Studio 2022 from this link.
During the installation, I also installed the .NET Desktop Development package, because without it we can’t create a Form application.
First Application Setup¶
After successfully installing Visual Studio, I opened it and clicked on “Create a new project.”
Then, in the search bar, I found “Windows Forms App” and clicked Next
After that, I named my project and created it.
When the solution is created, the following parts appear on the screen.
These are the main areas used during work:
-
Toolbox – Contains controls like buttons, labels, and text boxes that can be added to the form.
-
Properties – Shows the properties of the selected element, such as name, size, and color.
-
Solution Explorer – Displays the files and structure of the project, including forms and code.
-
Output (or Debug) Window – Shows messages and errors while running the project, useful for checking if everything works correctly.
Designing the UI¶
I added (Drag & Drop) several elements that I will need for my work:
-
TextBox – For entering the ESP32’s IP address.
-
Button – To connect to the ESP32.
-
ListBox – To display temperature readings.
-
Timer – To fetch data regularly.
-
Label – To describe some elements.
After adding the elements, I changed their names to make the code easier to understand and use.
Warning❗
Text is what appears on the screen. For example, a button can show the word “Connect” — this is the Text.
Name is used in the code. It helps the program know which element it is. For example, a button can have the name btnConnect — this is the Name.
Variable Naming Styles¶
When I studied at 42 Yerevan, I learned about different styles of naming variables. This is important because it helps make the code clear and easy to read.
Popular naming styles are:
-
camelCase – The first word is lowercase, and the next words start with a capital letter. Example: connectButton.
-
PascalCase – All words start with a capital letter. Example: MainForm.
-
snake_case – All letters are lowercase, and words are separated by underscores. Example: sensor_value.
I used camelCase style in my project because it was easy to read and many programmers used it.
Programming Part¶
Programming The Board¶
I started programming from the board to set up the temperature sensor.
First, I installed the DallasTemperature library.
Then I used an example from DallasTemperature library.
And it worked !! This means that the sensor was working.
For me, it was important to check if I connected the sensor correctly and if it worked. Anyway, the code will be changed later to connect through Wi-Fi and read values from the application.
Programming in the Application.¶
Since the sensor worked, I started coding in the application, specifically in Form1.cs. But first, I changed the name of Form1.cs to SubControl.cs.
By double-clicking on the window in the design part, the code part opens automatically.
Here is the code with explanations.
using System.Net.Sockets; // For socket-based networking
using System.Text; // For encoding text
using System.Net.Http; // For HTTP requests
using System.Threading; // For cancellation tokens
using System.Threading.Tasks; // For async/await tasks
namespace Submarine_Control
{
public partial class SubControl : Form
{
private bool isConnected = false; // Tracks whether we are connected to the device
private HttpClient client = new HttpClient(); // Used to make HTTP requests
private CancellationTokenSource cts; // Used to cancel the temperature polling task
public SubControl()
{
InitializeComponent(); // Initializes UI components
}
// Event handler for Connect/Disconnect button
private async void btnConnect_Click(object sender, EventArgs e)
{
if (!isConnected)
{
// If not connected, try to connect
string ipAddress = txtIPAddress.Text;
if (string.IsNullOrWhiteSpace(ipAddress))
{
MessageBox.Show("Please enter a valid IP address");
return; // Don't proceed if IP address is invalid
}
isConnected = true; // Mark as connected
btnConnect.Text = "Disconnect"; // Change button text
cts = new CancellationTokenSource(); // Create a new cancellation token
try
{
// Start polling temperature data
await StartTemperaturePolling(ipAddress, cts.Token);
}
catch (TaskCanceledException)
{
// Polling was cancelled, do nothing
}
}
else
{
// If already connected, disconnect
isConnected = false;
btnConnect.Text = "Connect"; // Reset button text
cts?.Cancel(); // Cancel the polling task if it's running
}
}
// This function runs in a loop and polls the temperature every second
private async Task StartTemperaturePolling(string ipAddress, CancellationToken token)
{
while (isConnected)
{
try
{
// Make HTTP GET request to fetch temperature from the ESP32/submarine
string response = await client.GetStringAsync($"http://{ipAddress}/temperature");
// Try to parse the response as a float
if (float.TryParse(response, out float temperature))
{
// Update the UI thread safely using Invoke
Invoke((MethodInvoker)delegate
{
lstTemperatures.Items.Add($"{DateTime.Now:T} - {temperature}°C");
// Keep the last 50 temperature readings only
if (lstTemperatures.Items.Count > 50)
lstTemperatures.Items.RemoveAt(0);
});
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}"); // Log errors in console
}
await Task.Delay(1000, token); // Wait 1 second before polling again
}
}
// Event handler when the form is closing
protected override void OnFormClosing(FormClosingEventArgs e)
{
cts?.Cancel(); // Cancel any running polling task
base.OnFormClosing(e); // Call base class handler
}
// Optional: this is called when the form first loads
private void SubControl_Load(object sender, EventArgs e)
{
// Can be used for initialization (currently empty)
}
}
}
My Board Code
#include <WiFi.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// WiFi credentials
const char* ssid = "Wifi_SSID";
const char* password = "*********";
// DS18B20 setup
const int oneWireBus = 2; // GPIO4
OneWire oneWire(oneWireBus);
DallasTemperature sensors(&oneWire);
WiFiServer server(80);
void setup() {
Serial.begin(115200);
sensors.begin();
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop() {
WiFiClient client = server.available();
if (client) {
String currentLine = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n') {
if (currentLine.startsWith("GET /temperature")) {
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.print(temp);
break;
}
currentLine = "";
} else if (c != '\r') {
currentLine += c;
}
}
}
client.stop();
}
}
Final Result: