From 75d05f197cd17318e32d2bdb4058e375d0110175 Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Fri, 9 Aug 2024 18:17:20 +0300 Subject: [PATCH] Improved SMS. --- api/modem/at/at.go | 28 +++++++---- api/modem/gps/gps.go | 4 +- api/modem/gps/nmea.go | 2 +- api/modem/modem.go | 41 ++++++++++------ api/modem/sms/errors.go | 69 -------------------------- api/modem/sms/setup.go | 105 ++++++++++++++++++++++++++++++++++++++++ api/modem/sms/sms.go | 52 +++++++++----------- main.go | 91 +++++++++++++++++++++++++++++----- 8 files changed, 255 insertions(+), 137 deletions(-) delete mode 100644 api/modem/sms/errors.go create mode 100644 api/modem/sms/setup.go diff --git a/api/modem/at/at.go b/api/modem/at/at.go index 3ea0297..3cfa85c 100644 --- a/api/modem/at/at.go +++ b/api/modem/at/at.go @@ -116,23 +116,33 @@ func (p *atPort) RawSend(msg string) (string, error) { } // time.Sleep(time.Millisecond) // Read - readLen, err := p.port.Read(p.inputBuf) - // p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(p.inputBuf[:readLen]), "\x1b[38;2;255;255;255m") - if err != nil { - return "", fmt.Errorf("port read: %w", err) + outBuf := make([]byte, 0) +readLoop: + for { + readLen, err := p.port.Read(p.inputBuf) + if err != nil { + return "", fmt.Errorf("port read: %w", err) + } + if readLen == 0 { + break readLoop + } + outBuf = append(outBuf, p.inputBuf[:readLen]...) + if readLen < len(p.inputBuf) { + break readLoop + } } + // p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(p.inputBuf[:readLen]), "\x1b[38;2;255;255;255m") - return string(p.inputBuf[:readLen]), nil + return string(outBuf), nil } func (p *atPort) Send(cmd string) (Resp, error) { - cmd += "\r\n" - rawResp, err := p.RawSend(cmd) + rawResp, err := p.RawSend(cmd + "\r\n") if err != nil { - return RespNil, fmt.Errorf("make request: %w", err) + return RespNil, fmt.Errorf("%s request: %w", cmd, err) } if len(rawResp) <= 4 { - return RespNil, fmt.Errorf("read too small msg: %d byte - %s", len(rawResp), string(rawResp)) + return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp)) } resp := rawResp[2 : len(rawResp)-2] // Cut \r\n diff --git a/api/modem/gps/gps.go b/api/modem/gps/gps.go index 9476e3a..aa55a7f 100644 --- a/api/modem/gps/gps.go +++ b/api/modem/gps/gps.go @@ -85,7 +85,7 @@ func (g *gps) switchGpsMode(on bool) error { // Check gps mode status resp, err := g.port.Send("AT+CGPS?") if err != nil { - return fmt.Errorf("make at ask: %w", err) + return err } if !resp.Check() || !resp.CheckFront("+CGPS:") { return fmt.Errorf("get GPS mode: error response: %s", resp) @@ -98,7 +98,7 @@ func (g *gps) switchGpsMode(on bool) error { // Modem is not in GPS mode resp, err = g.port.Send("AT+CGPS=" + onStr) if err != nil { - return fmt.Errorf("set GPS mode: %w", err) + return err } if !resp.Check() { return fmt.Errorf("set GPS mode: error response: %s", resp) diff --git a/api/modem/gps/nmea.go b/api/modem/gps/nmea.go index 06cfe54..5fe5ce8 100644 --- a/api/modem/gps/nmea.go +++ b/api/modem/gps/nmea.go @@ -56,7 +56,7 @@ func (g *gps) rawCollect(flags nmeaFlags) (string, error) { // Set output rate to 10Hz if resp, err := g.port.Send("AT+CGPSNMEARATE=1"); err != nil { - return "", fmt.Errorf("AT+CGPSNMEARATE= request: %w", err) + return "", err } else { if !resp.Check() { return "", fmt.Errorf("set output rate: error response: %s", resp) diff --git a/api/modem/modem.go b/api/modem/modem.go index df6726e..7e39586 100644 --- a/api/modem/modem.go +++ b/api/modem/modem.go @@ -112,19 +112,19 @@ func (m *modem) Init() error { // submodulesLogger := io.Discard // FOR less logs m.logger.Println("=============================== Init submodules") - //m.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port) - //if err := m.ic.Init(); err != nil { - // m.logger.Printf("\x1b[38;2;255;0;0mInternet: %s\x1b[38;2;255;255;255m\n", err.Error()) - //} else { - // m.logger.Println("\x1b[38;2;0;255;0mInternet OK\x1b[38;2;255;255;255m") - //} - // - //m.sms = sms.New(log.New(submodulesLogger, "modem-sms : ", log.LstdFlags), m.port) - //if err := m.sms.Init(); err != nil { - // m.logger.Printf("\x1b[38;2;255;0;0mSMS: %s\x1b[38;2;255;255;255m\n", err.Error()) - //} else { - // m.logger.Println("\x1b[38;2;0;255;0mSMS OK\x1b[38;2;255;255;255m") - //} + // m.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port) + // if err := m.ic.Init(); err != nil { + // m.logger.Printf("\x1b[38;2;255;0;0mInternet: %s\x1b[38;2;255;255;255m\n", err.Error()) + // } else { + // m.logger.Println("\x1b[38;2;0;255;0mInternet OK\x1b[38;2;255;255;255m") + // } + + m.sms = sms.New(log.New(submodulesLogger, "modem-sms : ", log.LstdFlags), m.port) + if err := m.sms.Init(); err != nil { + m.logger.Printf("\x1b[38;2;255;0;0mSMS: %s\x1b[38;2;255;255;255m\n", err.Error()) + } else { + m.logger.Println("\x1b[38;2;0;255;0mSMS OK\x1b[38;2;255;255;255m") + } m.gps = gps.New(log.New(submodulesLogger, "modem-gps : ", log.LstdFlags), m.port) if err := m.gps.Init(); err != nil { @@ -180,6 +180,13 @@ func (m *modem) PowerOff() error { return err } +func (m *modem) restart() error { + m.PowerOff() + time.Sleep(10 * time.Second) + m.PowerOn() + return nil +} + func (m *modem) Sms() sms.Sms { return m.sms } @@ -272,7 +279,7 @@ func (m *modem) printCmd(cmd string) { m.logger.Println("FAILED TO SEND REQ", cmd, ":", err.Error()) } else { _ = resp - // m.logger.Println("CMD", cmd, ":", resp) + m.logger.Println("CMD", cmd, ":", resp) } } @@ -287,7 +294,9 @@ func (m *modem) setupPort() error { return fmt.Errorf("reset output buffer: %w", err) } + // m.restart() // These commands ensure that correct modes are set + // m.port.RawSend("\r\n\x1A\r\n") // Sometimes enables echo mode m.printCmd("ATE0") // Sometimes enables echo mode m.printCmd("AT+CGPSFTM=0") // Sometimes does not turn off nmea m.printCmd("AT+CMEE=2") // Turn on errors describtion @@ -315,8 +324,10 @@ func (m *modem) checkPort(portName string) (outErr error) { } defer m.port.Disconnect() // Do not bother about errors... + // m.restart() + // To filter dead ports - if _, err := m.port.Send("AT"); err != nil { + if str, err := m.port.RawSend("AT"); err != nil || len(str) == 0 { return fmt.Errorf("ping error: %w", err) } diff --git a/api/modem/sms/errors.go b/api/modem/sms/errors.go deleted file mode 100644 index 6fe2e19..0000000 --- a/api/modem/sms/errors.go +++ /dev/null @@ -1,69 +0,0 @@ -package sms - -import ( - "fmt" - "strconv" -) - -func DecodeError(code int) string { - switch code { - case 300: - return "ME failure" - case 301: - return "SMS service of ME reserved" - case 302: - return "Operation not allowed" - case 303: - return "Operation not supported" - case 304: - return "Invalid PDU mode parameter" - case 305: - return "Invalid text mode parameter" - case 310: - return "SIM not inserted" - case 311: - return "SIM PIN required" - case 312: - return "PH-SIM PIN required" - case 313: - return "SIM failure" - case 314: - return "SIM busy" - case 315: - return "SIM wrong" - case 316: - return "SIM PUK required" - case 317: - return "SIM PIN2 required" - case 318: - return "SIM PUK2 required" - case 320: - return "Memory failure" - case 321: - return "Invalid memory index" - case 322: - return "Memory full" - case 330: - return "SMSC address unknown" - case 331: - return "No network service" - case 332: - return "Network timeout" - case 340: - return "NO +CNMA ACK EXPECTED" - case 341: - return "Buffer overflow" - case 342: - return "SMS size more than expected" - case 500: - return "Unknown error" - } - return "UNDEFINED ERROR CODE" -} - -func GetError(msg []byte) (int, error) { - if len(msg) >= len("+CMS ERROR: ")+3 && string(msg[:len("+CMS ERROR: ")]) == "+CMS ERROR: " { - return strconv.Atoi(string(msg[len("+CMS ERROR: ") : len("+CMS ERROR: ")+3])) - } - return 0, fmt.Errorf("failed to parse error") -} diff --git a/api/modem/sms/setup.go b/api/modem/sms/setup.go new file mode 100644 index 0000000..1616668 --- /dev/null +++ b/api/modem/sms/setup.go @@ -0,0 +1,105 @@ +package sms + +import ( + "fmt" + "strconv" + "strings" +) + +func (d *dialer) checkPIN() error { + // Get code + resp, err := d.port.Send("AT+CPIN?") + if err != nil { + return fmt.Errorf("AT+CPIN? request: %w", err) + } + if !resp.Check() || !resp.CheckFront("+CPIN:") { + return fmt.Errorf("AT+CPIN? error response: %s", resp) + } + code := strings.ReplaceAll(strings.ReplaceAll(strings.Split(resp.RmFront("+CPIN:").String(), "\n")[0], "\r", ""), " ", "") + if code != "READY" { + return fmt.Errorf("not READY code: %s", code) + } + d.logger.Println("PIN is ready") + return nil +} + +func (d *dialer) setupMsgSt() error { + // Check for free space for messages + // !!! I use one! memory for all three bindings + // 1: read and delete + // 2: sending + // 3: write received + + // First try SM + if _, err := d.port.Send(`AT+CPMS="SM","SM","SM"`); err != nil { + return fmt.Errorf(`AT+CPMS="SM","SM","SM" request: %w`, err) + } + st, err := d.getCurMsgStSize() + if err != nil { + return fmt.Errorf("SM: %w", err) + } + if st[0].Used < st[0].Total { + d.logger.Printf("Use SM message storage: %d/%d\n", st[0].Used, st[0].Total) + return nil // There is space + } + d.logger.Printf("SM message storage is full: %d/%d\n", st[0].Used, st[0].Total) + + // Second try ME + if _, err := d.port.Send(`AT+CPMS="ME","ME","ME"`); err != nil { + return fmt.Errorf(`AT+CPMS="ME","ME","ME" request: %w`, err) + } + st, err = d.getCurMsgStSize() + if err != nil { + return fmt.Errorf("ME: %w", err) + } + if st[0].Used < st[0].Total { + d.logger.Printf("Use ME message storage: %d/%d\n", st[0].Used, st[0].Total) + return nil // There is space + } + d.logger.Printf("ME message storage is full: %d/%d\n", st[0].Used, st[0].Total) + // Otherwise error + return fmt.Errorf("all storages are full") +} + +// Message storage +type msgSt struct { + Name string + Used int + Total int +} + +// Get size of used and total mem of current memory storage +func (d *dialer) getCurMsgStSize() ([]msgSt, error) { + // Request + resp, err := d.port.Send("AT+CPMS?") + if err != nil { + return nil, fmt.Errorf("AT+CPMS? request: %w", err) + } + // Check start and end + if !resp.Check() && !resp.CheckFront("+CPMS:") { + return nil, fmt.Errorf("AT+CPMS") + } + // Remove front and cut to values + resp = resp.RmFront("+CPMS:") + values := strings.Split(strings.ReplaceAll(strings.Split(resp.String(), "\n")[0], "\r", ""), ",") + if len(values) != 9 { + return nil, fmt.Errorf("CPMS response: invalid values count: [%s]", values) + } + + // Parse values + outMsgs := [3]msgSt{} + for i := 0; i < 3; i++ { + name := values[i] + used, err := strconv.Atoi(values[i*3+1]) + if err != nil { + return nil, fmt.Errorf("parse value #%d: %w", i+1, err) + } + total, err := strconv.Atoi(values[i*3+2]) + if err != nil { + return nil, fmt.Errorf("parse value #%d, %w", i+2, err) + } + + outMsgs[i] = msgSt{name, used, total} + } + return outMsgs[:], nil +} diff --git a/api/modem/sms/sms.go b/api/modem/sms/sms.go index d400c61..095dd68 100644 --- a/api/modem/sms/sms.go +++ b/api/modem/sms/sms.go @@ -30,55 +30,51 @@ func New(logger *log.Logger, port at.Port) Sms { } func (d *dialer) Init() error { - // Ensure serial port + // Ensure serial port is connected if !d.port.IsConnected() { return fmt.Errorf("serial port is not connected") } - - // Check SIM an PIN - if resp, err := d.port.Send("AT+CPIN?"); err != nil || !resp.Check() { - if err != nil { - return fmt.Errorf("check pin: %w", err) - } - return fmt.Errorf("check pin: error response: %s", resp) + // Check ping + if err := d.checkPIN(); err != nil { + return fmt.Errorf("check PIN: %w", err) } - - // Ensure text format - d.logger.Println(d.port.Send("AT+CMGF")) - if resp, err := d.port.Send("AT+CMGF=1"); err != nil || !resp.Check() { + // Setup prefered message storage + if err := d.setupMsgSt(); err != nil { + return fmt.Errorf("setup msg storage: %w", err) + } + // Check number + if resp, err := d.port.Send("AT+CNUM"); err != nil || !resp.Check() { if err != nil { - return fmt.Errorf("set to text format request: %w", err) + return fmt.Errorf("AT+CNUM request ") } - return fmt.Errorf("set SIM format: error response: %s", resp) } return nil } func (d *dialer) Send(number, msg string) error { - d.port.Send(fmt.Sprintf(`AT+CMGS="%s"`, number)) // Because it will throw error - resp, err := d.port.RawSend(fmt.Sprintf("%s\n\r", msg)) // Add additional \r\n because there is not supposed to be + sresp, err := d.port.Send(fmt.Sprintf(`AT+CMGSEX="%s"`, number)) // Because it will throw error + if err != nil { + return err + } + d.logger.Println(sresp) + resp, err := d.port.RawSend(fmt.Sprintf("%s\x1A", msg)) // Add additional \r\n because there is not supposed to be if err != nil { return fmt.Errorf("message request: %w", err) } - d.logger.Println("SEND RESPONSE:", resp) - resp, err = d.port.RawSend("\x1A") - if err != nil { - return fmt.Errorf("message request: %w", err) + d.logger.Println("Send response:", resp) + if !at.Resp(resp).Check() { + return fmt.Errorf("error response: %s", resp) } - d.logger.Println("SEND RESPONSE:", resp) - errCode, err := GetError([]byte(resp)) - if err != nil { - return fmt.Errorf("send sms failed and failed to get error: %w", err) - } - return fmt.Errorf("failed to send with SMS error: %d - %s", errCode, DecodeError(errCode)) + return nil } // Reads all new messages func (d *dialer) ReadNew() ([]string, error) { - resp, err := d.port.Send("AT+CMGL=\"UNREAD\"") + resp, err := d.port.Send("AT+CMGL=\"ALL\"") if err != nil { - return nil, fmt.Errorf("AT+CMGL request: %w", err) + return nil, err } + d.logger.Println("raw sms:", resp) msgs := strings.Split(strings.Replace(string(resp), "\r", "", -1), "\n") outMsgs := make([]string, 0) diff --git a/main.go b/main.go index 641f5a1..b4742d1 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "os" "gitea.unprism.ru/KRBL/sim-modem/api/modem" + "gitea.unprism.ru/KRBL/sim-modem/api/modem/at" ) func main() { @@ -35,16 +36,14 @@ func mainE() error { logger.Println("AAAAAAAAAAAAAAA Modem is not connected") return nil } - // m.PowerOff() - // time.Sleep(10 * time.Second) - // m.PowerOn() - logger.Println("||||||||||||||||| GET INFO |||||||||||||||||") - logger.Println(m.Update()) - logger.Printf("DATA: %+v\n", m.GetData()) + // logger.Println("||||||||||||||||| GET INFO |||||||||||||||||") + // logger.Println(m.Update()) + // logger.Printf("DATA: %+v\n", m.GetData()) - // logger.Println("||||||||||||||||| SEND SMS |||||||||||||||||") - // logger.Println(m.At().Send("AT+CNUM")) + logger.Println("||||||||||||||||| SMS |||||||||||||||||") + // resp, err := m.At().Send("AT+CNUM") + // logger.Println("CNUM:", resp, err) // // if err := m.Sms().Send("+79218937173", "CGSG forever"); err != nil { // // return err // // } @@ -53,12 +52,78 @@ func mainE() error { // } else { // logger.Println("NEW:", ms) // } - logger.Println("||||||||||||||||| Checking gps status |||||||||||||||||") - st, err := m.Gps().GetStatus() - if err != nil { - return err + + Cmd := func(cmd string) { + resp, err := m.At().Send(cmd) + logger.Println(cmd, "===>", resp, err) } - logger.Printf("GPS Status:%+v\n", st) + _ = Cmd + + var resp at.Resp + var err error + buf := make([]byte, 256) + _ = resp + _ = err + _ = buf + + // Select ME PMS + // resp, err = m.At().Send("AT+CPMS=?") + // logger.Println("Possible mem storages:", resp, err) + // resp, err = m.At().Send("AT+CPMS?") + // logger.Println("Prefered mem storage:", resp, err) + // Cmd("AT") + // logger.Println("SEND SMS") + // logger.Println(m.Sms().Send("+79218937173", "CGSG forever!!!")) + // m.At().RawSend("\r\n\x1A\r\n") + + for { + readLen, err := m.At().SerialPort().Read(buf) + if err != nil { + return err + } + if readLen > 0 { + logger.Println(string(buf[:readLen])) + } + } + + // resp, err = m.At().Send("AT+CPMS?") + // logger.Println("Prefered mem storage:", resp, err) + + // resp, err = m.At().Send("AT+CREG?") + // logger.Println("Network registration:", resp, err) + // resp, err = m.At().Send("AT+CPMS?") + // logger.Println("Prefered mem storage:", resp, err) + // resp, err = m.At().Send("AT+CPMS=?") + // logger.Println("Possible mem storage:", resp, err) + // resp, err = m.At().Send("AT+CNMI?") + // logger.Println("New message indications:", resp, err) + // resp, err = m.At().Send("AT+CMGL=\"REC UNREAD\"") + // logger.Println("New messages:", resp, err) + // resp, err = m.At().Send("AT+CNMI=2,1") + // logger.Println("AT+CNMI=2,1:", resp, err) + // resp, err = m.At().Send("AT+CNMI?") + // logger.Println("New message indications:", resp, err) + // logger.Println("Reading port...") + // for { + // readLen, err := m.At().SerialPort().Read(buf) + // if err != nil { + // return err + // } + // if readLen > 0 { + // logger.Println(string(buf[:readLen])) + // } + // } + // for { + // resp, err = m.At().Send("AT+CSQ") + // logger.Println("AT+CSQ:", resp, err) + // time.Sleep(500 * time.Millisecond) + // } + // logger.Println("||||||||||||||||| Checking gps status |||||||||||||||||") + // st, err := m.Gps().GetStatus() + // if err != nil { + // return err + // } + // logger.Printf("GPS Status:%+v\n", st) // logger.Println("Turn off", m.PowerOff())