#!/usr/bin/env python3
"""
Завантажує голосові підказки (.amr) у памʼять SIM800L, у теку C:\\User\\.

Модем підʼєднано до компʼютера через Nano зі скетчем sim800_mist
(або напряму через USB-UART-перехідник). Приклад:

    python zavantazhyty_zvuky.py COM5 2.amr 3.amr 4.amr 5.amr 6.amr 7.amr 8.amr

Файл із тією самою назвою в модулі перезаписується. Потрібен пакет pyserial:
    pip install pyserial

© 2026 BoxForma (boxforma.com). Ліцензія MIT, повний текст — у LICENSE.txt:
можна вільно копіювати, змінювати й поширювати, зберігши ці рядки.
Надається «як є», без жодних гарантій; автори не відповідають за наслідки
використання. Пристрій ви збираєте й підключаєте на власний ризик.
"""

import argparse
import os
import sys
import time

try:
    import serial
except ImportError:
    sys.exit("Немає пакета pyserial. Установіть: pip install pyserial")

FOLDER = "C:\\User\\"
WRITE_MAX = 10240   # більше одна команда AT+FSWRITE не приймає
CHUNK = 32          # байтів за раз: міст на Nano встигає їх переслати
PAUSE = 0.012       # пауза між шматками, с


def read_until(port, wanted, timeout):
    """Читає відповідь модема, доки в ній не зʼявиться один із рядків wanted."""
    end = time.time() + timeout
    buf = b""
    while time.time() < end:
        data = port.read(port.in_waiting or 1)
        if data:
            buf += data
            text = buf.decode("latin-1")
            if any(w in text for w in wanted):
                return text
    return buf.decode("latin-1")


def is_ok(reply):
    return "\r\nOK\r\n" in reply or reply.strip().endswith("OK")


def at(port, cmd, timeout=3.0, wanted=("\r\nOK\r\n", "ERROR")):
    port.reset_input_buffer()
    port.write((cmd + "\r\n").encode("ascii"))
    reply = read_until(port, wanted, timeout)
    shown = " ".join(line for line in reply.split() if line != cmd) or "(немає відповіді)"
    print(f"  {cmd}  ->  {shown}")
    return reply


def fail(message):
    sys.exit(f"\nПОМИЛКА: {message}")


def connect(port):
    print("Чекаю на модем...")
    for _ in range(15):
        if is_ok(at(port, "AT", timeout=1.0)):
            break
    else:
        fail("модем не відповідає. Перевірте живлення SIM800L, дроти TX/RX і те, що "
             "монітор порту в Arduino IDE закрито.")
    at(port, "ATE0")  # без луни команд
    if "+FSDRIVE" not in at(port, "AT+FSDRIVE=0"):
        fail("модем не знає файлових команд (AT+FSDRIVE). Потрібна прошивка SIM800 із файловою системою.")
    at(port, "AT+FSMEM")


def upload(port, path):
    name = os.path.basename(path)
    remote = FOLDER + name
    with open(path, "rb") as f:
        data = f.read()
    if not data:
        fail(f"{name} порожній")
    if not data.startswith(b"#!AMR\n"):
        fail(f"{name} — не AMR-NB (немає заголовка #!AMR). Перекодуйте запис через perekoduvaty.bat.")

    print(f"\n{name}: {len(data)} байтів -> {remote}")
    at(port, f"AT+FSDEL={remote}")  # ERROR означає, що такого файлу ще не було
    if not is_ok(at(port, f"AT+FSCREATE={remote}")):
        fail(f"не вдалося створити {remote}")

    for start in range(0, len(data), WRITE_MAX):
        part = data[start:start + WRITE_MAX]
        mode = 0 if start == 0 else 1  # 0 — з початку файлу, 1 — дописати
        reply = at(port, f"AT+FSWRITE={remote},{mode},{len(part)},100",
                   timeout=5.0, wanted=(">", "ERROR"))
        if ">" not in reply:
            fail("модем не прийняв команду запису")
        for i in range(0, len(part), CHUNK):
            port.write(part[i:i + CHUNK])
            port.flush()
            time.sleep(PAUSE)
            done = start + min(i + CHUNK, len(part))
            print(f"\r  записано {done} з {len(data)} байтів", end="", flush=True)
        print()
        if not is_ok(read_until(port, ("\r\nOK\r\n", "ERROR"), 30.0)):
            fail("модем не підтвердив запис")

    reply = at(port, f"AT+FSFLSIZE={remote}")
    size = None
    if "+FSFLSIZE:" in reply:
        size = int(reply.split("+FSFLSIZE:")[1].split()[0])
    if size != len(data):
        fail(f"у модулі {size} байтів замість {len(data)}: файл записано не повністю, повторіть")
    print(f"  {name} завантажено й перевірено")


def main():
    parser = argparse.ArgumentParser(description="Завантаження .amr у памʼять SIM800L")
    parser.add_argument("port", help="COM-порт, наприклад COM5")
    parser.add_argument("files", nargs="+", help="файли .amr, наприклад 2.amr 3.amr")
    parser.add_argument("--baud", type=int, default=9600)
    args = parser.parse_args()

    for path in args.files:
        if not os.path.isfile(path):
            fail(f"немає файлу {path}")

    with serial.Serial(args.port, args.baud, timeout=0.1) as port:
        time.sleep(3)  # Nano перезапускається, коли відкривають порт
        connect(port)
        for path in args.files:
            upload(port, path)
        print("\nФайли в модулі:")
        at(port, f"AT+FSLS={FOLDER}")
    print("\nГотово. Завантажте назад основний скетч: syhnalizatsiia чи zamok.")


if __name__ == "__main__":
    main()
