SIM7600x SIM826X 实现HTTP(S) POST

bash 复制代码
bash 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
import glob
import threading
import serial
from flask import Flask, request, render_template_string, redirect, url_for, jsonify

# ================== Configuration ==================
BAUD = 115200
DEFAULT_JSON = '{"value":5201314}'

WEBHOOK_API_URL  = "https://webhook.site/9a916047-dd6a-468e-89c2-14a3ea163651"
WEBHOOK_VIEW_URL = "https://webhook.site/#!/view/9a916047-dd6a-468e-89c2-14a3ea163651"

DEFAULT_APN = "CMNET"   # change to your carrier APN if needed
CID = 1                # PDP context id for HTTP stack
# ===================================================

app = Flask(__name__)

# --- shared runtime state (simple) ---
LOGS = ""
LOCK = threading.Lock()
RUNNING = False
LAST_RESULT = {"ok": None, "msg": ""}


def append_log(s: str):
    global LOGS
    with LOCK:
        LOGS += s.rstrip() + "\n"
        if len(LOGS) > 20000:
            LOGS = LOGS[-20000:]


def set_running(v: bool):
    global RUNNING
    with LOCK:
        RUNNING = v


def get_state():
    with LOCK:
        return LOGS, RUNNING, LAST_RESULT.copy()


def set_result(ok: bool, msg: str):
    global LAST_RESULT
    with LOCK:
        LAST_RESULT = {"ok": ok, "msg": msg}


def list_serial_ports():
    return sorted(glob.glob("/dev/ttyUSB*"))


class Modem:
    def __init__(self, port: str):
        self.port = port
        self.ser = serial.Serial(port, BAUD, timeout=0.2)
        time.sleep(0.3)
        self.ser.reset_input_buffer()

    def close(self):
        try:
            self.ser.close()
        except Exception:
            pass

    def _read_some(self):
        n = self.ser.in_waiting
        if n:
            return self.ser.read(n).decode(errors="ignore")
        return ""

    def cmd(self, command: str, timeout=4.0, wait=0.1, expect_ok=True):
        """
        Send one AT command and read until OK/ERROR or timeout.
        Logs command and full response.
        """
        append_log(f">> {command}")
        self.ser.write((command + "\r\n").encode())
        time.sleep(wait)

        buf = ""
        end = time.time() + timeout
        while time.time() < end:
            chunk = self._read_some()
            if chunk:
                buf += chunk
                # Most AT commands end with OK or ERROR
                if "OK" in buf or "ERROR" in buf:
                    break
            else:
                time.sleep(0.05)

        if buf.strip():
            append_log(buf.strip())
        else:
            append_log("(no response)")

        if expect_ok and "OK" not in buf:
            raise RuntimeError(f"Command failed: {command}")
        return buf

    def wait_urc(self, keyword: str, timeout=20.0):
        """Wait for an URC line containing keyword."""
        end = time.time() + timeout
        buf = ""
        while time.time() < end:
            chunk = self._read_some()
            if chunk:
                buf += chunk
                if keyword in buf:
                    append_log(buf.strip())
                    return buf
            time.sleep(0.05)
        if buf.strip():
            append_log(buf.strip())
        return buf

    def send_raw(self, text: str, timeout=4.0):
        """Send raw data (used after DOWNLOAD)."""
        append_log(f">> [SEND RAW] {text}")
        self.ser.write(text.encode())
        time.sleep(0.1)

        buf = ""
        end = time.time() + timeout
        while time.time() < end:
            chunk = self._read_some()
            if chunk:
                buf += chunk
                if "OK" in buf or "ERROR" in buf:
                    break
            else:
                time.sleep(0.05)

        if buf.strip():
            append_log(buf.strip())
        return buf


def ensure_data_bearer(m: Modem, apn: str):
    """
    Bring up data bearer for HTTP stack.
    Different SIM7600 firmwares vary; we try the most common sequence:
    - set CGDCONT
    - attach packet domain (CGATT=1)
    - NETOPEN (if supported)
    """
    # set APN
    m.cmd(f'AT+CGDCONT={CID},"IP","{apn}"', timeout=6.0)

    # attach to packet service (ignore if already)
    try:
        m.cmd("AT+CGATT=1", timeout=8.0)
    except Exception:
        # some operators/firmware return ERROR even when attached; continue
        append_log("[WARN] AT+CGATT=1 failed, continue...")

    # try NETOPEN? / NETOPEN (some firmwares require)
    try:
        m.cmd("AT+NETOPEN?", timeout=4.0, expect_ok=False)
    except Exception:
        pass

    # If NETOPEN supported, try open
    try:
        r = m.cmd("AT+NETOPEN", timeout=12.0, expect_ok=False)
        # Some return OK immediately then +NETOPEN: 0
        if "OK" in r:
            m.wait_urc("+NETOPEN:", timeout=12.0)
    except Exception:
        pass


def do_https_post(port: str, apn: str, json_body: str):
    """
    Main task: HTTPS POST via SIM7600 AT+HTTP* (no certificate loading).
    """
    m = Modem(port)
    try:
        # Basic checks
        m.cmd("AT", timeout=2.0)
        m.cmd("ATE0", timeout=2.0)
        m.cmd("AT+CSQ", timeout=3.0, expect_ok=False)
        m.cmd("AT+CGREG?", timeout=3.0, expect_ok=False)
        m.cmd("AT+COPS?", timeout=6.0, expect_ok=False)

        # Ensure bearer
        ensure_data_bearer(m, apn)

        # Reset HTTP stack
        m.cmd("AT+HTTPTERM", timeout=3.0, expect_ok=False)
        m.cmd("AT+HTTPINIT", timeout=5.0)   # <-- your error happens here if bearer not ready

        # Bind CID (many firmwares require this)
        m.cmd(f'AT+HTTPPARA="CID",{CID}', timeout=3.0, expect_ok=False)

        # Set URL / content type
        m.cmd(f'AT+HTTPPARA="URL","{WEBHOOK_API_URL}"', timeout=5.0)
        m.cmd('AT+HTTPPARA="CONTENT","application/json"', timeout=3.0)

        # Prepare body
        body = json_body.strip()
        body_len = len(body.encode("utf-8"))

        # Tell module we will send data
        r = m.cmd(f"AT+HTTPDATA={body_len},8000", timeout=5.0, expect_ok=False)
        # Wait for DOWNLOAD prompt if not already in response
        if "DOWNLOAD" not in r:
            m.wait_urc("DOWNLOAD", timeout=5.0)

        # Send JSON body + CRLF (some firmwares are tolerant; keep exactly body)
        m.send_raw(body, timeout=5.0)

        # Execute POST
        m.cmd("AT+HTTPACTION=1", timeout=3.0, expect_ok=False)
        m.wait_urc("+HTTPACTION:", timeout=30.0)

        # Read response (webhook.site returns a default HTML text; that's fine)
        m.cmd("AT+HTTPREAD", timeout=10.0, expect_ok=False)

        # Close HTTP
        m.cmd("AT+HTTPTERM", timeout=3.0, expect_ok=False)

    finally:
        m.close()


def task_runner(port: str, apn: str, body: str):
    set_running(True)
    set_result(None, "")
    append_log("\n=== Task started ===")
    append_log(f"[INFO] Port={port}, APN={apn}")
    try:
        do_https_post(port, apn, body)
        append_log("=== Task finished (OK) ===\n")
        set_result(True, "Done. Open the webhook view page to verify the POST content.")
    except Exception as e:
        append_log(f"=== Task finished (FAILED): {e} ===\n")
        set_result(False, str(e))
    finally:
        set_running(False)


HTML = """
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>SIM7600 HTTPS POST (No Certificate)</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 18px; }
    .card { border:1px solid #ddd; border-radius:12px; padding:14px; margin:14px 0; }
    select, input, textarea, button { width:100%; padding:8px; margin-top:6px; box-sizing:border-box; }
    pre { background:#f6f6f6; padding:10px; border-radius:10px; max-height:460px; overflow:auto; }
    a { color:#06c; text-decoration:none; }
    .status { font-weight: bold; }
  </style>
</head>
<body>

<h2>SIM7600 HTTPS POST (no certificate)</h2>

<div class="card">
  <div><b>Webhook view page:</b></div>
  <a href="{{view_url}}" target="_blank">{{view_url}}</a>
</div>

<div class="card">
  <form method="post" action="/start">
    <label>Serial port</label>
    <select name="port" required>
      {% for p in ports %}
        <option value="{{p}}" {% if p==selected_port %}selected{% endif %}>{{p}}</option>
      {% endfor %}
    </select>

    <label>APN</label>
    <input name="apn" value="{{apn}}" placeholder="e.g. CMNET / internet / ...">

    <label>POST JSON</label>
    <textarea name="body" rows="4">{{default_json}}</textarea>

    <button type="submit">Start HTTPS POST</button>
  </form>
</div>

<div class="card">
  <div class="status" id="status">Status: (loading...)</div>
</div>

<div class="card">
  <h3>AT Logs (auto refresh)</h3>
  <pre id="logs">(loading...)</pre>
</div>

<script>
async function refresh() {
  const r = await fetch('/status');
  const j = await r.json();

  document.getElementById('status').innerText =
    "Status: " + (j.running ? "RUNNING" : (j.result.ok === true ? "OK" : (j.result.ok === false ? "FAILED" : "IDLE")))
    + (j.result.msg ? (" | " + j.result.msg) : "");

  const r2 = await fetch('/logs');
  const t = await r2.text();
  const pre = document.getElementById('logs');
  pre.textContent = t;

  // auto scroll to bottom
  pre.scrollTop = pre.scrollHeight;
}

setInterval(refresh, 600);
refresh();
</script>

</body>
</html>
"""


@app.route("/", methods=["GET"])
def index():
    ports = list_serial_ports()
    selected = ports[0] if ports else ""
    return render_template_string(
        HTML,
        ports=ports,
        selected_port=selected,
        apn=DEFAULT_APN,
        default_json=DEFAULT_JSON,
        view_url=WEBHOOK_VIEW_URL
    )


@app.route("/start", methods=["POST"])
def start():
    ports = list_serial_ports()
    port = request.form.get("port", "").strip()
    apn  = request.form.get("apn", DEFAULT_APN).strip()
    body = request.form.get("body", DEFAULT_JSON)

    if port not in ports:
        # allow manual but warn
        append_log(f"[WARN] Port not in list: {port}")

    # prevent parallel runs
    _, running, _ = get_state()
    if running:
        append_log("[WARN] Task already running, ignoring new request.")
        return redirect(url_for("index"))

    # start background thread
    th = threading.Thread(target=task_runner, args=(port, apn, body), daemon=True)
    th.start()
    return redirect(url_for("index"))


@app.route("/logs", methods=["GET"])
def logs():
    text, _, _ = get_state()
    return text, 200, {"Content-Type": "text/plain; charset=utf-8"}


@app.route("/status", methods=["GET"])
def status():
    _, running, result = get_state()
    return jsonify({"running": running, "result": result})


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8886, debug=False)
相关推荐
YuMiao1 天前
gstatic连接问题导致Google Gemini / Studio页面乱码或图标缺失问题
服务器·网络协议
不可能的是2 天前
前端 SSE 流式请求三种实现方案全解析
前端·http
Jony_4 天前
高可用移动网络连接
网络协议
chilix4 天前
Linux 跨网段路由转发配置
网络协议
DianSan_ERP6 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet
呉師傅6 天前
火狐浏览器报错配置文件缺失如何解决#操作技巧#
运维·网络·windows·电脑
gihigo19986 天前
基于TCP协议实现视频采集与通信
网络协议·tcp/ip·音视频
2501_946205526 天前
晶圆机器人双臂怎么选型?适配2-12寸晶圆的末端效应器有哪些?
服务器·网络·机器人
linux kernel6 天前
第七部分:高级IO
服务器·网络
数字护盾(和中)6 天前
BAS+ATT&CK:企业主动防御的黄金组合
服务器·网络·数据库