Wireless commands to Inky Frame with Pico 2 W

Hi!

I’ve installed my Inky Frame and it works great with micropython installed. It’s also working online, I use this code to load a local .jpg file and it works fine using Thonny.

from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY
graphics = PicoGraphics(DISPLAY)

BLACK = 0
WHITE = 1
GREEN = 2
BLUE = 3
RED = 4
YELLOW = 5
ORANGE = 6
TAUPE = 7

graphics.set_pen(WHITE)
graphics.clear()
graphics.set_pen(BLACK)
graphics.text("Hello Inky", 0, 0, 600, 4)
graphics.update()

What I’m trying to do, is to be able to send a command over wifi from my laptop, telling it to load a different local file. Basically I want to run the above code but give it wirelessly instead of via Thonny.

How do I this? I’ve looked into mpremote, umqtt and webrepl but can’t get it to work. Has anyone else done this before or can someone point me in the right direction? Thanks so much!

This depends on what you really want to do: do you just want to load code remotely (i.e. the full program) or do you want to tell a static program to load a different file?

In the first case, you probably need to switch to CircuitPython. CP has a so called “web workflow” which allows loading and executing files remotely using a browser.

In the second case, you will need to write a server that exposes some URLs and then you can execute remote commands. Search the net for MicroPython web-servers, specifically search for on how to implement REST-APIs. This basically boils down that the webserver processes URLs like “http://ip-of-my-inkyframe/load_and_update?file=my_new_image.jpg

Thanks a lot for your response and pointing me in the right direction! I ended up creating a web server where I can select the images that are on the device, and also upload text that it will show on the screen. Works great! Here is the code in case anyone would like to use it:

import network
import socket
import time
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY
from jpegdec import JPEG

# Configure WiFi
ssid = 'WIFINAME'
password = 'password'

# Initialize display
graphics = PicoGraphics(DISPLAY)
j = JPEG(graphics)

# Define colors
BLACK = 0
WHITE = 1
GREEN = 2
BLUE = 3
RED = 4
YELLOW = 5
ORANGE = 6
TAUPE = 7

# Variables for image management
image_files = []  # This list will be filled dynamically
current_image_index = 0
current_text = ""  # Store the current displayed text

def load_image(image_path):
    """Load an image on the e-ink display"""
    graphics.set_pen(WHITE)  # White background
    graphics.clear()
    j.open_file(image_path)
    j.decode(0, 0)
    graphics.update()
    print(f"Image loaded: {image_path}")

def display_text(text, color=BLACK):
    """Display text on the e-ink display using the full screen"""
    global current_text
    current_text = text
    
    # Get display dimensions
    width, height = graphics.get_bounds()
    print(f"Display dimensions: {width}x{height}")
    
    graphics.set_pen(WHITE)  # White background
    graphics.clear()
    
    # Add a border with some margin
    border_margin = 10
    graphics.set_pen(BLACK)
    graphics.line(border_margin, border_margin, width - border_margin, border_margin)
    graphics.line(width - border_margin, border_margin, width - border_margin, height - border_margin)
    graphics.line(width - border_margin, height - border_margin, border_margin, height - border_margin)
    graphics.line(border_margin, height - border_margin, border_margin, border_margin)
    
    # Switch to selected color for text
    graphics.set_pen(color)
    
    # Set bitmap6 font
    graphics.set_font("bitmap6")
    
    # Calculate available space for text
    text_margin = 20
    text_width = width - (text_margin * 2)
    text_height = height - (text_margin * 2)
    
    # Start with a large size and reduce until text fits
    # Use binary search to find optimal size faster
    min_size = 1
    max_size = 10  # Start with a very large size
    optimal_size = min_size
    
    # Split text into paragraphs
    paragraphs = text.split('\n')
    
    while min_size <= max_size:
        mid_size = (min_size + max_size) // 2
        
        # Calculate total height needed at this text size
        total_height = 0
        for paragraph in paragraphs:
            # Calculate how many characters fit per line at this size
            char_width = 6 * mid_size  # Approximate character width for bitmap6
            chars_per_line = max(1, text_width // char_width)
            
            # Calculate lines needed for this paragraph
            words = paragraph.split()
            if not words:  # Empty paragraph (just a newline)
                total_height += (8 * mid_size)  # Add one line height for bitmap6
                continue
                
            lines = 1
            current_line_chars = 0
            
            for word in words:
                # Plus 1 for space
                if current_line_chars + len(word) + 1 <= chars_per_line:
                    current_line_chars += len(word) + 1
                else:
                    lines += 1
                    current_line_chars = len(word)
            
            # Add height for this paragraph with bitmap6 font
            paragraph_height = lines * (8 * mid_size)
            total_height += paragraph_height
            
            # Add extra space between paragraphs
            total_height += 5
        
        # Check if text fits at this size
        if total_height <= text_height:
            optimal_size = mid_size  # This size works
            min_size = mid_size + 1  # Try larger
        else:
            max_size = mid_size - 1  # Try smaller
    
    print(f"Selected text size: {optimal_size}")
    
    # Draw the text with proper wrapping and margin
    graphics.text(text, text_margin, text_margin, text_width, optimal_size)
    
    # Update the display
    graphics.update()
    print(f"Text displayed: {text}")


def connect_to_wifi():
    """Connect to WiFi network"""
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)
    
    max_wait = 10
    while max_wait > 0:
        if wlan.status() < 0 or wlan.status() >= 3:
            break
        max_wait -= 1
        print('Waiting for connection...')
        time.sleep(1)
    
    if wlan.status() != 3:
        print('Unable to connect to network')
        return None
    
    ip = wlan.ifconfig()[0]
    print(f'Connected on {ip}')
    return ip

def scan_image_files():
    """Scan all .jpg files in the root directory"""
    import os
    global image_files
    files = os.listdir()
    image_files = [f for f in files if f.lower().endswith('.jpg')]
    image_files.sort()
    print(f"Found images: {image_files}")
    return image_files

def parse_form_data(request_str):
    """Parse form data from POST request"""
    form_data = {}
    
    # Find the form data section
    parts = request_str.split('\r\n\r\n')
    if len(parts) < 2:
        return form_data
    
    # Parse the form data
    form_content = parts[1]
    fields = form_content.split('&')
    
    for field in fields:
        if '=' in field:
            key, value = field.split('=', 1)
            # Handle URL encoding
            value = value.replace('+', ' ')
            value = value.replace('%3F', '?')
            value = value.replace('%21', '!')
            value = value.replace('%2C', ',')
            value = value.replace('%2E', '.')
            value = value.replace('%3A', ':')
            value = value.replace('%3B', ';')
            value = value.replace('%0D%0A', '\n')  # Handle newlines
            value = value.replace('%0A', '\n')
            form_data[key] = value
    
    return form_data

def start_webserver():
    """Start a simple web server"""
    global current_image_index, image_files, current_text
    
    # Connect to WiFi
    ip = connect_to_wifi()
    if not ip:
        return
        
    # Scan all image files
    scan_image_files()
    
    # Check if any images were found
    if not image_files:
        print("No .jpg files found in the root directory!")
        # Display a message instead
        display_text("No images found")
    else:
        # Load first image
        try:
            load_image(image_files[current_image_index])
            print(f"First image loaded: {image_files[current_image_index]}")
        except OSError as e:
            print(f"Error loading image: {e}")
            display_text(f"Error: {str(e)}")
    
    # Open socket on port 80
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    try:
        s.bind(addr)
        s.listen(1)
    except OSError as e:
        if e.errno == 98:  # Address already in use
            print("Port 80 is already in use.")
            return
        else:
            print(f"Socket error: {e}")
            return
    
    print(f'Listening on http://{ip}/')
    print("Server is ready to receive requests.")
    
    # Main server loop
    while True:
        try:
            client, addr = s.accept()
            print(f'Client connected from {addr}')
            
            # Receive and decode the request
            request = client.recv(1024)
            request_str = request.decode('utf-8')
            request_line = request_str.split('\r\n')[0]
            
            # Extract method and path
            method = request_line.split(' ')[0]  # GET or POST
            url_path = request_line.split(' ')[1]
            print(f"Request: {method} {url_path}")
            
            # Status message to display on page
            status_message = ""
            
            # Handle POST request for text display
            if method == "POST" and url_path == "/display-text":
                form_data = parse_form_data(request_str)
                if 'text' in form_data:
                    text_content = form_data['text']
                    print(f"Received text: {text_content}")
                    
                    # Get text color if specified
                    text_color = BLACK
                    if 'color' in form_data:
                        color_name = form_data['color'].upper()
                        if color_name == 'BLACK':
                            text_color = BLACK
                        elif color_name == 'RED':
                            text_color = RED
                        elif color_name == 'BLUE':
                            text_color = BLUE
                        elif color_name == 'GREEN':
                            text_color = GREEN
                        elif color_name == 'YELLOW':
                            text_color = YELLOW
                        elif color_name == 'ORANGE':
                            text_color = ORANGE
                    
                    # Display the text on the e-ink screen
                    display_text(text_content, text_color)
                    status_message = "Text displayed on e-ink screen"
                
                # Redirect back to the main page
                response = "HTTP/1.0 303 See Other\r\nLocation: /\r\n\r\n"
                client.send(response.encode())
                client.close()
                continue
            
            # Handle GET requests for images
            if url_path == '/nextimage' and image_files:
                current_image_index = (current_image_index + 1) % len(image_files)
                load_image(image_files[current_image_index])
                status_message = f"New image loaded: {image_files[current_image_index]}"
                
            elif url_path == '/previmage' and image_files:
                current_image_index = (current_image_index - 1) % len(image_files)
                load_image(image_files[current_image_index])
                status_message = f"Previous image loaded: {image_files[current_image_index]}"
                
            elif url_path.startswith('/image/') and image_files:
                # Extract filename from the URL
                filename = url_path.replace('/image/', '')
                if filename in image_files:
                    current_image_index = image_files.index(filename)
                    load_image(filename)
                    status_message = f"Image loaded: {filename}"
                else:
                    status_message = f"Image not found: {filename}"
                
            elif url_path.startswith('/image') and image_files:
                try:
                    # Extract the image index from the URL
                    img_idx = int(url_path.replace('/image', ''))
                    if 0 <= img_idx < len(image_files):
                        current_image_index = img_idx
                        load_image(image_files[current_image_index])
                        status_message = f"Specific image loaded: {image_files[current_image_index]}"
                except ValueError:
                    pass
                
            elif url_path == '/refresh':
                old_image = image_files[current_image_index] if image_files else None
                scan_image_files()
                if old_image and old_image in image_files:
                    current_image_index = image_files.index(old_image)
                else:
                    current_image_index = 0
                    
                if image_files:
                    load_image(image_files[current_image_index])
                    status_message = f"Image list refreshed. {len(image_files)} images found."
                else:
                    status_message = "No images found after refresh"
            
            # Create HTML content
            html = """<!DOCTYPE html>
<html>
<head>
    <title>E-ink Display Control</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        body { font-family: Arial; text-align: center; padding: 20px; }
        .button { 
            background-color: #4CAF50; 
            color: white; 
            padding: 15px 32px; 
            margin: 10px;
            font-size: 16px;
            border: none;
            border-radius: 8px;
        }
        .active { background-color: #ff9800; }
        .text-container {
            border: 2px dashed #ccc; 
            padding: 20px; 
            margin: 20px auto; 
            max-width: 500px; 
            border-radius: 10px; 
            background-color: #f9f9f9;
        }
        textarea {
            width: 100%;
            height: 100px;
            margin: 10px 0;
            padding: 8px;
            font-family: Arial;
            border-radius: 5px;
            border: 1px solid #ddd;
        }
        .color-selection {
            display: flex;
            justify-content: center;
            margin: 10px 0;
            flex-wrap: wrap;
        }
        .color-option {
            margin: 5px;
        }
    </style>
</head>
<body>
    <h1>E-ink Display Control</h1>
"""
            # Add status message if there is one
            if status_message:
                html += f"<p style='background-color: #dff0d8; padding: 10px; color: #3c763d;'>{status_message}</p>"
            
            # Add navigation buttons if we have images
            if image_files:
                html += f"<p>Current image: <strong>{image_files[current_image_index] if image_files else 'None'}</strong></p>"
                
                html += """
    <div>
        <a href="/previmage"><button class="button">Previous</button></a>
        <a href="/nextimage"><button class="button">Next</button></a>
        <a href="/refresh"><button class="button" style="background-color: #2196F3;">Refresh Image List</button></a>
    </div>
"""
            
            # Add text display section
            html += """
    <div class="text-container">
        <h2>Display Text on E-ink Screen</h2>
        <form action="/display-text" method="post">
            <textarea name="text" placeholder="Enter text to display on the e-ink screen" required></textarea>
            
            <div class="color-selection">
                <div class="color-option">
                    <input type="radio" id="black" name="color" value="black" checked>
                    <label for="black">Black</label>
                </div>
                <div class="color-option">
                    <input type="radio" id="red" name="color" value="red">
                    <label for="red">Red</label>
                </div>
                <div class="color-option">
                    <input type="radio" id="blue" name="color" value="blue">
                    <label for="blue">Blue</label>
                </div>
                <div class="color-option">
                    <input type="radio" id="green" name="color" value="green">
                    <label for="green">Green</label>
                </div>
                <div class="color-option">
                    <input type="radio" id="yellow" name="color" value="yellow">
                    <label for="yellow">Yellow</label>
                </div>
                <div class="color-option">
                    <input type="radio" id="orange" name="color" value="orange">
                    <label for="orange">Orange</label>
                </div>
            </div>
            
            <button type="submit" class="button">Display Text</button>
        </form>
"""

            # If there is current text being displayed, show it
            if current_text:
                html += f"<p>Currently displayed text: <strong>{current_text}</strong></p>"
                
            html += "</div>"
            
            # Add image buttons section if we have images
            if image_files:
                html += """
    <h2>All Available Images:</h2>
    <div style="display: flex; flex-wrap: wrap; justify-content: center;">
"""
                
                # Add image buttons
                for i, img in enumerate(image_files):
                    button_class = "button active" if i == current_image_index else "button"
                    html += f'<a href="/image/{img}" style="margin: 5px;"><button class="{button_class}">{img}</button></a>'
                
                html += "</div>"
            
            # Close HTML
            html += """
</body>
</html>
"""
            
            # Send HTTP response
            response = "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n" + html
            
            try:
                client.send(response.encode())
            except:
                # Try alternative method if the first fails
                client.send(b"HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n")
                client.send(html.encode())
                
        except Exception as e:
            print(f"Error: {e}")
            try:
                client.send(b"HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\n\r\n")
                client.send(b"<html><body><h1>Error</h1></body></html>")
            except:
                pass
        finally:
            # Always close the client connection
            client.close()

# Start the webserver
if __name__ == "__main__":
    try:
        start_webserver()
    except Exception as e:
        print(f"Critical error: {e}")
2 Likes