Mengendalikan modem seluler seperti Quectel EC25 atau EG25-G langsung dari backend Linux membutuhkan arsitektur I/O serial yang disiplin. Masalah utama yang sering dihadapi adalah race condition pada port serial, pesan URC (Unsolicited Result Code) yang tercampur dengan respons perintah, dan modem yang mengalami lockup saat siklus reconnect jaringan seluler.
Arsitektur Serial Port dan Masalah State Concurrency
Modem LTE USB umumnya mengekspos beberapa virtual serial port (misalnya /dev/ttyUSB0 hingga /dev/ttyUSB3 melalui driver option atau qcserial di Linux). Alokasi port standar Quectel:
ttyUSB0: Diagnostic output.ttyUSB1: GPS/GNSS NMEA data stream.ttyUSB2: AT commands interface (aplikasi backend).ttyUSB3: Dial-up PPP atau alternate command port.
Kesalahan fatal yang sering terjadi adalah mengakses ttyUSB2 secara paralel dari multi-thread atau multi-proses tanpa mekanisme locking. Port serial adalah media half-duplex stream murni. Backend harus memperlakukan modem sebagai state machine tunggal dengan satu consumer/producer yang diatur oleh mutex atau worker thread terisolasi.
Membangun Kontrak AT Command yang Deterministik
AT command line protocol tidak memiliki header panjang paket. Penanda akhir transmisi bergantung pada karakter kontrol dan token status. Kontrak parsing yang deterministik harus memenuhi kriteria berikut:
- Echo Handling: Selalu kirim
ATE0saat inisialisasi untuk menonaktifkan command echo. Ini menyederhanakan parser karena buffer tidak akan memuat ulang perintah yang dikirim. - Delimiter Berpasangan: Setiap baris input dan output diakhiri oleh
\r\n(CRLF). - Final Response Token: Buffer pembacaan hanya boleh berhenti jika menemukan terminal token standar:
OK,ERROR,CME ERROR: <code>, atauCMS ERROR: <code>. - URC Separation: Notifikasi asinkron seperti
+CSQ:,+CREG:, atauRDYharus difilter dan dialihkan ke event listener terpisah, bukan dianggap sebagai payload respons perintah aktif.
Implementasi Driver Serial Minimal (Go)
Contoh berikut mengimplementasikan eksekusi AT command deterministik dengan timeout konteks dan filter URC sederhana menggunakan pustaka serial standar:
package main
import (
"bufio"
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"go.bug.st/serial"
)
type ModemDriver struct {
port serial.Port
mu sync.Mutex
}
func OpenModem(dev string) (*ModemDriver, error) {
mode := &serial.Mode{
BaudRate: 115200,
DataBits: 8,
Parity: serial.NoParity,
StopBits: serial.OneStopBit,
}
p, err := serial.Open(dev, mode)
if err != nil {
return nil, err
}
return &ModemDriver{port: p}, nil
}
func (m *ModemDriver) Exec(ctx context.Context, cmd string) ([]string, error) {
m.mu.Lock()
ponytail: lock serial bus; multithreading requires central queue worker
defer m.mu.Unlock()
// Bersihkan line endings lama dan kirim dengan CRLF
payload := strings.TrimSpace(cmd) + "\r\n"
if _, err := m.port.Write([]byte(payload)); err != nil {
return nil, fmt.Errorf("write fail: %w", err)
}
reader := bufio.NewReader(m.port)
lines := make([]string, 0)
respChan := make(chan error, 1)
go func() {
for {
line, err := reader.ReadString('\n')
if err != nil {
respChan <- err
return
}
line = strings.TrimRight(line, "\r\n")
if line == "" {
continue
}
// Filter URC (Unsolicited Result Codes)
if strings.HasPrefix(line, "+") && !strings.Contains(cmd, strings.Split(line, ":")[0]) {
// Jalur terpisah: kirim ke event dispatcher jika diperlukan
continue
}
if line == "OK" {
respChan <- nil
return
}
if line == "ERROR" || strings.HasPrefix(line, "+CME ERROR:") || strings.HasPrefix(line, "+CMS ERROR:") {
respChan <- errors.New(line)
return
}
lines = append(lines, line)
}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case err := <-respChan:
if err != nil {
return nil, err
}
return lines, nil
}
}
Manajemen Timeout dan Idempotency Reconnect
Sebagian besar integrasi modem mengalami kegagalan karena menganggap semua AT command memiliki durasi eksekusi yang sama. Tabel berikut menunjukkan batas timeout realistis untuk modem Quectel:
AT: 500 ms (Basic ping)AT+CPIN?: 5 detik (Status validasi kartu SIM)AT+CSQ: 1 detik (Kualitas sinyal)AT+COPS=?: 45 hingga 180 detik (Network scan manual — operasi blocking berat)AT+QIACT=1: 15 hingga 40 detik (Aktivasi PDP context/koneksi IP)
Mencegah Modem Hanging dengan Retry Idempoten
Ketika koneksi data putus, backend tidak boleh melakukan spam perintah aktivasi (AT+QIACT). Operasi reconnect harus dirancang sebagai state machine tertutup dengan status idempoten:
- Cek Status Baseband: Jalankan
AT+CFUN?. Pastikan modem berada pada mode penuh (+CFUN: 1). - Validasi SIM Card: Pastikan
AT+CPIN?mengembalikan+CPIN: READY. - Validasi Registrasi Jaringan: Cek
AT+CREG?atauAT+CGREG?. Nilai respons harus1(terdaftar, home) atau5(terdaftar, roaming). Jika berstatus2(mencari sinyal) atau0(tidak terdaftar), retry eksternal harus ditunda dengan exponential backoff. - Deaktivasi Bersih Sebelum Re-aktivasi: Panggil
AT+QIDEACT=1terlebih dahulu sebelum mencobaAT+QIACT=1baru. Mengaktifkan PDP context yang separuh aktif adalah penyebab utama modem hang. - Hard Recovery: Jika AT command tidak merespons dalam 3 kali percobaan backoff berturut-turut, backend harus memicu reset hardware via pin GPIO (Power Key / Reset Pin) atau mengirim pulsa reset software via
AT+CFUN=1,1(jika serial port masih merespons).
Catatan: Selalu hindari pemutusan power supply secara mendadak (hard power-cut via relay) tanpa menonaktifkan modem secara software (AT+QPOWD). Hal ini dapat menyebabkan korupsi flash memory internal modem.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!