Compare commits

..

3 Commits

Author SHA1 Message Date
Andrey Egorov
4f89f15146 Fix: latitude, longitude format 2024-09-30 20:43:37 +03:00
Andrey Egorov
509006fae1 Add: power on with context, SIM808 support 2024-08-19 17:07:46 +03:00
Andrey Egorov
3f5412fac0 Add: internet default routes 2024-08-15 16:47:20 +03:00
10 changed files with 363 additions and 93 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

@ -11,10 +11,10 @@ import (
) )
type Data struct { type Data struct {
Latitude float64 `json:"Latitude"` Latitude float64 `json:"Latitude"` // ddmm.mmmmmm
Longitude float64 `json:"Longitude"` Longitude float64 `json:"Longitude"` // dddmm.mmmmmm
LatitudeIndicator string `json:"Latitude_indicator"` // North/South LatitudeIndicator string `json:"Latitude_indicator"` // N/S - North/South
LongitudeIndicator string `json:"Longitude_indicator"` // West/East LongitudeIndicator string `json:"Longitude_indicator"` // W/E - West/East
Speed float64 `json:"Speed"` Speed float64 `json:"Speed"`
Course float64 `json:"-"` Course float64 `json:"-"`
Altitude float64 `json:"-"` Altitude float64 `json:"-"`
@ -59,8 +59,6 @@ func (gps *Data) decode(str string) error {
if err != nil { if err != nil {
logger.Println("ERROR parse longitude:", err.Error()) logger.Println("ERROR parse longitude:", err.Error())
} }
newGpsInfo.Latitude /= 100
newGpsInfo.Longitude /= 100
newGpsInfo.LatitudeIndicator = strs[1] newGpsInfo.LatitudeIndicator = strs[1]
newGpsInfo.LongitudeIndicator = strs[3] newGpsInfo.LongitudeIndicator = strs[3]
newGpsInfo.Date = strs[4] newGpsInfo.Date = strs[4]

View File

@ -34,12 +34,14 @@ const (
) )
func secondCountDownTimer(title string, logger *log.Logger, t time.Duration) { func secondCountDownTimer(title string, logger *log.Logger, t time.Duration) {
_ = title
_ = logger
counter := 0 counter := 0
for { for {
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 +120,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

@ -1,15 +1,27 @@
package internet package internet
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"time"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at" "gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
) )
const (
pingPacketsCount = 3
pingTimeout = 5
inetConnectedTimeout = 4 * time.Second
pingAddr = "8.8.8.8"
ifName = "ppp0" // Interface name
inetMetric = 2000
)
type conn struct { type conn struct {
logger *log.Logger logger *log.Logger
port at.Port port at.Port
@ -18,6 +30,10 @@ type conn struct {
isConnectExecuted bool isConnectExecuted bool
isInited bool isInited bool
isRouteSet bool
connectTime time.Time
gw string // Gateway
} }
type Conn interface { type Conn interface {
@ -26,6 +42,9 @@ type Conn interface {
Connect() error Connect() error
Disconnect() error Disconnect() error
SetDefaultRouteTable() error
UnsetDefaultRouteTable() error
IsConnected() bool // Check interface existance IsConnected() bool // Check interface existance
Ping() error Ping() error
@ -36,8 +55,10 @@ func New(logger *log.Logger, port at.Port) Conn {
return &conn{ return &conn{
logger: logger, logger: logger,
port: port, port: port,
isConnectExecuted: false, isConnectExecuted: false,
isInited: false, isInited: false,
isRouteSet: false,
} }
} }
@ -60,12 +81,6 @@ func (c *conn) Connect() error {
return fmt.Errorf("already connected") return fmt.Errorf("already connected")
} }
// Check signal // Check signal
// if ok, err := utils.CheckService(c.port, c.logger); err != nil || !ok {
// if err != nil {
// return fmt.Errorf("check service: %w", err)
// }
// return fmt.Errorf("no service")
// }
resp, err := exec.Command("pon", pppConfigName).Output() resp, err := exec.Command("pon", pppConfigName).Output()
if err != nil { if err != nil {
@ -74,11 +89,15 @@ func (c *conn) Connect() error {
if len(resp) > 0 { if len(resp) > 0 {
c.logger.Println("pon response:", string(resp)) c.logger.Println("pon response:", string(resp))
} }
c.isConnectExecuted = true c.isConnectExecuted = true
c.connectTime = time.Now()
// Set default route c.gw, err = c.GetHostIp()
if err != nil {
return fmt.Errorf("get host ip: %w", err)
}
c.logger.Println("\x1b[38;2;0;255;0mInternet connected.\x1b[38;2;255;255;255m") c.logger.Println("\x1b[38;2;0;255;0mInternet connected.\x1b[38;2;255;255;255m")
return nil return nil
} }
@ -97,30 +116,156 @@ func (c *conn) Disconnect() error {
c.logger.Println("poff response:", string(resp)) c.logger.Println("poff response:", string(resp))
} }
c.isConnectExecuted = false c.isConnectExecuted = false
c.logger.Println("\x1b[38;2;0;255;0mInternet disconnected.\x1b[38;2;255;255;255m")
return nil return nil
} }
func (c *conn) Ping() error { func (c *conn) SetDefaultRouteTable() error {
// Test - try to connect to Google DNS // route add -net default gw 10.64.64.64 metric 2000 dev ppp0
// ping -I ppp0 8.8.8.8 resp, err := exec.Command("route", "add", "-net", "default", "gw", c.gw, "metric", strconv.Itoa(inetMetric), "dev", ifName).Output()
resp, err := exec.Command("ping", "8.8.8.8").Output() if err != nil {
return fmt.Errorf("execute add cmd: %w", err)
}
// Check response
if len(resp) != 0 {
c.logger.Println("Not nil response:", string(resp))
}
c.isRouteSet = true
c.logger.Println("\x1b[38;2;0;255;0mInternet route table set.\x1b[38;2;255;255;255m")
return nil
}
func (c *conn) UnsetDefaultRouteTable() error {
if !c.isRouteSet {
return fmt.Errorf("route table is not set")
}
resp, err := exec.Command("route", "del", "-net", "default", "gw", c.gw, "metric", strconv.Itoa(inetMetric), "dev", ifName).Output()
if err != nil {
return fmt.Errorf("execute del cmd: %w", err)
}
// Check response
if len(resp) != 0 {
c.logger.Println("Not nil response:", string(resp))
}
c.logger.Println("\x1b[38;2;0;255;0mInternet route table unset.\x1b[38;2;255;255;255m")
return nil
}
func (c *conn) ping(flags []string, timeout int) error {
c.logger.Println("Ping", flags[len(flags)-1])
// Just counter
ctx, cancel := context.WithCancel(context.Background())
go func(c *conn, ctx context.Context) {
for i := 0; i < timeout; i++ {
c.logger.Printf("Ping %d/%d", i, timeout)
select {
case <-ctx.Done():
return
case <-time.After(time.Second):
}
}
}(c, ctx)
// Executing cmd
cmd := exec.Command("ping", flags...)
resp, err := cmd.Output()
cancel()
if err != nil { if err != nil {
c.logger.Println("Ping default interface cmd error:", err) c.logger.Println("Ping default interface cmd error:", err)
} }
c.logger.Println("Ping default interface resp:", string(resp))
resp, err = exec.Command("ping", "-I", "ppp0", "8.8.8.8").Output() // Parse
if err != nil { lines := strings.Split(string(resp), "\n")
c.logger.Println("Ping ppp0 interface cmd error:", err)
// Look for string "--- *.*.*.* ping statistics ---" by first simbol '-'
stLineI := 0
searchStLineLoop:
for i, l := range lines {
if len(l) > 0 && l[0] == '-' {
stLineI = i + 1
break searchStLineLoop
} }
c.logger.Println("Ping ppp0 interface resp:", string(resp)) }
if stLineI == 0 || stLineI >= len(lines) {
return fmt.Errorf("failed to find statistics line: %d", stLineI)
}
stStr := lines[stLineI]
if strings.Contains(string(resp), "Destination Host Unreachable") || strings.Contains(string(resp), "Destination Net Unreachable") { // Get third value "packet lost"
return fmt.Errorf("ping response: %s", string(resp)) values := strings.Split(stStr, ",")
if len(values) < 3 {
return fmt.Errorf("invalid statistic values(len): [%s]", values)
}
// Get number
words := strings.Split(values[2], " ")
if len(words) < 2 {
return fmt.Errorf("invalid \"packets lost\" value(words count): [%s]", words)
}
// First is ''
// Second is '...%'
packetsLost, err := strconv.Atoi(words[1][:len(words[1])-1]) // Without '%' char
if err != nil {
return fmt.Errorf("parse \"packets lost\" value: %w", err)
}
if packetsLost == 100 {
return fmt.Errorf("lost all packages")
}
if packetsLost > 0 {
c.logger.Printf("lost some packets: %d%%\n", packetsLost)
} }
return nil return nil
} }
func (c *conn) GetHostIp() (string, error) {
if !c.isConnectExecuted {
return "", fmt.Errorf("internet not connected")
}
// Wait some time for system to setup route table
time.Sleep(time.Until(c.connectTime.Add(inetConnectedTimeout)))
// Execute cmd
resp, err := exec.Command("route").Output()
if err != nil {
return "", fmt.Errorf("exec route cmd: %w", err)
}
// Check and split to lines
lines := strings.Split(string(resp), "\n")
if len(lines) <= 3 || lines[0] != "Kernel IP routing table" {
return "", fmt.Errorf("invalid route response: [% s]", lines)
}
// Search line about ppp interface
searchLoop:
for _, l := range lines[1:] {
words := strings.Fields(l)
if len(words) != 8 {
/// c.logger.Printf("invalid route line(words number): [%s]\n", words)
continue searchLoop
}
if words[7] == ifName {
if words[3] != "UH" {
// c.logger.Println("invalid flags")
continue searchLoop
}
return words[0], nil
}
}
return "", fmt.Errorf("found no suitable ppp interface")
}
func (c *conn) PingDefault() error {
return c.ping([]string{"-c", strconv.Itoa(pingPacketsCount), "-w", strconv.Itoa(pingTimeout), pingAddr}, pingTimeout)
}
func (c *conn) PingPPP() error {
return c.ping([]string{"-I", ifName, "-c", string(pingPacketsCount), "-w", string(pingTimeout), pingAddr}, pingTimeout)
}
func (c *conn) Ping() error {
return c.PingDefault()
}
func (c *conn) IsConnected() bool { func (c *conn) IsConnected() bool {
if !c.isConnectExecuted { if !c.isConnectExecuted {
return false return false
@ -140,7 +285,7 @@ func (c *conn) IsConnected() bool {
continue continue
} }
interfaceName := strings.Split(l, ":")[0] interfaceName := strings.Split(l, ":")[0]
if interfaceName == "ppp0" { if interfaceName == ifName {
return true return true
} }
} }
@ -149,8 +294,17 @@ func (c *conn) IsConnected() bool {
func (c *conn) Close() error { func (c *conn) Close() error {
c.isInited = false c.isInited = false
// Unset route table
if c.isRouteSet {
if err := c.UnsetDefaultRouteTable(); err != nil {
c.logger.Println("unset route table error:", err.Error())
}
}
// Disconnect
if c.isConnectExecuted {
if err := c.Disconnect(); err != nil { if err := c.Disconnect(); err != nil {
return fmt.Errorf("diconnect: %w", err) c.logger.Println("diconnect error:", err.Error())
}
} }
return nil return nil
} }

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
@ -130,12 +136,12 @@ func (m *modem) Init() error {
// submodulesLogger := io.Discard // FOR less logs // submodulesLogger := io.Discard // FOR less logs
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")
// } }
// m.sms = sms.New(log.New(submodulesLogger, "modem-sms : ", log.LstdFlags), m.port) // m.sms = sms.New(log.New(submodulesLogger, "modem-sms : ", log.LstdFlags), m.port)
// if err := m.sms.Init(); err != nil { // if err := m.sms.Init(); err != nil {
@ -203,6 +209,7 @@ func (m *modem) GetTime() (time.Time, error) {
return time.Time{}, fmt.Errorf("invalid values (len): [%s]", values) return time.Time{}, fmt.Errorf("invalid values (len): [%s]", values)
} }
timeStr := values[1] timeStr := values[1]
m.logger.Println("Raw time:", timeStr)
if len(timeStr) != len("yy/MM/dd,hh:mm:ss+zz") { if len(timeStr) != len("yy/MM/dd,hh:mm:ss+zz") {
return time.Time{}, fmt.Errorf("invalid time string: %s", timeStr) return time.Time{}, fmt.Errorf("invalid time string: %s", timeStr)
} }
@ -222,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
@ -358,18 +370,38 @@ 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
m.printCmd("AT+CTZU=1") // Turn on time update
return nil return nil
} }
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
@ -427,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
} }
@ -475,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)

View File

@ -25,7 +25,7 @@ func CheckSignal(port at.Port, logger *log.Logger) (bool, error) {
} }
func checkRssi(rssi int) error { func checkRssi(rssi int) error {
// rssi - Received signal strenght indicator // rssi - Received signal strength indicator
// 0 -113 dBm or less // 0 -113 dBm or less
// 1 -111 dBm // 1 -111 dBm
// 2...30 -109... - 53 dBm // 2...30 -109... - 53 dBm

69
main.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"fmt"
"log" "log"
"os" "os"
"os/signal" "os/signal"
@ -28,49 +29,78 @@ func main() {
log.Println("END") log.Println("END")
} }
var m modem.Modem
var logger *log.Logger
func InetInit() error {
// Connect to internet
if err := m.Ic().Connect(); err != nil {
return fmt.Errorf("connect to internet: %w", err)
}
// Setup route table
// Linux now always manage to add ppp0 interface to route table in time so it is better to add ф loop here
setupRouteTableLoop:
for {
if err := m.Ic().SetDefaultRouteTable(); err != nil {
logger.Println("set route table:", err.Error())
time.Sleep(2 * time.Second)
} else {
break setupRouteTableLoop
}
}
return nil
}
func Cmd(cmd string) {
resp, err := m.At().SendWithTimeout(cmd, 10*time.Second) //50*time.Millisecond)
logger.Println(cmd, "===>", resp, err)
}
func mainE(ctx context.Context) error { func mainE(ctx context.Context) error {
logger := log.New(os.Stdout, "main : ", log.LstdFlags) logger = log.New(os.Stdout, "main : ", log.LstdFlags)
m := modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags)) m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
logger.Println("||||||||||||||||| INIT |||||||||||||||") logger.Println("||||||||||||||||| INIT |||||||||||||||")
// If power is down modem won't find suitable devices add will try to send powerOn signal and then try again
initLoop: initLoop:
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done(): // For interupt
logger.Println("Break init loop") logger.Println("Break init loop")
return nil return nil
default: default:
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
} }
} }
// Final check for sure
if !m.IsConnected() { if !m.IsConnected() {
logger.Println("Modem is not connected") logger.Println("Modem is not connected")
return nil return nil
} }
// Close() deinits everything recursively
defer func() { defer func() {
logger.Println("||||||||||||||||| CLOSE |||||||||||||||") logger.Println("||||||||||||||||| CLOSE |||||||||||||||")
m.Close() m.Close()
}() }()
// Connect to internet // Internet connect
// if err := m.Ic().Connect(); err != nil { // if err := InetInit(); err != nil {
// return fmt.Errorf("connect to internet: %w", err) // return err
// } // }
logger.Println("||||||||||||||||| SMS |||||||||||||||||") logger.Println("||||||||||||||||| SMS |||||||||||||||||")
Cmd := func(cmd string) {
resp, err := m.At().SendWithTimeout(cmd, 50*time.Millisecond)
logger.Println(cmd, "===>", resp, err)
}
_ = Cmd
// Select ME PMS // Select ME PMS
// logger.Println("SEND SMS") // logger.Println("SEND SMS")
@ -79,7 +109,7 @@ initLoop:
// Cmd("AT+CREG?") // Cmd("AT+CREG?")
// Cmd("AT+CNMI?") // Cmd("AT+CNMI?")
// Cmd("AT+CSQ") Cmd("AT+CNETSCAN")
// Cmd("AT+CSCA?") // Cmd("AT+CSCA?")
// Cmd("AT+CPOL?") // Cmd("AT+CPOL?")
// Cmd("AT+COPS?") // Cmd("AT+COPS?")
@ -93,15 +123,20 @@ initLoop:
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+CPIN?")
// Cmd("AT+CCLK?") // Cmd("AT+CCLK?")
// 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.Println(m.GetTime()) // logger.Printf("GPS DATA: %f%s %f%s\n", data.Latitude, data.LatitudeIndicator, data.Longitude, data.LongitudeIndicator)
time.Sleep(2 * time.Second) // logger.Println(m.GetTime())
// logger.Println(m.Ic().Ping())
time.Sleep(time.Second)
} }
// Cmd("AT+CSQ") // Cmd("AT+CSQ")
// Cmd("AT+COPS?") // Cmd("AT+COPS?")