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)
相关推荐
一条闲鱼_mytube2 小时前
智能体设计模式全景总结:21个模式快速串联指南
网络·设计模式
极安代理2 小时前
什么是HTTP隧道,怎么理解HTTP隧道?
网络·网络协议·http
水境传感 张园园2 小时前
自来水厂水质监测站:用数据守护饮水安全
运维·服务器·网络
花间相见2 小时前
【JAVA开发】—— HTTP常见请求方法
java·开发语言·http
试剂小课堂 Pro3 小时前
mPEG-Silane:mPEG链单端接三乙氧基硅的亲水性硅烷偶联剂
java·c语言·网络·c++·python·tomcat
谢怜823 小时前
计算机网络第三章数据链路层
网络·计算机网络
热心市民R先生3 小时前
对象字典(OD)、服务数据对象(SDO)、过程数据对象(PDO)(二)
服务器·网络
希赛网3 小时前
网工面试:常问技术问题汇总(4)
网络·计算机网络·网络工程师·面试问题·路由交换·网工面试·网工面试提问
(Charon)3 小时前
【网络编程】基于 DPDK 的 UDP/TCP 抓包与最简协议栈实现
网络·tcp/ip·udp