I was planning to make a RSS reader using this display, but I came across a weather display project I wanted to check out. (So I probably end up buying another one)
There are many questions and issues around this project using the S3.
Combining a GPS module, compass, a LED ring and some code, I want to make a little device which shows you the way to the nearest … something.
To make it completely standalone, I have to use a SIM module. (Same as I have used before) This POC will use my phone as hotspot.
The LED ring will show the direction to go.
Edit: Maybe not a LED ring but a little display.
GPS moduleCompass moduleLedring
As previously posted, I was playing with Overpass turbo. Using an API, I can use code to query this.
Arduino sends latitude, longitude to my webserver
Webserver queries API for neastest POIs and calculates distance.
Send data from webserver to arduino
Arduino uses heading data to light up direction LED (also on secondary display with distance info?) edit: and shop info
Test code for my web server to query the data
import overpy
import math
api = overpy.Overpass()
# This location will be filled with data from GPS module on Arduino.
latitude = 52.2270745 # Center latitude (e.g. Berlin)
longitude = 5.177519 # Center longitude
box_size = 0.05 # Box size in degrees (about ~5 km)
south = latitude - box_size
north = latitude + box_size
west = longitude - box_size
east = longitude + box_size
def haversine(lat1, lon1, lat2, lon2):
R = 6371 # Earth radius in km
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
d_phi = math.radians(lat2 - lat1)
d_lambda = math.radians(lon2 - lon1)
a = math.sin(d_phi / 2)**2 + math.cos(phi1) * math.cos(phi2) * math.sin(d_lambda / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c # Distance in kilometers
# Calculate bearing in degrees (0-360)
def bearing(lat1, lon1, lat2, lon2):
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
delta_lon = math.radians(lon2 - lon1)
x = math.sin(delta_lon) * math.cos(phi2)
y = math.cos(phi1) * math.sin(phi2) - math.sin(phi1) * math.cos(phi2) * math.cos(delta_lon)
initial_bearing = math.atan2(x, y)
compass_bearing = (math.degrees(initial_bearing) + 360) % 360 # Normalize to 0–360
return compass_bearing
# Overpass QL query
query = f"""
[out:json];
node
["shop"="alcohol"]
({south}, {west}, {north}, {east});
out body;
>;
out skel qt;
"""
try:
result = api.query(query)
# Collect and sort places by distance
places = []
for node in result.nodes:
node_lat = float(node.lat)
node_lon = float(node.lon)
distance = haversine(latitude, longitude, node_lat, node_lon)
direction = bearing(latitude, longitude, node_lat, node_lon)
name = node.tags.get("name", "Unnamed")
places.append((distance, direction, name, node_lat, node_lon))
places.sort()
print(f"Found {len(places)} alcohol-related places sorted by distance:")
for dist, dir_deg, name, lat, lon in places:
print(f"- {name} at ({lat:.5f}, {lon:.5f}) — {dist:.2f} km, {dir_deg:.0f}°")
except Exception as e:
print(f"Error: {e}")
Output:
Found 10 alcohol-related places sorted by distance:
- The Skiff at (52.22583, 5.17860) — 0.16 km, 152°
- Onzewijnen at (52.22612, 5.17045) — 0.49 km, 258°
- Gall & Gall at (52.23244, 5.19204) — 1.15 km, 59°
- Gall & Gall at (52.21536, 5.16735) — 1.48 km, 208°
- Eric's Beer Craft at (52.21549, 5.16632) — 1.50 km, 211°
- Slijterij at (52.21082, 5.15692) — 2.29 km, 218°
- Gall & Gall at (52.21590, 5.14074) — 2.80 km, 244°
- Gall & Gall at (52.25422, 5.22705) — 4.53 km, 48°
- Gall & Gall at (52.26808, 5.18348) — 4.58 km, 5°
- Il DiVino at (52.27507, 5.16414) — 5.41 km, 350°
Example using Overpass Turbo to find breweries
Other ideas
Geocaching (Thanks Vincent)
Find each other at festivals?
UPDATE
Building the hardware : First design
Screen programming (First setup)
Some test code
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_GC9A01A.h"
// Overrule stuff
#define TFT_CS 18 // Chip select
#define TFT_DC 5 // Data/command mode
#define TFT_BL 4 // Backlight control
#define TFT_MOSI 12 // SPI Out AKA SDA
#define TFT_SCLK 13 // Clock out AKA SCL
#define TFT_MISO -1 // pin not used
#define TFT_RST 23 // Reset ################# IMPORTANT, won't work without!! Took me a hour!
// Need this changed from example also
Adafruit_GC9A01A tft(TFT_CS, TFT_DC,TFT_MOSI,TFT_SCLK,TFT_RST,TFT_MISO);
float angle = 0;
void setup() {
tft.begin();
tft.setRotation(0);
tft.fillScreen(GC9A01A_BLACK);
drawCompassFace();
}
void loop() {
drawNeedle(angle, GC9A01A_RED);
delay(1000);
drawNeedle(angle, GC9A01A_BLACK); // Erase previous needle
angle += 15;
if (angle >= 360) angle = 0;
tft.setCursor(60, 100);
tft.setTextColor(GC9A01A_WHITE); tft.setTextSize(2);
tft.println("230 Meters");
}
// Draw static compass face
void drawCompassFace() {
int cx = tft.width() / 2;
int cy = tft.height() / 2;
int radius = 100;
tft.drawCircle(cx, cy, radius, GC9A01A_WHITE);
tft.setTextColor(GC9A01A_WHITE);
tft.setTextSize(1);
tft.setCursor(cx - 3, cy - radius + 5); tft.print("N");
tft.setCursor(cx - 3, cy + radius - 10); tft.print("S");
tft.setCursor(cx - radius + 5, cy - 3); tft.print("W");
tft.setCursor(cx + radius - 10, cy - 3); tft.print("E");
}
// Draw compass needle
void drawNeedle(float angleDeg, uint16_t color) {
int cx = tft.width() / 2;
int cy = tft.height() / 2;
float angleRad = angleDeg * DEG_TO_RAD;
int x = cx + cos(angleRad) * 90;
int y = cy + sin(angleRad) * 90;
tft.drawLine(cx, cy, x, y, color);
}
Its SSID started with ESP. So I probably am the one responsible for its existence. I’ve got a sh*tload of ESPs/NodeMCUs/8266 turned on 24-7.
Using a Wifi analizer I could narrow it down to my livingroom. Checked all devices, and they are all connected to my AccessPoint. (So no fallback AP mode)
The problem with this method is that you can’t figure out a direction.
So I used this on my Laptop.
See graphs on the left
This is a directional antenna.
Using Wireshark and wavemon, I could find the direction.
There were only two devices in the direction with the strongest signal. My photo viewer remote, and my mini turntable controller with RFID.
But these devices are working just fine! .. So lets disconnect the power. So it IS the mini recordplayer!
I reversed engineered the workings, and created a python upload script to push images.
Original workings are a mess. Per 4 bit of color, high-low switched in a byte. Black and red separated. Using a till p encoding over curl commands.
My implementation uses a python script called as:
python3 epaper-pusher.py ~/Downloads/Untitled.png
http://10.1.0.99/EPDI_
30 times something like
http://10.1.0.99/ppppppppppppppppppppppppppppppppppppppppppppppppppppppaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppiodaLOAD_
http://10.1.0.99/NEXT_
30 times something like
http://10.1.0.99/pbcdefghijjjjjjffffffoooooooaaabbbbbbeeeedddppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppiodaLOAD_
http://10.1.0.99/SHOW_
NOTES:
a = 0000
-
-
-
p = 1111 = 15
30 lines with 1000 bytes ( ending with iodaLOAD_ )
black pixels
first block 1
second block 0
red pixels
first block 0
second block 1
white pixels
first block 1
second block 1
PIXEL Example
RBRB
BWBW
First block
1010 - letter K
0101 - Letter F - second nibble = white
Second block
0101 - Letter F
1111 - Letter P - second nibble white
Code
from PIL import Image
import numpy
import requests
url="http://10.1.0.99/"
black_pixels = numpy.zeros((400,300))
red_pixels = numpy.zeros((400,300))
def classify_pixel_color(pixel):
"""
Classify a pixel as black, white, or red.
"""
r, g, b = pixel[:3] # Ignore alpha if present
# Define thresholds for classification
if r < 128 and g < 128 and b < 128:
return 'black'
elif r > 200 and g > 200 and b > 200:
return 'white'
elif r > 128 and g < 100 and b < 100:
return 'red'
else:
return None
def process_image(image_path):
"""
Process the image and classify its pixels into black, white, or red.
"""
image = Image.open(image_path)
image = image.convert("RGB") # Ensure the image is in RGB mode
width, height = image.size
pixel_data = image.load()
color_counts = {'black': 0, 'white': 0, 'red': 0}
for y in range (0, 299):
for x in range (0, 399):
black_pixels[x][y] = 0
red_pixels[x][y] = 0
for y in range(299):
for x in range(399):
color = classify_pixel_color(pixel_data[x, y])
if color:
color_counts[color] += 1
if color == 'black':
black_pixels[x][y] = 1;
if color == 'red':
red_pixels[x][y] = 1;
if color == 'white':
black_pixels[x][y] = 1;
red_pixels[x][y] = 1;
return color_counts, black_pixels, red_pixels
def number_to_letter(num):
"""
Translates a number from 0 to 15 into a corresponding letter (a-p).
Args:
num (int): The number to translate.
Returns:
str: The corresponding letter (a-p).
"""
if 0 <= num <= 15:
return chr(ord('a') + num)
else:
raise ValueError("Number must be between 0 and 15, inclusive.")
def print_array_in_chunks(array, chunk_size=1001):
current_chunk = ""
for item in array:
# Convert item to string and add to the current chunk
item_str = str(item)
if len(current_chunk) + len(item_str) + 1 > chunk_size:
# Print the current chunk and reset it
current_chunk += "iodaLOAD_"
try:
requests.get(url + current_chunk, verify=False)
if not response.content: # Equivalent to expecting an empty reply
pass
except requests.exceptions.RequestException as e:
# Catch any request-related errors
pass
current_chunk = item_str
else:
# Append the item to the current chunk
current_chunk += (item_str)
current_chunk += "iodaLOAD_"
# Print any remaining items in the chunk
if current_chunk:
try:
requests.get(url + current_chunk, verify=False)
if not response.content: # Equivalent to expecting an empty reply
pass
except requests.exceptions.RequestException as e:
# Catch any request-related errors
pass
def switch_in_pairs(arr):
# Loop through the array with a step of 2
for i in range(0, len(arr) - 1, 2):
# Swap values at index i and i+1
arr[i], arr[i + 1] = arr[i + 1], arr[i]
return arr
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python3 script.py <image_path>")
sys.exit(1)
image_path = sys.argv[1]
try:
color_counts, black_pixels, red_pixels = process_image(image_path)
try:
requests.get(url + "EPDI_" , verify=False)
if not response.content: # Equivalent to expecting an empty reply
pass
except requests.exceptions.RequestException as e:
# Catch any request-related errors
pass
lines=[]
for y in range(300):
for x in range(0,399,4):
first = red_pixels[x][y]
second = red_pixels[x+1][y]
thirth = red_pixels[x+2][y]
fourth = red_pixels[x+3][y]
nibble = 0
if (first == 1):
nibble = nibble + 8
if (second == 1):
nibble = nibble + 4
if (thirth == 1):
nibble = nibble + 2
if (fourth == 1):
nibble = nibble + 1
lines.append(number_to_letter(nibble))
switched_array = switch_in_pairs(lines)
print_array_in_chunks(switched_array)
try:
requests.get(url + "NEXT_" , verify=False)
if not response.content: # Equivalent to expecting an empty reply
pass
except requests.exceptions.RequestException as e:
# Catch any request-related errors
pass
lines=[]
for y in range(300):
for x in range(0,399,4):
first = black_pixels[x][y]
second = black_pixels[x+1][y]
thirth = black_pixels[x+2][y]
fourth = black_pixels[x+3][y]
nibble = 0
if (first == 1):
nibble = nibble + 8
if (second == 1):
nibble = nibble + 4
if (thirth == 1):
nibble = nibble + 2
if (fourth == 1):
nibble = nibble + 1
lines.append(number_to_letter(nibble))
switched_array = switch_in_pairs(lines)
print_array_in_chunks(switched_array)
try:
requests.get(url + "SHOW_" , verify=False)
if not response.content: # Equivalent to expecting an empty reply
pass
except requests.exceptions.RequestException as e:
# Catch any request-related errors
pass
except Exception as e:
pass
This is a Non-Cloud solution like Alexa and Google devices. I only could play with it for a few minutes because I was working on Arduino code with an ILI9341 Display and a BME280 (Temperature/Humidity/Air pressure).
Today I got some new goodies in, one of these is a LilyGO LoRa display which works on 433 Mhz.
I flashed OpenMQTTGateway on this device.
In the past, I posted about the RFCOM Gateway using Domoticz. This runs on a Raspberry Pi. While looking for alternatives, I found a rtl-sdr solution.
While working on a client project, I tested multiple displays.
ILI9341
1.3inch SPI TFT LCD Display RGB (ST7789)
Waveshare 4.2 Epaper with ESP32 Controller
I thought it was fun to connect the Epaper to ESPHome.
This probably ends up being a Quote displayer
Universal e-Paper Driver Board with WiFi / Bluetooth SoC ESP32 onboard, supports various Waveshare SPI e-Paper raw panels
It was not without problems. For example, the ESPHome editor gave squiggly lines under type. This has to be changed in the libraries. (Already notified developers)
model: 4.20in-V2 does not work .. use model: 4.20in-v2
Emulates a floppy drive: Meatloaf plugs into the Commodore 64’s IEC serial port and acts like a virtual floppy drive. This allows you to load software and data stored on its internal flash memory, sd card, or stream it via WiFi using various protocols from servers all over the world.
Supports multiple virtual drives: Unlike a single floppy drive, Meatloaf can be configured to emulate up to 26 virtual drives (IDs 4-30). Each virtual drive can have a different disk image loaded, essentially offering the equivalent of having thousands of floppies connected to your C64.
Supports additional virtual device types: Printers, a network interface, and more.
Connects to the internet: Meatloaf also functions as a WiFi modem, enabling your Commodore 64 to connect to Telnet BBS (bulletin board systems) for communication and sharing information.
load from urldebug outputmy own repo testOnly a Lolin D32 and a cable with din connector.
Socket connect to server, enter number and get reply test.
server.py
import socket
import threading
# Define the host and port
HOST = '0.0.0.0' # Localhost (change as needed)
PORT = 65432 # Port to listen on (non-privileged ports are > 1023)
# Function to handle each client connection
def handle_client(conn, addr):
print(f"Connected by {addr}")
# Send a thank you message to the client upon connection
thank_you_message = "Thank you for connecting! Please enter a number:\n"
conn.sendall(thank_you_message.encode('utf-8'))
while True:
try:
data = conn.recv(1024)
if not data:
break
# Decode the received data
received_number = data.decode('utf-8').strip()
print(f"Received from {addr}: {received_number}")
# Try to convert the received data to an integer
try:
number = int(received_number)
response = f"The double of {number} is {number * 2}\n"
except ValueError:
response = "Please enter a valid number.\n"
# Send the response back to the client
conn.sendall(response.encode('utf-8'))
except ConnectionResetError:
print(f"Connection with {addr} lost.")
break
conn.close()
print(f"Connection with {addr} closed.")
# Function to start the server and listen for connections
def start_server():
# Create a socket object
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the host and port
server.bind((HOST, PORT))
# Start listening with a maximum backlog of 5 connections
server.listen(5)
print(f"Server listening on {HOST}:{PORT}")
while True:
# Accept a new connection
conn, addr = server.accept()
# Create a new thread to handle the client connection
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
client_thread.start()
# Run the server
if __name__ == "__main__":
start_server()
python-client.py
import socket
# Define the server host and port
HOST = 'IPNUMBERSERVER' # The server's hostname or IP address
PORT = 65432 # The port used by the server
def start_client():
# Create a socket object
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to the server
client.connect((HOST, PORT))
# Receive and print the welcome message from the server
welcome_message = client.recv(1024).decode('utf-8')
print(welcome_message)
while True:
# Enter a number and send it to the server
number = input("Enter a number (or type 'exit' to quit): ")
if number.lower() == 'exit':
print("Closing connection...")
break
client.sendall(number.encode('utf-8'))
# Receive the response from the server and print it
response = client.recv(1024).decode('utf-8')
print(response)
# Close the connection after the loop ends
client.close()
# Run the client
if __name__ == "__main__":
start_client()
arduino-client.ino
#include <ESP8266WiFi.h> // For ESP8266
//#include <WiFi.h> // For ESP32
// Replace with your network credentials
const char* ssid = "your_SSID"; // Replace with your network SSID (name)
const char* password = "your_PASSWORD"; // Replace with your network password
// Define the server's IP address and port
const char* host = "192.168.1.100"; // Replace with your server's IP address
const int port = 65432; // Server port
WiFiClient client;
void setup() {
Serial.begin(115200);
delay(10);
// Connect to WiFi
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Connect to the server
Serial.print("Connecting to server at ");
Serial.print(host);
Serial.print(":");
Serial.println(port);
if (client.connect(host, port)) {
Serial.println("Connected to server!");
// Wait for the welcome message from the server
while (client.available() == 0);
// Read and print the welcome message
while (client.available()) {
char c = client.read();
Serial.print(c);
}
} else {
Serial.println("Connection failed.");
}
}
void loop() {
// Check if connected to the server
if (client.connected()) {
// Check if there is any serial input from the user
if (Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input.equalsIgnoreCase("exit")) {
Serial.println("Closing connection...");
client.stop(); // Disconnect from the server
while (true); // Stop the loop
}
// Send the number to the server
client.println(input);
// Wait for the server's response
while (client.available() == 0);
// Read and print the server's response
while (client.available()) {
char c = client.read();
Serial.print(c);
}
}
} else {
Serial.println("Disconnected from server.");
while (true); // Stop the loop
}
}
"If something is worth doing, it's worth overdoing."