Hello Everybody,
I need help with my project I am using a laser communication to achieve sending data (text, images, sound) with turning each into text first and sending text as 1's and 0's via alternating the laser state, on the other side there is an IR sensor that detects it and is able to do the process backwards.
Sender Code:
const int laserPin = 4; // Pin connected to the laser
short bitDuration = 700;
void setup() {
// Initialize the laser pin as an output
pinMode(laserPin, OUTPUT);
digitalWrite(laserPin, HIGH);
// Initialize the serial communication
Serial.begin(2000000);
}
void loop() {
// Check if any data is available to read from the serial port
if (Serial.available() > 0) {
// Read the incoming byte
char incomingByte = Serial.read();
sendByteAsBits(incomingByte);
}
}
void sendByteAsBits(char byte) {
// Send start bit (LOW)
digitalWrite(laserPin, LOW);
delayMicroseconds(bitDuration*2);
// Send each bit of the byte
for (int i = 7; i >= 0; i--) {
bool bitValue = bitRead(byte, i);
digitalWrite(laserPin, bitValue ? HIGH : LOW);
Serial.print(bitValue);
delayMicroseconds(bitDuration);
}
// Send stop bit (HIGH)
digitalWrite(laserPin, HIGH);
delayMicroseconds(bitDuration*2);
}
Receiver Code:
int irpin = 7;
bool receiving = false;
float bitDuration = 700;
unsigned char recived_byte = 0;
void setup() {
Serial.begin(2000000);
pinMode(irpin, INPUT);
}
void loop() {
if (receiving) {
recived_byte = 0; // Reset the received byte
// Read each bit of the byte
for (int i = 0; i < 8; i++) {
recived_byte = (recived_byte << 1) | !digitalRead(irpin); // Read the bit and shift into position
// Serial.print(!digitalRead(irpin));
delayMicroseconds(bitDuration); // Wait for the next bit
}
receiving = false; // Reset the receiving flag
if (recived_byte != 0) {
char receivedChar = (char)recived_byte; // Convert to character
Serial.print(receivedChar); // Print the received character
}
} else if (!digitalRead(irpin) == LOW) {
// Detect start bit (LOW signal)
receiving = true; // Set the receiving flag
delayMicroseconds(bitDuration * 3); // Wait to the middle of the first data bit
}
}
Fortunately the Data is being sent correctly and I am able to retrieve it the correct way when it comes to serial monitor inputted data,
When it comes to me wanting to send multiple types of data, I have used Python to make this possible and to make a GUI for the sender and receiver and handle encoding of file and so on like so:
via these code
sender:
import tkinter as tk
from tkinter import filedialog
import serial
import time
import threading
from datetime import datetime
import base64
import os
import zlib # Import zlib for compression
class SenderApp:
def __init__(self, root):
self.root = root
self.root.title("Chat APP")
self.send_button = tk.Button(root, text="Send Text", command=self.send_text)
self.send_button.pack(pady=10)
self.send_button = tk.Button(root, text="Send Image", command=self.send_image)
self.send_button.pack(pady=10)
self.send_button = tk.Button(root, text="Send Sound", command=self.send_sound)
self.send_button.pack(pady=10)
self.progress_label = tk.Label(root, text="Progress: 0%")
self.progress_label.pack(pady=10)
self.chat_label = tk.Label(root, text="Chat")
self.chat_label.pack(pady=10)
self.chat_display = tk.Text(root, height=15, width=50)
self.chat_display.pack(pady=10)
self.chat_entry = tk.Entry(root, width=40)
self.chat_entry.pack(side=tk.LEFT, padx=10)
self.chat_send_button = tk.Button(root, text="Send", command=self.send_chat)
self.chat_send_button.pack(side=tk.RIGHT, padx=10)
def split_text(self, text, chunk_size=512):
return [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)]
def send_text(self):
file_path = filedialog.askopenfilename()
if not file_path:
return
# Clear the chat display
self.chat_display.delete(1.0, tk.END)
with open(file_path, 'r') as file:
text = file.read()
compressed_text = zlib.compress(text.encode('utf-8')) # Compress the text
encoded_text = base64.b64encode(compressed_text).decode('utf-8') # Encode to base64
self.total_size = len(encoded_text)
self.sent_size = 0
chunks = self.split_text(encoded_text)
threading.Thread(target=self.send_chunks, args=(chunks,b'\x03')).start()
def send_image(self):
image_path = filedialog.askopenfilename()
# Check if the image file exists
if not os.path.isfile(image_path):
raise FileNotFoundError(f"The image file {image_path} does not exist.")
# Read the image file in binary mode
with open(image_path, "rb") as image_file:
image_data = image_file.read()
# Encode the image data to base64
compressed_image = zlib.compress(image_data) # Compress the image
encoded_image_data = base64.b64encode(compressed_image).decode('utf-8')
# Get the directory and file name without extension
file_dir, file_name = os.path.split(image_path)
file_name_without_ext = os.path.splitext(file_name)[0]
# Create the text file path
text_file_path = os.path.join(file_dir, f"{file_name_without_ext}.txt")
# Write the encoded image data to the text file
with open(text_file_path, "w") as text_file:
text_file.write(encoded_image_data)
file_path = text_file_path
if not file_path:
return
# Clear the chat display
self.chat_display.delete(1.0, tk.END)
with open(text_file_path, 'r') as file:
text = file.read()
self.total_size = len(text)
self.sent_size = 0
chunks = self.split_text(text)
threading.Thread(target=self.send_chunks, args=(chunks,b'\x06')).start()
def send_sound(self):
file_path = filedialog.askopenfilename(
title="Select an MP3 file",
filetypes=[("MP3 files", "*.mp3")]
)
if not file_path:
print("No file selected.")
return
# Read the MP3 file
with open(file_path, 'rb') as mp3_file:
mp3_data = mp3_file.read()
# Encode the MP3 data to UTF-8
compressed_sound = zlib.compress(mp3_data) # Compress the image
base64_data = base64.b64encode(compressed_sound).decode('utf-8')
# Create a new filename for the encoded text file
base_name = os.path.basename(file_path)
name, _ = os.path.splitext(base_name)
utf8_file_path = os.path.join(os.path.dirname(file_path), f"{name}.txt")
# Save the encoded data to a text file
with open(utf8_file_path, 'w', encoding='utf-8') as utf8_file:
utf8_file.write(base64_data)
# Clear the chat display
self.chat_display.delete(1.0, tk.END)
with open(utf8_file_path, 'r') as file:
text = file.read()
self.total_size = len(text)
self.sent_size = 0
chunks = self.split_text(text)
threading.Thread(target=self.send_chunks, args=(chunks,b'\x07')).start()
def send_chunks(self, chunks, end_frame):
with serial.Serial('COM3', 2000000, timeout=1) as ser:
time.sleep(2) # Wait for the serial connection to initialize
ser.flushInput() # Clear Arduino buffer
# Send unique start frame for the first chunk
ser.write(b'\x01') # Unique start of transmission for the first chunk
for chunk in chunks:
ser.write(b'\x02') # Standard start of transmission for each chunk
self.send_chunk(ser, chunk)
# ser.flushInput() # Clear Arduino buffer
ser.reset_output_buffer()
ser.reset_input_buffer()
ser.write(end_frame) # Unique end of transmission for the last chunk
# ser.flushInput() # Clear Arduino buffer
ser.reset_output_buffer()
ser.reset_input_buffer()
def send_chunk(self, ser, chunk):
for char in chunk:
ser.write(char.encode('utf-8')) # Send the character to Arduino
self.chat_display.insert(tk.END, char)
self.chat_display.see(tk.END)
self.sent_size += 1
time.sleep(0.007) # Give Arduino time to process each character
self.update_progress()
self.root.update()
def send_chat(self):
message = self.chat_entry.get()
if message:
self.chat_entry.delete(0, tk.END)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
display_message = f"[{timestamp}] Sender: {message}\n"
self.chat_display.insert(tk.END, display_message)
self.chat_display.see(tk.END)
threading.Thread(target=self.send_chat_message, args=(message,)).start()
def send_chat_message(self, message):
with serial.Serial('COM3', 9600, timeout=1) as ser:
time.sleep(2) # Wait for the serial connection to initialize
ser.write(b'\x04') # Unique start frame for chat
for char in message:
ser.write(char.encode('utf-8'))
time.sleep(0.02) # Give Arduino time to process each character
ser.write(b'\x05') # Unique end frame for chat
def update_progress(self):
progress = (self.sent_size / self.total_size) * 100
self.progress_label.config(text=f"Progress: {progress:.2f}%")
root = tk.Tk()
app = SenderApp(root)
root.mainloop()
receiver:
import tkinter as tk
import serial
import threading
import os
import time
from datetime import datetime
import base64
import os
import zlib # Import zlib for compression
class ReceiverApp:
def __init__(self, root):
self.root = root
self.root.title("Text File and Chat Receiver")
self.speed_label = tk.Label(root, text="Receiving Speed: 0 bits/second")
self.speed_label.pack(pady=10)
self.time_label = tk.Label(root, text="Elapsed Time: 0 seconds")
self.time_label.pack(pady=10)
self.chat_display = tk.Text(root, height=20, width=50)
self.chat_display.pack(pady=20)
self.received_data = []
self.chunk_files = []
self.start_char_first_chunk = '\x01' # Unique start of transmission for the first chunk
self.start_char_chunk = '\x02' # Start of transmission for each chunk
self.end_char_last_chunk = '\x03' # Unique end of transmission for the last chunk
self.start_char_chat = '\x04' # Unique start frame for chat
self.end_char_chat = '\x05' # Unique end frame for chat
self.end_image = '\x06' # Unique end frame for image
self.end_sound = '\x07' # Unique end frame for sound
self.receiving = False
self.receiving_chat = False
self.start_time = None
self.total_bits_received = 0
# Start the receiving thread
threading.Thread(target=self.receive_file).start()
def get_unique_filename(self, base_name, extension=".txt"):
"""
Generate a unique file name by appending a number if the file exists.
"""
counter = 1
file_name = f"{base_name}{extension}"
while os.path.isfile(file_name):
file_name = f"{base_name}_{counter}{extension}"
counter += 1
return file_name
def receive_file(self):
base_file_name = "received_chunk"
with serial.Serial('COM5', 2000000, timeout=1) as ser:
while True:
if ser.in_waiting > 0:
incoming_byte = ser.read().decode('utf-8' , errors='ignore')
if incoming_byte == self.start_char_first_chunk:
# Clear everything before in memory
self.chunk_files = []
self.received_data = []
self.chat_display.delete(1.0, tk.END) # Clear chat display
self.start_time = time.time() # Start timing
self.total_bits_received = 0 # Reset bit counter
if incoming_byte == self.start_char_first_chunk or incoming_byte == self.start_char_chunk:
if self.received_data: # Save previous chunk if exists
chunk_file_name = self.get_unique_filename(base_file_name, extension=".chunk")
with open(chunk_file_name, 'w') as file:
file.write(''.join(self.received_data))
self.chunk_files.append(chunk_file_name)
ser.flushInput() # Clear Arduino buffer for the next chunk
ser.reset_output_buffer()
ser.reset_input_buffer()
self.receiving = True
self.received_data = [] # Reset received data
self.chat_display.delete(1.0, tk.END) # Clear chat display
elif incoming_byte == self.end_char_last_chunk:
self.receiving = False
# Calculate and display receiving speed and elapsed time
elapsed_time = time.time() - self.start_time
bit_rate = self.total_bits_received / elapsed_time
self.speed_label.config(text=f"Receiving Speed: {bit_rate:.2f} bits/second")
self.time_label.config(text=f"Elapsed Time: {elapsed_time:.2f} seconds")
# Save the last chunk data to a unique file
chunk_file_name = self.get_unique_filename(base_file_name, extension=".chunk")
with open(chunk_file_name, 'w') as file:
file.write(''.join(self.received_data))
self.chunk_files.append(chunk_file_name)
ser.flushInput() # Clear Arduino buffer for the next chunk
ser.reset_output_buffer()
ser.reset_input_buffer()
# Combine all chunk files into one file
combined_file_name = self.get_unique_filename("received_text")
with open(combined_file_name, 'w') as combined_file:
for chunk_file in self.chunk_files:
with open(chunk_file, 'r') as file:
combined_file.write(file.read())
# Delete all chunk files after combining
for chunk_file in self.chunk_files:
os.remove(chunk_file)
self.decompress_and_save_file(combined_file_name, 'txt')
elif incoming_byte == self.end_image:
self.receiving = False
# Calculate and display receiving speed and elapsed time
elapsed_time = time.time() - self.start_time
bit_rate = self.total_bits_received / elapsed_time
self.speed_label.config(text=f"Receiving Speed: {bit_rate:.2f} bits/second")
self.time_label.config(text=f"Elapsed Time: {elapsed_time:.2f} seconds")
# Save the last chunk data to a unique file
chunk_file_name = self.get_unique_filename(base_file_name, extension=".chunk")
with open(chunk_file_name, 'w') as file:
file.write(''.join(self.received_data))
self.chunk_files.append(chunk_file_name)
ser.flushInput() # Clear Arduino buffer for the next chunk
# Combine all chunk files into one file
combined_file_name = self.get_unique_filename("received_text")
with open(combined_file_name, 'w') as combined_file:
for chunk_file in self.chunk_files:
with open(chunk_file, 'r') as file:
combined_file.write(file.read())
# Delete all chunk files after combining
for chunk_file in self.chunk_files:
os.remove(chunk_file)
#decompression
self.decompress_and_save_file(combined_file_name, "png")
elif incoming_byte == self.end_sound:
self.receiving = False
# Calculate and display receiving speed and elapsed time
elapsed_time = time.time() - self.start_time
bit_rate = self.total_bits_received / elapsed_time
self.speed_label.config(text=f"Receiving Speed: {bit_rate:.2f} bits/second")
self.time_label.config(text=f"Elapsed Time: {elapsed_time:.2f} seconds")
# Save the last chunk data to a unique file
chunk_file_name = self.get_unique_filename(base_file_name, extension=".chunk")
with open(chunk_file_name, 'w') as file:
file.write(''.join(self.received_data))
self.chunk_files.append(chunk_file_name)
ser.flushInput() # Clear Arduino buffer for the next chunk
# Combine all chunk files into one file
combined_file_name = self.get_unique_filename("received_text")
with open(combined_file_name, 'w') as combined_file:
for chunk_file in self.chunk_files:
with open(chunk_file, 'r') as file:
combined_file.write(file.read())
# Delete all chunk files after combining
for chunk_file in self.chunk_files:
os.remove(chunk_file)
file_path = combined_file_name
# Check if a file was selected
if not file_path:
print("No file selected.")
return
self.decompress_and_save_file(combined_file_name, 'mp3')
elif incoming_byte == self.start_char_chat:
self.receiving_chat = True
self.chat_data = []
elif incoming_byte == self.end_char_chat:
self.receiving_chat = False
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
chat_message = ''.join(self.chat_data)
display_message = f"[{timestamp}] Receiver: {chat_message}\n"
self.chat_display.insert(tk.END, display_message)
self.chat_display.see(tk.END)
elif self.receiving:
self.received_data.append(incoming_byte)
self.chat_display.insert(tk.END, incoming_byte)
self.chat_display.see(tk.END)
self.root.update()
self.total_bits_received += 8 # Increment bit counter by 8 bits (1 byte)
elif self.receiving_chat:
self.chat_data.append(incoming_byte)
def decompress_and_save_file(self, file_path, file_type):
with open(file_path, 'r') as text_file:
encoded_data = text_file.read()
decoded_data = base64.b64decode(encoded_data)
decompressed_data = zlib.decompress(decoded_data)
file_dir, file_name = os.path.split(file_path)
file_name_without_ext = os.path.splitext(file_name)[0]
output_file_path = os.path.join(file_dir, f"{self.get_unique_filename(file_name_without_ext)}.{file_type}")
with open(output_file_path, 'wb') as output_file:
output_file.write(decompressed_data)
return output_file_path
root = tk.Tk()
app = ReceiverApp(root)
root.mainloop()
and fortunately I was able to achieve a correct sending and receiving process.
the problem is as follows:
1- when I remove the
time.sleep(0.007)
from the sender between charachters the sending is not done correctly although it is done correctly in the Arduino serial monitor
and that's the fastest I was able to get (around 1300 bits/second).
2- the second problem and the most frustrating one is when I try to send a small file let's say 2000 characters it is fast and keeps the same speed, when I increase the file size to maybe 10000 characters it works fine for a few seconds and then it start to slow down a ton,
approaches I tried to solve:
1- to cut them into chunks.
2- flush input and reset input and output buffers between chunks as shown in the code (that prevented the receiving to stop entirely but the slowing down is still happening).
please help me I am sending a 15 kb file in around 10 minutes. I want to increase the speed as fast as possible.