diff --git a/boot.py b/boot.py new file mode 100644 index 0000000..5b629bb --- /dev/null +++ b/boot.py @@ -0,0 +1,27 @@ +import network +from machine import Pin +from config import * + +def do_connect(): + led = Pin(8, Pin.OUT) + sta_if = network.WLAN(network.STA_IF) + sta_if.ifconfig((ip, '255.255.255.0', gateway, '1.1.1.1')) + if not sta_if.isconnected(): + print('connecting to network...') + sta_if.active(True) + sta_if.connect(ssid, password) + led.on() + while not sta_if.isconnected(): + pass + print('network config:', sta_if.ifconfig()) + +do_connect() + +ap = network.WLAN(network.AP_IF) +ap.active(True) +ap.config(essid="led", password="qwerty1234") +print(ap.ifconfig()) + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..6db7767 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + LED Control + + + +

Control LEDs

+
+ + + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+ + + diff --git a/main.css b/main.css new file mode 100644 index 0000000..7b3d403 --- /dev/null +++ b/main.css @@ -0,0 +1,40 @@ +body { + font-family: Arial, sans-serif; + background-color: #f0f0f0; + margin: 0; + padding: 20px; +} + +h1 { + color: #333; +} + +form { + margin-bottom: 20px; +} + +label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="text"], +input[type="range"], +input[type="color"], +button { + margin-bottom: 10px; + padding: 10px; + font-size: 16px; +} + +button:disabled { + background-color: #ccc; +} + +button { + margin-right: 5px; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; +} diff --git a/main.js b/main.js new file mode 100644 index 0000000..e601f25 --- /dev/null +++ b/main.js @@ -0,0 +1,95 @@ +let delayTimeout; + +async function post(path, value) { +console.log(path, value); + try { + const response = await fetch(path, { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: value + }); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + } catch (error) { + console.error('Error during POST request:', error); + } +} + +async function get(path) { + try { + const response = await fetch(path); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + return await response.json(); // Assuming you are expecting JSON response + } catch (error) { + console.error('Error during GET request:', error); + } +} + +async function updateColor(event) { + event.preventDefault(); + const color = document.getElementById('color').value; + await post("/color", color); +} + +async function updateColor2(event) { + event.preventDefault(); + const color = document.getElementById('color2').value; + await post("/color2", color); +} + +async function updatePattern(pattern) { + event.preventDefault(); + await post("/pattern", pattern); +} + +async function updateDelay(event) { + event.preventDefault(); + clearTimeout(delayTimeout); + delayTimeout = setTimeout(async function() { + const delay = document.getElementById('delay').value; + await post('/delay', delay); + }, 500); +} + +async function updateNumLeds(event) { + event.preventDefault(); + const numLeds = document.getElementById('num_leds').value; + await post('/num_leds', numLeds); +} + +function createPatternButtons(patterns) { + const container = document.getElementById('pattern_buttons'); + container.innerHTML = ''; // Clear previous buttons + + patterns.forEach(pattern => { + const button = document.createElement('button'); + button.type = 'button'; // Use 'button' instead of 'submit' + button.textContent = pattern; + button.value = pattern; + button.addEventListener('click', async function(event) { + event.preventDefault(); + await updatePattern(pattern); + }); + container.appendChild(button); + }); +} + +document.addEventListener('DOMContentLoaded', async function() { + document.getElementById('color').addEventListener('input', updateColor); + document.getElementById('color2').addEventListener('input', updateColor2); + document.getElementById('delay').addEventListener('input', updateDelay); + document.getElementById('led_form').addEventListener('submit', updateNumLeds); + + try { + const patterns = await get("/patterns"); + console.log('Patterns fetched:', patterns); + createPatternButtons(patterns); + } catch (error) { + console.error('Error fetching patterns:', error); + } +}); diff --git a/main.py b/main.py new file mode 100644 index 0000000..9e3b4f4 --- /dev/null +++ b/main.py @@ -0,0 +1,187 @@ +from machine import Pin +from patterns import Patterns +import socket +import select +import json + +class LEDServer: + SETTINGS_FILE = "/settings.json" # Path should be adjusted for MicroPython's filesystem + + def __init__(self, num_leds=50, pin=4, led_pin=8, brigtness=255): + # Initialize NeoPixel Patterns + self.num_leds = num_leds + self.patterns = Patterns(pin, num_leds) + self.selected_pattern = "blink" + self.color = (16, 16, 0) + self.color2 = (16, 16, 0) + self.delay = 100 + + # Initialize single LED + self.led = Pin(led_pin, Pin.OUT) + + # Initialize server + self.server_socket = socket.socket() + self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.server_socket.bind(('0.0.0.0', 80)) + self.server_socket.listen(1) + self.server_socket.settimeout(1) # Adjust timeout as needed + + self.poll = select.poll() + self.poll.register(self.server_socket, select.POLLIN) + + # Load settings from file + self.load_settings() + # Read static files + color = f'#{self.color[0]:02x}{self.color[1]:02x}{self.color[2]:02x}' + color2 = f'#{self.color2[0]:02x}{self.color2[1]:02x}{self.color2[2]:02x}' + print(color) + self.html = self.read_file("/index.html").format(num_leds=self.num_leds, delay=self.delay, color=color, color2=color2).encode() + self.js = self.read_file("/main.js").encode('utf-8') + self.css = self.read_file("/main.css").encode('utf-8') + self.patterns_json = json.dumps(list(self.patterns.patterns.keys())) + + + + def read_file(self, file_path): + try: + with open(file_path, 'r') as file: + return file.read() + except OSError as e: + print(f"Error reading file {file_path}: {e}") + return "" + + def save_settings(self): + settings = { + "num_leds": self.num_leds, + "selected_pattern": self.selected_pattern, + "color": self.color, + "color2": self.color2, + "delay": self.delay + } + print(settings) + try: + with open(self.SETTINGS_FILE, 'w') as file: + json.dump(settings, file) + except OSError as e: + print(f"Error saving settings: {e}") + + def load_settings(self): + if self.file_exists(self.SETTINGS_FILE): + try: + with open(self.SETTINGS_FILE, 'r') as file: + settings = json.load(file) + self.num_leds = settings.get("num_leds", self.num_leds) + self.selected_pattern = settings.get("selected_pattern", self.selected_pattern) + self.color = tuple(settings.get("color", self.color)) + self.color2 = tuple(settings.get("color2", self.color2)) + self.delay = settings.get("delay", self.delay) + self.patterns.update_num_leds(4, self.num_leds) + self.patterns.set_delay(self.delay) + self.patterns.selected = self.selected_pattern + except (OSError, ValueError) as e: + print(f"Error loading settings: {e}") + + def file_exists(self, file_path): + try: + with open(file_path, 'r'): + return True + except OSError: + return False + + def handle_post(self, path, post_data, client_socket): + print(post_data) + if path == "/num_leds": + try: + self.num_leds = int(post_data) + self.patterns.update_num_leds(4, self.num_leds) + self.save_settings() + client_socket.send(b'HTTP/1.0 200 OK\r\n\r\n') + except ValueError: + client_socket.send(b'HTTP/1.0 400 Bad request\r\n\r\n') + + elif path == "/pattern": + self.selected_pattern = post_data + self.patterns.selected = post_data + self.save_settings() + client_socket.send(b'HTTP/1.0 200 OK\r\n\r\n') + + elif path == "/delay": + try: + self.delay = int(post_data) + self.patterns.set_delay(self.delay) + self.save_settings() + client_socket.send(b'HTTP/1.0 200 OK\r\n\r\n') + except ValueError: + client_socket.send(b'HTTP/1.0 400 Bad request\r\n\r\n') + + elif path == "/color": +# try: + self.patterns.set_color1(tuple(int(post_data[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB + self.save_settings() + client_socket.send(b'HTTP/1.0 200 OK\r\n\r\n') +# except: + +# client_socket.send(b'HTTP/1.0 400 Bad request\r\n\r\n') + elif path == "/color2": + try: + self.patterns.set_color2(tuple(int(post_data[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB + self.save_settings() + client_socket.send(b'HTTP/1.0 200 OK\r\n\r\n') + except: + client_socket.send(b'HTTP/1.0 400 Bad request\r\n\r\n') + else: + client_socket.send(b'HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\n') + + def handle_get(self, path, client_socket): + if path == "/": + client_socket.send(b'HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') + client_socket.send(self.html) + elif path == "/main.js": + client_socket.send(b'HTTP/1.0 200 OK\r\nContent-type: application/javascript\r\n\r\n') + client_socket.send(self.js) + elif path == "/main.css": + client_socket.send(b'HTTP/1.0 200 OK\r\nContent-type: text/css\r\n\r\n') + client_socket.send(self.css) + elif path == "/patterns": + client_socket.send(b'HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n') + client_socket.send(self.patterns_json.encode()) + else: + client_socket.send(b'HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\n') + + def start(self): + count = 0 + try: + while True: + count += 1 + events = self.poll.poll(1) + for file in events: + if file[0] == self.server_socket: + client_socket, addr = self.server_socket.accept() + request = client_socket.recv(1024).decode() + method, path, _ = request.split('\r\n')[0].split() + print(f"Method: {method}, Path: {path}") + if method == "POST": + post_data = request.split('\r\n\r\n')[1] if '\r\n\r\n' in request else '' + self.handle_post(path, post_data, client_socket) + elif method == "GET": + self.handle_get(path, client_socket) + client_socket.close() + + if count > 50: + self.led.off() + if count > 100: + self.led.on() + count = 0 + + self.patterns.tick() + + except Exception as e: + print("Error:", e) + finally: + self.server_socket.close() + self.save_settings() + +# Example of creating and starting the server +if __name__ == "__main__": + server = LEDServer() + server.start() diff --git a/patterns.py b/patterns.py index bc2c030..7f7d55a 100644 --- a/patterns.py +++ b/patterns.py @@ -1,114 +1,220 @@ from machine import Pin from neopixel import NeoPixel -import _thread -import time, math -import sys +import utime +import random -class LedPatterns: - def __init__(self, pin, num_leds): +class Patterns: + def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100): + self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds) self.num_leds = num_leds - self.np = NeoPixel(Pin(pin), num_leds) - self.run = True + self.pattern_step = 0 + self.last_update = utime.ticks_ms() + self.delay = delay + self.brightness = brightness + self.patterns = { + "color_wipe": self.color_wipe_step, + "rainbow_cycle": self.rainbow_cycle_step, + "theater_chase": self.theater_chase_step, + "blink": self.blink_step, + "random_color_wipe": self.random_color_wipe_step, + "random_rainbow_cycle": self.random_rainbow_cycle_step, + "random_theater_chase": self.random_theater_chase_step, + "random_blink": self.random_blink_step, + "color_transition": self.color_transition_step # Added color transition pattern + } + self.selected = selected + self.color1 = color1 + self.color2 = color2 + self.transition_duration = 5000 # Duration of color transition in milliseconds + self.transition_step = 0 - def color_chase(self, color, wait): - self.run = True + def tick(self): + self.patterns[self.selected]() + + def update_num_leds(self, pin, num_leds): + self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds) + self.num_leds = num_leds + self.pattern_step = 0 + + def set_delay(self, delay): + self.delay = delay + + def set_brightness(self, brightness): + self.brightness = brightness + + def set_color1(self, color): + print(color) + self.color1 = self.apply_brightness(color) + + def set_color2(self, color): + self.color2 = self.apply_brightness(color) + + def apply_brightness(self, color): + return tuple(int(c * self.brightness / 255) for c in color) + + def fill(self): for i in range(self.num_leds): - self.np[i] = color - self.np.write() - if not run: - break - time.sleep(wait) - - def wheel(self, pos): - if pos < 0 or pos > 255: - return (0, 0, 0) - if pos < 85: - return (255 - pos * 3, pos * 3, 0) - if pos < 170: - pos -= 85 - return (0, 255 - pos * 3, pos * 3) - pos -= 170 - return (pos * 3, 0, 255 - pos * 3) - - def rainbow_cycle(self, wait): - run = True - for j in range(255): + self.n[i] = self.color1 + self.n.write() + + def color_wipe_step(self): + color = self.apply_brightness(self.color1) + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + if self.pattern_step < self.num_leds: + for i in range(self.num_leds): + self.n[i] = (0, 0, 0) + self.n[self.pattern_step] = color + self.n.write() + self.pattern_step += 1 + else: + self.pattern_step = 0 + self.last_update = current_time + + def rainbow_cycle_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + def wheel(pos): + if pos < 85: + return (pos * 3, 255 - pos * 3, 0) + elif pos < 170: + pos -= 85 + return (255 - pos * 3, 0, pos * 3) + else: + pos -= 170 + return (0, pos * 3, 255 - pos * 3) + for i in range(self.num_leds): - self.np[i] = self.wheel((i * 256 // self.num_leds) + j) - self.np.write() - if not run: - break - time.sleep(wait) - - def breathing(self, color, duration): - steps = 256 - for _ in range(steps): - brightness = int(255 * abs(steps / 2 - _) / (steps / 2)) - self.np.fill((brightness * color[0] // 255, brightness * color[1] // 255, brightness * color[2] // 255)) - self.np.write() - time.sleep(duration / steps) - - def color_transition(self, start_color, end_color, duration): - steps = 256 - for step in range(steps): - color = ( - int(start_color[0] + (end_color[0] - start_color[0]) * step / steps), - int(start_color[1] + (end_color[1] - start_color[1]) * step / steps), - int(start_color[2] + (end_color[2] - start_color[2]) * step / steps), - ) - self.np.fill(color) - self.np.write() - if not self.running(): - return - time.sleep(duration / steps) + rc_index = (i * 256 // self.num_leds) + self.pattern_step + self.n[i] = self.apply_brightness(wheel(rc_index & 255)) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 256 + self.last_update = current_time + + def theater_chase_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + for i in range(self.num_leds): + if (i + self.pattern_step) % 3 == 0: + self.n[i] = self.color1 + else: + self.n[i] = (0, 0, 0) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 3 + self.last_update = current_time + + def blink_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + if self.pattern_step % 2 == 0: + for i in range(self.num_leds): + self.n[i] = self.color1 + else: + for i in range(self.num_leds): + self.n[i] = (0, 0, 0) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 2 + self.last_update = current_time + + def random_color_wipe_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + if self.pattern_step < self.num_leds: + for i in range(self.num_leds): + self.n[i] = (0, 0, 0) + self.n[self.pattern_step] = self.apply_brightness(color) + self.n.write() + self.pattern_step += 1 + else: + self.pattern_step = 0 + self.last_update = current_time + + def random_rainbow_cycle_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + def wheel(pos): + if pos < 85: + return (pos * 3, 255 - pos * 3, 0) + elif pos < 170: + pos -= 85 + return (255 - pos * 3, 0, pos * 3) + else: + pos -= 170 + return (0, pos * 3, 255 - pos * 3) + + random_offset = random.randint(0, 255) + for i in range(self.num_leds): + rc_index = (i * 256 // self.num_leds) + self.pattern_step + random_offset + self.n[i] = self.apply_brightness(wheel(rc_index & 255)) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 256 + self.last_update = current_time + + def random_theater_chase_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + for i in range(self.num_leds): + if (i + self.pattern_step) % 3 == 0: + self.n[i] = self.apply_brightness(color) + else: + self.n[i] = (0, 0, 0) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 3 + self.last_update = current_time + + def random_blink_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + if self.pattern_step % 2 == 0: + for i in range(self.num_leds): + self.n[i] = self.apply_brightness(color) + else: + for i in range(self.num_leds): + self.n[i] = (0, 0, 0) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 2 + self.last_update = current_time + + def color_transition_step(self): + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + # Calculate transition factor based on elapsed time + transition_factor = (self.pattern_step * 100) / self.transition_duration + if transition_factor > 100: + transition_factor = 100 + color = self.interpolate_color(self.color1, self.color2, transition_factor / 100) - def scanner(self, color, speed): - position = 0 - direction = 1 - - while True: - self.np.fill((0, 0, 0)) - self.np[position] = color - self.np.write() - time.sleep(speed) - position += direction - if position == self.num_leds - 1 or position == 0: - direction *= -1 - - def bidirectional_scanner(self, color_left, color_right=None, speed=0.1): - color_right = color_right or color_left - position_left = 0 - position_right = self.num_leds - 1 - direction_left = 1 - direction_right = -1 - while True: - self.np.fill((0, 0, 0)) - self.np[position_left] = color_left - self.np[position_right] = color_right - self.np.write() - time.sleep(speed) - - position_left += direction_left - position_right += direction_right - - if position_left == self.num_leds - 1 or position_left == 0: - direction_left *= -1 - if position_right == self.num_leds - 1 or position_right == 0: - direction_right *= -1 - - def sine_wave_propagation(self, color, speed): - frequency = 10 - phase = 0 - while True: - self.np.fill((0, 0, 0)) + # Apply the interpolated color to all LEDs for i in range(self.num_leds): - brightness = int(127.5 * (math.sin(frequency * i + phase) + 1)) - self.np[i] = (brightness * color[0] // 255, brightness * color[1] // 255, brightness * color[2] // 255) - self.np.write() - time.sleep(speed) - phase += 0.1 + self.n[i] = self.apply_brightness(color) + self.n.write() - def fill(self,color): - for i in range(self.num_leds): - self.np[i] = color - self.np.write() + self.pattern_step += self.delay + if self.pattern_step > self.transition_duration: + self.pattern_step = 0 + + self.last_update = current_time + + def interpolate_color(self, color1, color2, factor): + return ( + int(color1[0] + (color2[0] - color1[0]) * factor), + int(color1[1] + (color2[1] - color1[1]) * factor), + int(color1[2] + (color2[2] - color1[2]) * factor) + ) + +if __name__ == "__main__": + p = Patterns(4, 180) + p.set_color1((255,0,0)) + p.set_color2((0,255,0)) + try: + while True: + for key in p.patterns: + print(key) + for _ in range(1000): + p.tick() + utime.sleep_ms(1) + except KeyboardInterrupt: + p.fill((0, 0, 0)) diff --git a/scripts.js b/scripts.js new file mode 100644 index 0000000..87a7e8e --- /dev/null +++ b/scripts.js @@ -0,0 +1,61 @@ +let delayTimeout; + +function post(path, value) { + fetch(path, { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: encodeURIComponent(value) + }); +} + +function get(path, value) { + fetch(path, { + method: "GET", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + }); +} + +function updateColor() { + const color = document.getElementById('color').value; + post("POST", "/color", color); +} + +function updatePattern(pattern) { + const patternButtons = document.querySelectorAll('button[name="pattern"]'); + //patternButtons.forEach(button => button.disabled = true); + post("/pattern", pattern); +} + +function updateDelay() { + clearTimeout(delayTimeout); + delayTimeout = setTimeout(function() { + const delay = document.getElementById('delay').value; + post('/delay', delay); + }, 500); +} + +function updateNumLeds(event) { + event.preventDefault(); + const numLeds = document.getElementById('num_leds').value; + post('/num_leds', numLeds); +} + +document.addEventListener('DOMContentLoaded', function() { + document.getElementById('color').addEventListener('input', updateColor); + document.getElementById('delay').addEventListener('input', updateDelay); + document.getElementById('led_form').addEventListener('submit', updateNumLeds); + + const patternButtons = document.querySelectorAll('button[name="pattern"]'); + patternButtons.forEach(function(button) { + button.addEventListener('click', function(event) { + event.preventDefault(); + const pattern = this.value; + updatePattern(pattern); + }); + }); +}); + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..418247e --- /dev/null +++ b/styles.css @@ -0,0 +1,79 @@ +body { + font-family: Arial, sans-serif; + padding: 20px; +} + +h1, h2 { + color: #333; +} + +form { + margin-bottom: 20px; +} + +input[type="number"], input[type="color"], input[type="range"] { + margin-right: 10px; +} + +button { + padding: 5px 10px; + background-color: #007bff; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +button:hover { + background-color: #0056b3; +} + +#delay_value { + display: inline-block; + width: 80px; +} + +#patterns_radio_buttons label { + display: block; +} +body { + font-family: Arial, sans-serif; + background-color: #f0f0f0; + margin: 0; + padding: 20px; +} + +h1 { + color: #333; +} + +form { + margin-bottom: 20px; +} + +label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="text"], +input[type="range"], +input[type="color"], +button { + margin-bottom: 10px; + padding: 10px; + font-size: 16px; +} + +button:disabled { + background-color: #ccc; +} + +button { + margin-right: 5px; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; +} +