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)