Add: power on with context, SIM808 support

This commit is contained in:
Andrey Egorov 2024-08-19 17:07:46 +03:00
parent 3f5412fac0
commit 509006fae1
8 changed files with 129 additions and 46 deletions

View File

@ -4,4 +4,4 @@ export GOARM=6
export CGO_ENABLED=0 export CGO_ENABLED=0
build: build:
@go build -o out/modem main.go @go build -o out/out main.go

View File

@ -37,7 +37,8 @@ type Port interface {
Disconnect() error Disconnect() error
IsConnected() bool 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) Send(cmd string) (Resp, error)
SendWithTimeout(cmd string, timeout time.Duration) (Resp, error) SendWithTimeout(cmd string, timeout time.Duration) (Resp, error)
@ -106,42 +107,53 @@ func (p *atPort) IsConnected() bool {
return p.port != nil return p.port != nil
} }
// Low level write/read function func (p *atPort) RawRead(timeout time.Duration) (string, error) {
func (p *atPort) RawSend(msg string, timeout time.Duration) (string, error) {
p.mutex.Lock() p.mutex.Lock()
defer p.mutex.Unlock() defer p.mutex.Unlock()
// Write deadline := time.Now().Add(timeout)
if _, err := p.port.Write([]byte(msg)); err != nil {
return "", fmt.Errorf("serial port write: %w", err)
}
time.Sleep(timeout)
// Read
outBuf := make([]byte, 0) outBuf := make([]byte, 0)
readLoop: readLoop:
for { for {
readLen, err := p.port.Read(p.inputBuf) readLen, err := p.port.Read(p.inputBuf)
if err != nil { if err != nil {
return "", fmt.Errorf("port read: %w", err) return "", fmt.Errorf("port read: %w", err)
} }
if readLen == 0 { if readLen == 0 && time.Now().After(deadline) {
break readLoop break readLoop
} }
outBuf = append(outBuf, p.inputBuf[:readLen]...) outBuf = append(outBuf, p.inputBuf[:readLen]...)
if readLen < len(p.inputBuf) { // if readLen < len(p.inputBuf) {
break readLoop // break readLoop
} // }
} }
// p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(outBuf), "\x1b[38;2;255;255;255m") // p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(outBuf), "\x1b[38;2;255;255;255m")
return string(outBuf), nil 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) { 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 { if err != nil {
return RespNil, fmt.Errorf("%s request: %w", cmd, err) 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 { if len(rawResp) <= 4 {
return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp)) 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) { 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 { if err != nil {
return RespNil, fmt.Errorf("%s request: %w", cmd, err) 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 { if len(rawResp) <= 4 {
return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp)) return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp))
} }

View File

@ -1,6 +1,7 @@
package gpio package gpio
import ( import (
"context"
"io" "io"
"log" "log"
"time" "time"
@ -8,6 +9,10 @@ import (
gpio "github.com/stianeikeland/go-rpio/v4" gpio "github.com/stianeikeland/go-rpio/v4"
) )
const (
waitCtxTimeout = 100 * time.Microsecond
)
type gpioPin struct { type gpioPin struct {
logger *log.Logger logger *log.Logger
pin gpio.Pin pin gpio.Pin
@ -16,7 +21,7 @@ type gpioPin struct {
type Pin interface { type Pin interface {
Init() error Init() error
PowerOn() PowerOn()
PowerOff() PowerOnCtx(ctx context.Context)
io.Closer io.Closer
} }
@ -31,32 +36,47 @@ func (p gpioPin) Init() error {
return gpio.Open() 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.pin.Output()
p.logger.Println("Power on 0/3 + 100ms") p.logger.Println("Power on 0/3 + 100ms")
p.pin.Low() p.pin.Low()
p.pin.Toggle() p.pin.Toggle()
time.Sleep(100 * time.Millisecond) waitCtx(ctx, 100*time.Millisecond)
p.logger.Println("Power on 1/3 + 3s") p.logger.Println("Power on 1/3 + 3s")
p.pin.High() p.pin.High()
p.pin.Toggle() p.pin.Toggle()
time.Sleep(3 * time.Second) waitCtx(ctx, 3*time.Second)
p.logger.Println("Power on 2/3 + 30s") p.logger.Println("Power on 2/3 + 30s")
p.pin.Low() p.pin.Low()
p.pin.Toggle() p.pin.Toggle()
time.Sleep(30 * time.Second) waitCtx(ctx, 30*time.Second)
p.logger.Println("Power on 3/3") p.logger.Println("Power on 3/3")
} }
func (p gpioPin) PowerOn() { func (p gpioPin) PowerOn() {
p.sendOnOffSignal() p.sendOnOffSignal(context.Background())
} }
func (p gpioPin) PowerOff() { func (p gpioPin) PowerOnCtx(ctx context.Context) {
p.sendOnOffSignal() p.sendOnOffSignal(ctx)
} }
func (p gpioPin) Close() error { func (p gpioPin) Close() error {

View File

@ -39,7 +39,7 @@ func secondCountDownTimer(title string, logger *log.Logger, t time.Duration) {
if counter > int(t.Seconds()) { if counter > int(t.Seconds()) {
break 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) time.Sleep(time.Second)
counter += 1 counter += 1
} }
@ -118,7 +118,7 @@ func (g *gps) collectNmeaReports(flags nmeaFlags) ([]string, error) {
} }
// DEBUG // DEBUG
// g.logger.Println("NMEA raw collect:", resp) g.logger.Println("NMEA raw collect:", resp)
// Right responce struct: // Right responce struct:
// \r\n // \r\n

View File

@ -16,7 +16,7 @@ import (
const ( const (
pingPacketsCount = 3 pingPacketsCount = 3
pingTimeout = 5 pingTimeout = 5
inetConnectedTimeout = 2 * time.Second inetConnectedTimeout = 4 * time.Second
pingAddr = "8.8.8.8" pingAddr = "8.8.8.8"
ifName = "ppp0" // Interface name ifName = "ppp0" // Interface name
inetMetric = 2000 inetMetric = 2000

View File

@ -1,6 +1,7 @@
package modem package modem
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -21,6 +22,9 @@ import (
// yy/MM/dd,hh:mm:ss+zzzz // yy/MM/dd,hh:mm:ss+zzzz
const timeLayout = "06/01/02,15:04:05-0700" 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 { type ModemData struct {
Port string `json:"Port"` Port string `json:"Port"`
gps.Data gps.Data
@ -35,6 +39,7 @@ type modem struct {
baudrate int baudrate int
deviceName string deviceName string
port at.Port port at.Port
model string
// Gpio values // Gpio values
onOffPin gpio.Pin // For turning on and off onOffPin gpio.Pin // For turning on and off
@ -60,6 +65,7 @@ type Modem interface {
GetTime() (time.Time, error) GetTime() (time.Time, error)
PowerOn() error PowerOn() error
PowerOnCtx(ctx context.Context) error // Because it takes ~30 seconds
PowerOff() error PowerOff() error
// Access to SMS, GPS, AT interfaces mostly for debug // Access to SMS, GPS, AT interfaces mostly for debug
@ -131,7 +137,7 @@ func (m *modem) Init() error {
m.logger.Println("=============================== Init submodules") m.logger.Println("=============================== Init submodules")
m.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port) 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()) m.logger.Printf("\x1b[38;2;255;0;0mInternet: %s\x1b[38;2;255;255;255m\n", err.Error())
} else { } else {
m.logger.Println("\x1b[38;2;0;255;0mInternet OK\x1b[38;2;255;255;255m") 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 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 { func (m *modem) PowerOff() error {
_, err := m.At().Send("AT+CPOF") _, err := m.At().Send("AT+CPOF")
return err return err
@ -359,6 +370,22 @@ func (m *modem) setupPort() error {
// m.restart() // m.restart()
// These commands ensure that correct modes are set // These commands ensure that correct modes are set
// m.port.RawSend("\r\n\x1A\r\n") // Sometimes enables echo mode // 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("ATE0") // Sometimes enables echo mode
m.printCmd("AT+CGPSFTM=0") // Sometimes does not turn off nmea m.printCmd("AT+CGPSFTM=0") // Sometimes does not turn off nmea
m.printCmd("AT+CMEE=2") // Turn on errors describtion m.printCmd("AT+CMEE=2") // Turn on errors describtion
@ -368,10 +395,13 @@ func (m *modem) setupPort() error {
} }
func (m *modem) checkCurPortDead() 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 {
if err != nil {
return fmt.Errorf("raw send: %w", err) 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 read: %w", err)
}
return fmt.Errorf("read 0") return fmt.Errorf("read 0")
} }
return nil return nil
@ -429,11 +459,19 @@ func (m *modem) checkPort(portName string) (outErr error) {
if err != nil { if err != nil {
return fmt.Errorf("get model: %w", err) return fmt.Errorf("get model: %w", err)
} }
rightModel := "SIMCOM_SIM7600E-H"
// m.logger.Printf("[% x]\n [% x]", []byte("SIMCOM_SIM7600E-H"), []byte(model)) // Check model
if len(model) >= len(rightModel) && model[:len(rightModel)] != rightModel { foundModel := ""
return fmt.Errorf("invalid modem model: %s", model) 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") m.logger.Println("\x1b[38;2;0;255;0mOK\x1b[38;2;255;255;255m")
return nil return nil
} }
@ -477,11 +515,11 @@ func (m *modem) getAtPorts(ports []string) ([]string, error) {
func getTtyPorts(isSoft bool) ([]string, error) { func getTtyPorts(isSoft bool) ([]string, error) {
if isSoft { if isSoft {
return []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}, nil return ttyPorts, nil
} }
// Get ports // 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 { if err != nil {
return nil, fmt.Errorf("execute ls command: %w", err) return nil, fmt.Errorf("execute ls command: %w", err)
} }

View File

@ -5,7 +5,6 @@ import (
"io" "io"
"log" "log"
"strings" "strings"
"time"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at" "gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/utils" "gitea.unprism.ru/KRBL/sim-modem/api/modem/utils"
@ -69,10 +68,15 @@ func (d *dialer) Send(number, msg string) error {
return err return err
} }
d.logger.Println(sresp) 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) 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) d.logger.Println("Send response:", resp)
if !at.Resp(resp).Check() { if !at.Resp(resp).Check() {
return fmt.Errorf("error response: %s", resp) return fmt.Errorf("error response: %s", resp)

16
main.go
View File

@ -74,9 +74,10 @@ initLoop:
if err := m.Init(); err != nil { if err := m.Init(); err != nil {
logger.Println("Init ended with error:", err.Error()) logger.Println("Init ended with error:", err.Error())
// logger.Println("Turn on...") // logger.Println("Turn on...")
// if err := m.PowerOn(); err != nil { // if err := m.PowerOnCtx(ctx); err != nil {
// logger.Println("Turn on error:", err.Error()) // logger.Println("Turn on error:", err.Error())
// } // }
time.Sleep(time.Second)
continue initLoop continue initLoop
} }
break initLoop break initLoop
@ -94,6 +95,7 @@ initLoop:
m.Close() m.Close()
}() }()
// Internet connect
if err := InetInit(); err != nil { if err := InetInit(); err != nil {
return err return err
} }
@ -120,7 +122,8 @@ initLoop:
logger.Println("Break main loop") logger.Println("Break main loop")
return nil return nil
default: default:
Cmd("AT+CPSI?") // Cmd("AT+CPSI?")
// Cmd("AT+CIPGSMLOC=1")
// Cmd("AT+CSQ") // Cmd("AT+CSQ")
// Cmd("AT+CTZU?") // Cmd("AT+CTZU?")
// Cmd("AT+CPIN?") // Cmd("AT+CPIN?")
@ -128,11 +131,12 @@ initLoop:
// logger.Println(m.Gps().GetStatus()) // logger.Println(m.Gps().GetStatus())
// m.Update() // m.Update()
// st, _ := m.Gps().GetStatus() // st, _ := m.Gps().GetStatus()
// logger.Printf("GPS STATUS: %+v", st) // logger.Printf("FOUND SATELITES: %d\n", st.FoundSatelitesCount)
// logger.Printf("GPS DATA: %+v", m.GetData()) // 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.GetTime())
logger.Println(m.Ic().Ping()) // logger.Println(m.Ic().Ping())
time.Sleep(2 * time.Second) time.Sleep(time.Second)
} }
// Cmd("AT+CSQ") // Cmd("AT+CSQ")
// Cmd("AT+COPS?") // Cmd("AT+COPS?")