1
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -1,2 +1,38 @@
|
|||||||
# esp32-ir
|
# 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`
|
||||||
@@ -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)
|
|
||||||
+67
@@ -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("Файл кодов пока пуст")
|
||||||
@@ -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
|
||||||
+162
@@ -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()
|
||||||
Reference in New Issue
Block a user