From 509006fae1d27b79ca0a3e2d3078845ea8602c2e Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Mon, 19 Aug 2024 17:07:46 +0300 Subject: [PATCH] Add: power on with context, SIM808 support --- Makefile | 2 +- api/modem/at/at.go | 49 +++++++++++++++++++++++------------ api/modem/gpio/gpio.go | 36 ++++++++++++++++++++------ api/modem/gps/nmea.go | 4 +-- api/modem/internet/ic.go | 2 +- api/modem/modem.go | 56 +++++++++++++++++++++++++++++++++------- api/modem/sms/sms.go | 10 ++++--- main.go | 16 +++++++----- 8 files changed, 129 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 85cdd17..ce98226 100644 --- a/Makefile +++ b/Makefile @@ -4,4 +4,4 @@ export GOARM=6 export CGO_ENABLED=0 build: - @go build -o out/modem main.go + @go build -o out/out main.go diff --git a/api/modem/at/at.go b/api/modem/at/at.go index f79801a..f5d1d48 100644 --- a/api/modem/at/at.go +++ b/api/modem/at/at.go @@ -37,7 +37,8 @@ type Port interface { Disconnect() error IsConnected() bool - RawSend(msg string, timeout time.Duration) (string, error) + RawSend(msg string) error + RawRead(timeout time.Duration) (string, error) Send(cmd string) (Resp, error) SendWithTimeout(cmd string, timeout time.Duration) (Resp, error) @@ -106,42 +107,53 @@ func (p *atPort) IsConnected() bool { return p.port != nil } -// Low level write/read function -func (p *atPort) RawSend(msg string, timeout time.Duration) (string, error) { +func (p *atPort) RawRead(timeout time.Duration) (string, error) { p.mutex.Lock() defer p.mutex.Unlock() - // Write - if _, err := p.port.Write([]byte(msg)); err != nil { - return "", fmt.Errorf("serial port write: %w", err) - } - time.Sleep(timeout) - // Read + deadline := time.Now().Add(timeout) 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 { + if readLen == 0 && time.Now().After(deadline) { break readLoop } outBuf = append(outBuf, p.inputBuf[:readLen]...) - if readLen < len(p.inputBuf) { - break readLoop - } + // if readLen < len(p.inputBuf) { + // break readLoop + // } } // p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(outBuf), "\x1b[38;2;255;255;255m") - return string(outBuf), nil } +// Low level write/read function +func (p *atPort) RawSend(msg string) error { + p.mutex.Lock() + defer p.mutex.Unlock() + + // Write + if _, err := p.port.Write([]byte(msg)); err != nil { + return fmt.Errorf("serial port write: %w", err) + } + return nil +} + func (p *atPort) Send(cmd string) (Resp, error) { - rawResp, err := p.RawSend(cmd+"\r\n", time.Microsecond) + err := p.RawSend(cmd + "\r\n") if err != nil { return RespNil, fmt.Errorf("%s request: %w", cmd, err) } + rawResp, err := p.RawRead(ReadTimeout) + if err != nil { + return RespNil, fmt.Errorf("%s request: %w", cmd, err) + } + if len(rawResp) <= 4 { return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp)) } @@ -151,10 +163,15 @@ func (p *atPort) Send(cmd string) (Resp, error) { } func (p *atPort) SendWithTimeout(cmd string, timeout time.Duration) (Resp, error) { - rawResp, err := p.RawSend(cmd+"\r\n", timeout) + err := p.RawSend(cmd + "\r\n") if err != nil { return RespNil, fmt.Errorf("%s request: %w", cmd, err) } + rawResp, err := p.RawRead(timeout) + if err != nil { + return RespNil, fmt.Errorf("%s request: %w", cmd, err) + } + if len(rawResp) <= 4 { return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp)) } diff --git a/api/modem/gpio/gpio.go b/api/modem/gpio/gpio.go index e3f1761..957322b 100644 --- a/api/modem/gpio/gpio.go +++ b/api/modem/gpio/gpio.go @@ -1,6 +1,7 @@ package gpio import ( + "context" "io" "log" "time" @@ -8,6 +9,10 @@ import ( gpio "github.com/stianeikeland/go-rpio/v4" ) +const ( + waitCtxTimeout = 100 * time.Microsecond +) + type gpioPin struct { logger *log.Logger pin gpio.Pin @@ -16,7 +21,7 @@ type gpioPin struct { type Pin interface { Init() error PowerOn() - PowerOff() + PowerOnCtx(ctx context.Context) io.Closer } @@ -31,32 +36,47 @@ func (p gpioPin) Init() error { return gpio.Open() } -func (p gpioPin) sendOnOffSignal() { +func waitCtx(ctx context.Context, timeout time.Duration) { + deadline := time.Now().Add(timeout) + for { + select { + case <-ctx.Done(): + return + default: + if time.Now().After(deadline) { + return + } + } + time.Sleep(waitCtxTimeout) + } +} + +func (p gpioPin) sendOnOffSignal(ctx context.Context) { p.pin.Output() p.logger.Println("Power on 0/3 + 100ms") p.pin.Low() p.pin.Toggle() - time.Sleep(100 * time.Millisecond) + waitCtx(ctx, 100*time.Millisecond) p.logger.Println("Power on 1/3 + 3s") p.pin.High() p.pin.Toggle() - time.Sleep(3 * time.Second) + waitCtx(ctx, 3*time.Second) p.logger.Println("Power on 2/3 + 30s") p.pin.Low() p.pin.Toggle() - time.Sleep(30 * time.Second) + waitCtx(ctx, 30*time.Second) p.logger.Println("Power on 3/3") } func (p gpioPin) PowerOn() { - p.sendOnOffSignal() + p.sendOnOffSignal(context.Background()) } -func (p gpioPin) PowerOff() { - p.sendOnOffSignal() +func (p gpioPin) PowerOnCtx(ctx context.Context) { + p.sendOnOffSignal(ctx) } func (p gpioPin) Close() error { diff --git a/api/modem/gps/nmea.go b/api/modem/gps/nmea.go index 5fe5ce8..ef04a8c 100644 --- a/api/modem/gps/nmea.go +++ b/api/modem/gps/nmea.go @@ -39,7 +39,7 @@ func secondCountDownTimer(title string, logger *log.Logger, t time.Duration) { if counter > int(t.Seconds()) { break } - logger.Printf("%s: %d/%f\n", title, counter, t.Seconds()) + // logger.Printf("%s: %d/%f\n", title, counter, t.Seconds()) time.Sleep(time.Second) counter += 1 } @@ -118,7 +118,7 @@ func (g *gps) collectNmeaReports(flags nmeaFlags) ([]string, error) { } // DEBUG - // g.logger.Println("NMEA raw collect:", resp) + g.logger.Println("NMEA raw collect:", resp) // Right responce struct: // \r\n diff --git a/api/modem/internet/ic.go b/api/modem/internet/ic.go index 1a9b198..d0a1540 100644 --- a/api/modem/internet/ic.go +++ b/api/modem/internet/ic.go @@ -16,7 +16,7 @@ import ( const ( pingPacketsCount = 3 pingTimeout = 5 - inetConnectedTimeout = 2 * time.Second + inetConnectedTimeout = 4 * time.Second pingAddr = "8.8.8.8" ifName = "ppp0" // Interface name inetMetric = 2000 diff --git a/api/modem/modem.go b/api/modem/modem.go index aee2b27..d9b99f6 100644 --- a/api/modem/modem.go +++ b/api/modem/modem.go @@ -1,6 +1,7 @@ package modem import ( + "context" "fmt" "io" "log" @@ -21,6 +22,9 @@ import ( // yy/MM/dd,hh:mm:ss+zzzz const timeLayout = "06/01/02,15:04:05-0700" +var ttyPorts = []string{"ttyUSB1", "ttyUSB2", "ttyUSB3", "ttyS0", "ttyAMA2"} +var availableModels = []string{"SIMCOM_SIM7600E-H", "SIMCOM_SIM808"} + type ModemData struct { Port string `json:"Port"` gps.Data @@ -35,6 +39,7 @@ type modem struct { baudrate int deviceName string port at.Port + model string // Gpio values onOffPin gpio.Pin // For turning on and off @@ -60,6 +65,7 @@ type Modem interface { GetTime() (time.Time, error) PowerOn() error + PowerOnCtx(ctx context.Context) error // Because it takes ~30 seconds PowerOff() error // Access to SMS, GPS, AT interfaces mostly for debug @@ -131,7 +137,7 @@ func (m *modem) Init() error { m.logger.Println("=============================== Init submodules") m.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port) - if err := m.ic.Init(ports[1]); err != nil { + if err := m.ic.Init(ports[len(ports)-1]); 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") @@ -223,6 +229,11 @@ func (m *modem) PowerOn() error { return nil } +func (m *modem) PowerOnCtx(ctx context.Context) error { + m.onOffPin.PowerOnCtx(ctx) // DEBUG do not want to wait 30 seconds + return nil +} + func (m *modem) PowerOff() error { _, err := m.At().Send("AT+CPOF") return err @@ -359,6 +370,22 @@ func (m *modem) setupPort() error { // m.restart() // These commands ensure that correct modes are set // m.port.RawSend("\r\n\x1A\r\n") // Sometimes enables echo mode + //if m.At().GetName() == "/dev/ttyUSB0" { + // buf := make([]byte, 256) + // for i := 0; i < 10; i++ { + // len, err := m.port.SerialPort().Read(buf) + // if err != nil { + // m.logger.Println("ERROR:", err.Error()) + // } + // if len != 0 { + // m.logger.Println(string(buf[:len])) + // } + // time.Sleep(time.Second) + // m.logger.Println(".") + // } + //} + m.printCmd("AT") // Sometimes enables echo mode + m.printCmd("AT") // 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 @@ -368,9 +395,12 @@ func (m *modem) setupPort() error { } func (m *modem) checkCurPortDead() error { - if resp, err := m.port.RawSend("AT\r\n", 20*time.Millisecond); err != nil || len(resp) == 0 { + if err := m.port.RawSend("AT\r\n"); err != nil { + return fmt.Errorf("raw send: %w", err) + } + if resp, err := m.port.RawRead(time.Second); err != nil || len(resp) == 0 { if err != nil { - return fmt.Errorf("raw send: %w", err) + return fmt.Errorf("raw read: %w", err) } return fmt.Errorf("read 0") } @@ -429,11 +459,19 @@ func (m *modem) checkPort(portName string) (outErr error) { if err != nil { return fmt.Errorf("get model: %w", err) } - rightModel := "SIMCOM_SIM7600E-H" - // m.logger.Printf("[% x]\n [% x]", []byte("SIMCOM_SIM7600E-H"), []byte(model)) - if len(model) >= len(rightModel) && model[:len(rightModel)] != rightModel { - return fmt.Errorf("invalid modem model: %s", model) + + // Check model + foundModel := "" + for _, rightModel := range availableModels { + if len(model) >= len(rightModel) && model[:len(rightModel)] == rightModel { + foundModel = rightModel + break + } } + if foundModel == "" { + return fmt.Errorf("invalid model: %s", model) + } + m.model = foundModel m.logger.Println("\x1b[38;2;0;255;0mOK\x1b[38;2;255;255;255m") return nil } @@ -477,11 +515,11 @@ func (m *modem) getAtPorts(ports []string) ([]string, error) { func getTtyPorts(isSoft bool) ([]string, error) { if isSoft { - return []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}, nil + return ttyPorts, nil } // Get ports /**/ - out, err := exec.Command("ls", "--", "/dev/tty[!0-9]*").Output() + out, err := exec.Command("ls", "/dev/tty[!0-9]*").Output() if err != nil { return nil, fmt.Errorf("execute ls command: %w", err) } diff --git a/api/modem/sms/sms.go b/api/modem/sms/sms.go index 3443d9e..c67995e 100644 --- a/api/modem/sms/sms.go +++ b/api/modem/sms/sms.go @@ -5,7 +5,6 @@ import ( "io" "log" "strings" - "time" "gitea.unprism.ru/KRBL/sim-modem/api/modem/at" "gitea.unprism.ru/KRBL/sim-modem/api/modem/utils" @@ -69,10 +68,15 @@ func (d *dialer) Send(number, msg string) error { return err } d.logger.Println(sresp) - resp, err := d.port.RawSend(fmt.Sprintf("%s\x1A", msg), time.Millisecond) // Add additional \r\n because there is not supposed to be - if err != nil { + + // Message body + if err := d.port.RawSend(fmt.Sprintf("%s\x1A", msg)); err != nil { return fmt.Errorf("message request: %w", err) } + resp, err := d.port.RawRead(at.ReadTimeout) + if err != nil { + return fmt.Errorf("message request read: %w", err) + } d.logger.Println("Send response:", resp) if !at.Resp(resp).Check() { return fmt.Errorf("error response: %s", resp) diff --git a/main.go b/main.go index b324755..2b0fc7e 100644 --- a/main.go +++ b/main.go @@ -74,9 +74,10 @@ initLoop: if err := m.Init(); err != nil { logger.Println("Init ended with error:", err.Error()) // logger.Println("Turn on...") - // if err := m.PowerOn(); err != nil { + // if err := m.PowerOnCtx(ctx); err != nil { // logger.Println("Turn on error:", err.Error()) // } + time.Sleep(time.Second) continue initLoop } break initLoop @@ -94,6 +95,7 @@ initLoop: m.Close() }() + // Internet connect if err := InetInit(); err != nil { return err } @@ -120,7 +122,8 @@ initLoop: logger.Println("Break main loop") return nil default: - Cmd("AT+CPSI?") + // Cmd("AT+CPSI?") + // Cmd("AT+CIPGSMLOC=1") // Cmd("AT+CSQ") // Cmd("AT+CTZU?") // Cmd("AT+CPIN?") @@ -128,11 +131,12 @@ initLoop: // logger.Println(m.Gps().GetStatus()) // m.Update() // st, _ := m.Gps().GetStatus() - // logger.Printf("GPS STATUS: %+v", st) - // logger.Printf("GPS DATA: %+v", m.GetData()) + // logger.Printf("FOUND SATELITES: %d\n", st.FoundSatelitesCount) + // data := m.GetData() + // logger.Printf("GPS DATA: %f%s %f%s\n", data.Latitude, data.LatitudeIndicator, data.Longitude, data.LongitudeIndicator) // logger.Println(m.GetTime()) - logger.Println(m.Ic().Ping()) - time.Sleep(2 * time.Second) + // logger.Println(m.Ic().Ping()) + time.Sleep(time.Second) } // Cmd("AT+CSQ") // Cmd("AT+COPS?")