Improved SMS.

This commit is contained in:
Andrey Egorov
2024-08-09 18:17:20 +03:00
parent b3fb13b7b3
commit 75d05f197c
8 changed files with 255 additions and 137 deletions

View File

@ -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")
}

105
api/modem/sms/setup.go Normal file
View File

@ -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
}

View File

@ -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)