diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/README.md b/README.md index 2cfa67f..0a3385b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,38 @@ # esp32-ir + + +### воспроизвести сигнал по имени +``` +from transmit import send_code +send_code("power") +``` + +### сделать новую запись +``` +from capture import capture_raw +data = capture_raw() +save_code("name", data) +``` + +### посмотреть записи +``` +from capture import list_codes +list_codes() +``` + + +### start server by hands +``` +import wifi_server +wifi_server.main() +``` + + + +#### пример запущенного сервера +Сервер запущен: `http://192.168.1.22/` + +Записанные команды: `http://192.168.1.22/list` + +Исполнение команды: `http://192.168.1.22/send?code=power` \ No newline at end of file diff --git a/blink_test.py b/blink_test.py deleted file mode 100644 index c3cbf55..0000000 --- a/blink_test.py +++ /dev/null @@ -1,9 +0,0 @@ -from machine import Pin -import time -pin = Pin(19, Pin.OUT) -for i in range(10): - pin.value(1) - time.sleep(0.5) - pin.value(0) - time.sleep(0.5) - print(i) diff --git a/capture.py b/capture.py new file mode 100644 index 0000000..f58200a --- /dev/null +++ b/capture.py @@ -0,0 +1,67 @@ +from machine import Pin +import time +import json + +RECV_PIN = 20 + + +def capture_raw(pin_num=RECV_PIN, timeout_ms=5000, idle_us=8000): + """ + Захватывает тайминги ИК-сигнала. + timeout_ms - сколько ждать начала сигнала + idle_us - через сколько мкс тишины считать сигнал законченным + """ + pin = Pin(pin_num, Pin.IN, Pin.PULL_UP) + timings = [] + + print("Ожидание сигнала... наведите пульт и нажмите кнопку") + + # Ждём начала сигнала (первый переход в 0, т.к. сигнал инвертирован) + start_wait = time.ticks_ms() + while pin.value() == 1: + if time.ticks_diff(time.ticks_ms(), start_wait) > timeout_ms: + print("Таймаут, сигнал не обнаружен") + return [] + + last_state = pin.value() + last_time = time.ticks_us() + + while True: + state = pin.value() + now = time.ticks_us() + + if state != last_state: + duration = time.ticks_diff(now, last_time) + timings.append(duration) + last_time = now + last_state = state + + # если долго нет изменений и мы в состоянии "покоя" (1) - сигнал закончился + if state == 1 and time.ticks_diff(now, last_time) > idle_us: + break + + print(f"Захвачено {len(timings)} импульсов") + return timings + + +def save_code(name, timings, filename="codes.json"): + try: + with open(filename, "r") as f: + codes = json.load(f) + except OSError: + codes = {} + + codes[name] = timings + with open(filename, "w") as f: + json.dump(codes, f) + print(f"Сохранено: '{name}' ({len(timings)} импульсов)") + + +def list_codes(filename="codes.json"): + try: + with open(filename, "r") as f: + codes = json.load(f) + for name, timings in codes.items(): + print(f"{name}: {len(timings)} импульсов") + except OSError: + print("Файл кодов пока пуст") diff --git a/dotenv.py b/dotenv.py new file mode 100644 index 0000000..6846275 --- /dev/null +++ b/dotenv.py @@ -0,0 +1,28 @@ +""".env file loader""" + +ENV_FILE = ".env" + + +def load_env(filename=ENV_FILE): + """ + Формат файла: + WIFI_SSID=ИмяСети + WIFI_PASSWORD=Пароль + Пустые строки и строки начинающиеся с # игнорируются. + Значения не нужно оборачивать в кавычки. + """ + env = {} + try: + with open(filename, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + if "=" not in line: + continue + key, value = line.split("=", 1) + env[key.strip()] = value.strip() + except OSError: + print( + f"Файл {filename} не найден. Создайте его с ключами WIFI_SSID и WIFI_PASSWORD") + return env diff --git a/main.py b/main.py index 06d7405..b77b3cd 100644 Binary files a/main.py and b/main.py differ diff --git a/wifi_server.py b/wifi_server.py new file mode 100644 index 0000000..60143ee --- /dev/null +++ b/wifi_server.py @@ -0,0 +1,162 @@ +import network +import socket +import time +import json + +from dotenv import load_env + +# ==== НАСТРОЙКИ ==== +TRANSMIT_PIN = 19 +# ==================== + + +def connect_wifi(ssid, password, timeout_s=15): + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + if not wlan.isconnected(): + print(f"Подключение к WiFi '{ssid}'...") + wlan.connect(ssid, password) + start = time.time() + while not wlan.isconnected(): + if time.time() - start > timeout_s: + print("Не удалось подключиться к WiFi (таймаут)") + return None + time.sleep(0.5) + ip = wlan.ifconfig()[0] + print(f"WiFi подключен. IP адрес: {ip}") + return ip + + +def load_codes(filename="codes.json"): + try: + with open(filename, "r") as f: + return json.load(f) + except OSError: + return {} + + +def send_ir_code(name, pin_num=TRANSMIT_PIN): + from transmit import send_code + send_code(name, pin_num=pin_num) + + +def url_decode(s): + """Простейший url-decode для параметров запроса (без внешних зависимостей).""" + s = s.replace("+", " ") + result = "" + i = 0 + while i < len(s): + if s[i] == "%" and i + 2 < len(s): + try: + result += chr(int(s[i + 1:i + 3], 16)) + i += 3 + continue + except ValueError: + pass + result += s[i] + i += 1 + return result + + +def parse_query(path): + """Возвращает (route, {param: value}) из строки вида /send?code=power""" + if "?" not in path: + return path, {} + route, qs = path.split("?", 1) + params = {} + for pair in qs.split("&"): + if "=" in pair: + k, v = pair.split("=", 1) + params[url_decode(k)] = url_decode(v) + return route, params + + +def handle_request(path): + route, params = parse_query(path) + + if route == "/send": + name = params.get("code") + if not name: + return 400, {"error": "укажите ?code=имя_кода"} + codes = load_codes() + if name not in codes: + return 404, {"error": f"код '{name}' не найден", "available": list(codes.keys())} + try: + send_ir_code(name) + return 200, {"status": "ok", "sent": name} + except Exception as e: + return 500, {"error": str(e)} + + elif route == "/list": + codes = load_codes() + return 200, {"codes": list(codes.keys())} + + elif route == "/": + return 200, {"status": "ESP32 IR сервер запущен", "endpoints": ["/list", "/send?code=ИМЯ"]} + + else: + return 404, {"error": "неизвестный маршрут"} + + +def run_server(ip): + addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1] + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(addr) + s.listen(1) + print(f"Сервер запущен: http://{ip}/") + print(f"Примеры: http://{ip}/list | http://{ip}/send?code=power") + + while True: + try: + conn, client_addr = s.accept() + request = conn.recv(1024).decode("utf-8", "ignore") + + # Парсим первую строку запроса: "GET /send?code=power HTTP/1.1" + first_line = request.split("\r\n", 1)[0] + parts = first_line.split(" ") + path = parts[1] if len(parts) > 1 else "/" + + status, body = handle_request(path) + body_json = json.dumps(body) + + status_text = {200: "OK", 400: "Bad Request", 404: "Not Found", + 500: "Internal Server Error"}.get(status, "OK") + + response = ( + f"HTTP/1.1 {status} {status_text}\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n" + f"Content-Length: {len(body_json)}\r\n" + "Connection: close\r\n" + "\r\n" + f"{body_json}" + ) + conn.send(response.encode()) + conn.close() + except Exception as e: + print(f"Ошибка обработки запроса: {e}") + try: + conn.close() + except Exception: + pass + + +def main(): + env = load_env() + ssid = env.get("WIFI_SSID") + password = env.get("WIFI_PASSWORD") + + if not ssid or not password: + print("Ошибка: заполните WIFI_SSID и WIFI_PASSWORD в .env") + return + + ip = connect_wifi(ssid, password) + if ip: + run_server(ip) + else: + print("Сервер не запущен: нет WiFi") + + +if __name__ == "__main__": + main()