Compare commits

..

No commits in common. "master" and "v0.1.4" have entirely different histories.

17 changed files with 429 additions and 1151 deletions

View File

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

View File

@ -37,10 +37,8 @@ type Port interface {
Disconnect() error Disconnect() error
IsConnected() bool IsConnected() bool
RawSend(msg string) error RawSend(msg string) (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)
io.Closer io.Closer
} }
@ -107,73 +105,34 @@ func (p *atPort) IsConnected() bool {
return p.port != nil return p.port != nil
} }
func (p *atPort) RawRead(timeout time.Duration) (string, error) {
p.mutex.Lock()
defer p.mutex.Unlock()
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 && time.Now().After(deadline) {
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(outBuf), "\x1b[38;2;255;255;255m")
return string(outBuf), nil
}
// Low level write/read function // Low level write/read function
func (p *atPort) RawSend(msg string) error { func (p *atPort) RawSend(msg string) (string, error) {
p.mutex.Lock() p.mutex.Lock()
defer p.mutex.Unlock() defer p.mutex.Unlock()
// Write // Write
if _, err := p.port.Write([]byte(msg)); err != nil { if _, err := p.port.Write([]byte(msg)); err != nil {
return fmt.Errorf("serial port write: %w", err) return "", fmt.Errorf("serial port write: %w", err)
} }
return nil // 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)
}
return string(p.inputBuf[:readLen]), nil
} }
func (p *atPort) Send(cmd string) (Resp, error) { func (p *atPort) Send(cmd string) (Resp, error) {
err := p.RawSend(cmd + "\r\n") cmd += "\r\n"
rawResp, err := p.RawSend(cmd)
if err != nil { if err != nil {
return RespNil, fmt.Errorf("%s request: %w", cmd, err) return RespNil, fmt.Errorf("make request: %w", 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("read too small msg: %d byte - %s", len(rawResp), string(rawResp))
}
resp := rawResp[2 : len(rawResp)-2] // Cut \r\n
return Resp(resp), nil
}
func (p *atPort) SendWithTimeout(cmd string, timeout time.Duration) (Resp, error) {
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))
} }
resp := rawResp[2 : len(rawResp)-2] // Cut \r\n resp := rawResp[2 : len(rawResp)-2] // Cut \r\n

View File

@ -1,7 +1,6 @@
package gpio package gpio
import ( import (
"context"
"io" "io"
"log" "log"
"time" "time"
@ -9,10 +8,6 @@ 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
@ -21,7 +16,7 @@ type gpioPin struct {
type Pin interface { type Pin interface {
Init() error Init() error
PowerOn() PowerOn()
PowerOnCtx(ctx context.Context) PowerOff()
io.Closer io.Closer
} }
@ -36,47 +31,32 @@ func (p gpioPin) Init() error {
return gpio.Open() return gpio.Open()
} }
func waitCtx(ctx context.Context, timeout time.Duration) { func (p gpioPin) sendOnOffSignal() {
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()
waitCtx(ctx, 100*time.Millisecond) time.Sleep(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()
waitCtx(ctx, 3*time.Second) time.Sleep(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()
waitCtx(ctx, 30*time.Second) time.Sleep(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(context.Background()) p.sendOnOffSignal()
} }
func (p gpioPin) PowerOnCtx(ctx context.Context) { func (p gpioPin) PowerOff() {
p.sendOnOffSignal(ctx) p.sendOnOffSignal()
} }
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"` // ddmm.mmmmmm Latitude float64 `json:"Latitude"`
Longitude float64 `json:"Longitude"` // dddmm.mmmmmm Longitude float64 `json:"Longitude"`
LatitudeIndicator string `json:"Latitude_indicator"` // N/S - North/South LatitudeIndicator string `json:"Latitude_indicator"` // North/South
LongitudeIndicator string `json:"Longitude_indicator"` // W/E - West/East LongitudeIndicator string `json:"Longitude_indicator"` // West/East
Speed float64 `json:"Speed"` Speed float64 `json:"Speed"`
Course float64 `json:"-"` Course float64 `json:"-"`
Altitude float64 `json:"-"` Altitude float64 `json:"-"`
@ -59,6 +59,8 @@ 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

@ -85,7 +85,7 @@ func (g *gps) switchGpsMode(on bool) error {
// Check gps mode status // Check gps mode status
resp, err := g.port.Send("AT+CGPS?") resp, err := g.port.Send("AT+CGPS?")
if err != nil { if err != nil {
return err return fmt.Errorf("make at ask: %w", err)
} }
if !resp.Check() || !resp.CheckFront("+CGPS:") { if !resp.Check() || !resp.CheckFront("+CGPS:") {
return fmt.Errorf("get GPS mode: error response: %s", resp) 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 // Modem is not in GPS mode
resp, err = g.port.Send("AT+CGPS=" + onStr) resp, err = g.port.Send("AT+CGPS=" + onStr)
if err != nil { if err != nil {
return err return fmt.Errorf("set GPS mode: %w", err)
} }
if !resp.Check() { if !resp.Check() {
return fmt.Errorf("set GPS mode: error response: %s", resp) return fmt.Errorf("set GPS mode: error response: %s", resp)

View File

@ -34,14 +34,12 @@ 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
} }
@ -58,7 +56,7 @@ func (g *gps) rawCollect(flags nmeaFlags) (string, error) {
// Set output rate to 10Hz // Set output rate to 10Hz
if resp, err := g.port.Send("AT+CGPSNMEARATE=1"); err != nil { if resp, err := g.port.Send("AT+CGPSNMEARATE=1"); err != nil {
return "", err return "", fmt.Errorf("AT+CGPSNMEARATE= request: %w", err)
} else { } else {
if !resp.Check() { if !resp.Check() {
return "", fmt.Errorf("set output rate: error response: %s", resp) return "", fmt.Errorf("set output rate: error response: %s", resp)
@ -120,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

@ -42,14 +42,14 @@ checkLoop:
} }
st.GotResponses = true st.GotResponses = true
// g.logger.Println("NMEA check:", s) g.logger.Println("NMEA check:", s)
values := strings.Split(s, ",") values := strings.Split(s, ",")
if len(values[0]) != 6 { if len(values[0]) != 6 {
return StatusNil, fmt.Errorf("nmea invalid sentence: %s", s) return StatusNil, fmt.Errorf("nmea invalid sentence: %s", s)
} }
switch values[0][3:] { // Switch by content switch values[0][3:] { // Switch by content
case "GSV": // Any satelites case "GSV": // Any satelites
// g.logger.Println("check GSV") g.logger.Println("check GSV")
// Check len // Check len
if len(values) < 17 { if len(values) < 17 {
g.logger.Println("GSV too small values") g.logger.Println("GSV too small values")
@ -83,7 +83,7 @@ checkLoop:
} }
st.FoundSatelitesCount = satCount st.FoundSatelitesCount = satCount
case "GSA": // Active satelites case "GSA": // Active satelites
// g.logger.Println("check GSA") g.logger.Println("check GSA")
// Check len // Check len
if len(values) < 17 { if len(values) < 17 {
@ -107,7 +107,7 @@ checkLoop:
} }
st.ActiveSatelitesCount = max(st.ActiveSatelitesCount, count) st.ActiveSatelitesCount = max(st.ActiveSatelitesCount, count)
case "RMC": // Minimum GPS data case "RMC": // Minimum GPS data
// g.logger.Println("check RMC") g.logger.Println("check RMC")
// Check len // Check len
if len(values) < 12 { if len(values) < 12 {
g.logger.Println("RMC too small values") g.logger.Println("RMC too small values")
@ -125,7 +125,7 @@ checkLoop:
st.IsValidData = true st.IsValidData = true
} }
case "GST": case "GST":
// g.logger.Println("check GST") g.logger.Println("check GST")
// Check len // Check len
if len(values) < 8 { if len(values) < 8 {
g.logger.Println("GST too small values") g.logger.Println("GST too small values")

View File

@ -1,53 +1,51 @@
package internet package internet
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
"os"
"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 ( var apns = map[string]string{
pingPacketsCount = 3 "Tinkoff": "m.tinkoff",
pingTimeout = 5 }
inetConnectedTimeout = 4 * time.Second
pingAddr = "8.8.8.8" const pppConfigName = "annalistnet"
ifName = "ppp0" // Interface name const pppConfig = `
inetMetric = 2000 # Annalist project custom internet connection
)
# APN:
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T %s"
# Port:
%s
# Baudrate:
%d
noipdefault
usepeerdns
defaultroute
persist
noauth
nocrtscts
local
`
type conn struct { type conn struct {
logger *log.Logger logger *log.Logger
port at.Port port at.Port
pppPort string
isConnectExecuted bool
isInited bool
isRouteSet bool
connectTime time.Time
gw string // Gateway
} }
type Conn interface { type Conn interface {
Init(pppPort string) error Init() error
ConfigurePPP() error
Connect() error Ping() bool // Is connected
Disconnect() error
SetDefaultRouteTable() error
UnsetDefaultRouteTable() error
IsConnected() bool // Check interface existance
Ping() error
io.Closer io.Closer
} }
@ -55,256 +53,189 @@ func New(logger *log.Logger, port at.Port) Conn {
return &conn{ return &conn{
logger: logger, logger: logger,
port: port, port: port,
isConnectExecuted: false,
isInited: false,
isRouteSet: false,
} }
} }
func (c *conn) Init(pppPort string) error { func (c *conn) checkPackageExist(pname string) bool {
c.pppPort = pppPort resp, err := exec.Command("apt-mark", "showmanual", pname).Output()
// Setup only setup c.logger.Println("CHECK:", resp)
if err != nil {
c.logger.Println("CHECK PACKAGE ERROR: ", err.Error())
return false
}
return string(resp[:len(pname)]) == pname
}
func (c *conn) ensurePackage(pname string) error {
if c.checkPackageExist(pname) {
return nil
}
return fmt.Errorf("package %s not installed", pname)
// c.logger.Println("Installing", pname, "package...")
// resp, err := exec.Command("apt-get", "install", pname).Output()
// if err != nil {
// return fmt.Errorf("execute install cmd: %w", err)
// }
// c.logger.Println(resp)
// c.logger.Println("\x1b[38;2;255;0;0mComplete\x1b[38;2;255;255;255m")
// return nil
}
// Check requirenments
func (c *conn) checkReqs() error {
// Check AT port for sure
if c.port == nil || !c.port.IsConnected() {
return fmt.Errorf("AT port is not connect or nil")
}
// Ensure all necessary packages installed
if err := c.ensurePackage("ppp"); err != nil {
return fmt.Errorf("ensure ppp package: %w", err)
}
// if err := c.ensurePackage("net-tools"); err != nil {
// return fmt.Errorf("ensure net-tools package: %w", err)
// }
// Check SIM is valid
// AT+CPIN? and just check
resp, err := c.port.Send("AT+CPIN?")
if err != nil {
return fmt.Errorf("AT+CPIN? request: %w", err)
}
if !resp.Check() {
return fmt.Errorf("validate SIM: error response: %s", resp)
}
return nil
}
func (c *conn) ConfigurePPP() error {
// Get provider name and its APN
resp, err := c.port.Send("AT+CSPN?")
if err != nil {
return fmt.Errorf("AT+CSPN? request: %w", err)
}
if !resp.Check() {
return fmt.Errorf("get provider: error response: %s", resp)
}
strs := strings.Split(string(resp), "\"")
if len(strs) < 3 {
return fmt.Errorf("parse AT+CSPN response: %s", string(resp))
}
provider := strs[1]
apn := apns[provider]
if apn == "" {
return fmt.Errorf("no apn for provider: %s", provider)
}
// Make config
c.logger.Printf("Config values: %s, %s, %d", apn, c.port.GetName(), c.port.GetBaudrate())
config := fmt.Sprintf(pppConfig, apn, c.port.GetName(), c.port.GetBaudrate())
// Write to file
f, err := os.OpenFile("/etc/ppp/peers/"+pppConfigName, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return fmt.Errorf("open ppp config file %w", err)
}
defer f.Close()
if _, err := f.Write([]byte(config)); err != nil {
return fmt.Errorf("write to ppp config file: %w", err)
}
return nil
}
func (c *conn) setup() error {
c.logger.Println("Check requirenments...")
if err := c.checkReqs(); err != nil {
return fmt.Errorf("check requirenments: %w", err)
}
// DEBUG show networks and signal
resp, err := c.port.Send("AT+COPS?")
if err != nil {
return fmt.Errorf("AT+COPS? request: %w", err)
}
c.logger.Println("DEBUG networks:", resp)
resp, err = c.port.Send("AT+CSQ")
if err != nil {
return fmt.Errorf("AT+CSQ request: %w", err)
}
c.logger.Println("DEBUG signal quality:", resp)
// Configure ppp
// what is better ASK If /etc/ppp/peers/annalistnet not exists
c.logger.Println("Configure ppp...")
if err := c.ConfigurePPP(); err != nil {
return fmt.Errorf("configure ppp: %w", err)
}
return nil
}
func (c *conn) connect() error {
resp, err := exec.Command("pon", pppConfigName).Output()
if err != nil {
return fmt.Errorf("execute connect cmd: %w", err)
}
c.logger.Println("DEBUG pon response:", string(resp))
return nil
}
func (c *conn) diconnect() error {
resp, err := exec.Command("poff", pppConfigName).Output()
if err != nil {
return fmt.Errorf("execute disconnect cmd: %w", err)
}
c.logger.Println("DEBUG poff response:", string(resp))
return nil
}
func (c *conn) Init() error {
// Setup
c.logger.Println("Setup...")
if err := c.setup(); err != nil { if err := c.setup(); err != nil {
return fmt.Errorf("setup: %w", err) return fmt.Errorf("setup: %w", err)
} }
c.isInited = true // Connect
return nil c.logger.Println("Connect...")
if err := c.connect(); err != nil {
return fmt.Errorf("connect: %w", err)
} }
func (c *conn) Connect() error { //DEBUG
if !c.isInited {
return fmt.Errorf("internet submodule is not inited")
}
// Check is already connected
if c.isConnectExecuted {
return fmt.Errorf("already connected")
}
// Check signal
resp, err := exec.Command("pon", pppConfigName).Output()
if err != nil {
return fmt.Errorf("execute pon cmd: %w", err)
}
if len(resp) > 0 {
c.logger.Println("pon response:", string(resp))
}
c.isConnectExecuted = true
c.connectTime = time.Now()
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")
return nil
}
func (c *conn) Disconnect() error {
// return nil // Temporary do not turn off inet
if !c.isConnectExecuted {
return nil
// return fmt.Errorf("internet is not connected")
}
c.isConnectExecuted = false
resp, err := exec.Command("poff", pppConfigName).Output()
if err != nil {
return fmt.Errorf("execute poff cmd: %w", err)
}
if len(resp) > 0 {
c.logger.Println("poff response:", string(resp))
}
c.isConnectExecuted = false
c.logger.Println("\x1b[38;2;0;255;0mInternet disconnected.\x1b[38;2;255;255;255m")
return nil
}
func (c *conn) SetDefaultRouteTable() error {
// route add -net default gw 10.64.64.64 metric 2000 dev ppp0
resp, err := exec.Command("route", "add", "-net", "default", "gw", c.gw, "metric", strconv.Itoa(inetMetric), "dev", ifName).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 {
c.logger.Println("Ping default interface cmd error:", err)
}
// Parse
lines := strings.Split(string(resp), "\n")
// 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
}
}
if stLineI == 0 || stLineI >= len(lines) {
return fmt.Errorf("failed to find statistics line: %d", stLineI)
}
stStr := lines[stLineI]
// Get third value "packet lost"
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
}
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 {
if !c.isConnectExecuted {
return false
}
// Make "ifconfig" request
resp, err := exec.Command("ifconfig").Output() resp, err := exec.Command("ifconfig").Output()
if err != nil { if err != nil {
c.logger.Println("ifconfig cmd error:", err.Error()) return fmt.Errorf("execute ifconfig cmd: %w", err)
return false
} }
lines := strings.Split(string(resp), "\n") c.logger.Println("DEBUG ifconfig resp:", string(resp))
for _, l := range lines {
if len(l) == 0 { // Test connectin using Ping
continue c.logger.Println("Test...")
if !c.Ping() {
return fmt.Errorf("ping failed")
} }
if l[0] == ' ' { return nil
continue
} }
interfaceName := strings.Split(l, ":")[0]
if interfaceName == ifName { func (c *conn) Ping() bool {
return true // Test - try to connect to Google DNS
// ping -I ppp0 8.8.8.8
resp, err := exec.Command("ping", "8.8.8.8").Output()
if err != nil {
c.logger.Println("Ping 1 cmd error:", err)
} }
c.logger.Println("Ping 1 resp:", string(resp))
resp, err = exec.Command("ping", "-I", "ppp0", "8.8.8.8").Output()
if err != nil {
c.logger.Println("Ping 2 cmd error:", err)
} }
return false // Did not found c.logger.Println("Ping 2 resp:", string(resp))
return !(strings.Contains(string(resp), "Destination Host Unreachable") || strings.Contains(string(resp), "Destination Net Unreachable")) // tmp solution
} }
func (c *conn) Close() error { func (c *conn) Close() error {
c.isInited = false if err := c.diconnect(); err != nil {
// Unset route table return fmt.Errorf("diconnect: %w", err)
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 {
c.logger.Println("diconnect error:", err.Error())
}
} }
return nil return nil
} }

View File

@ -1,131 +0,0 @@
package internet
import (
"fmt"
"os"
"os/exec"
"strings"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/utils"
)
var apns = map[string]string{
"Tinkoff": "m.tinkoff",
"Megafon": "internet",
"BeeLine": "internet.beeline.ru",
}
const pppConfigName = "annalistnet"
const pppConfig = `
# Annalist project custom internet connection
# APN:
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T %s"
# Port:
%s
# Baudrate:
%d
noipdefault
usepeerdns
defaultroute
persist
noauth
nocrtscts
local
`
func (c *conn) setup() error {
if err := c.checkReqs(); err != nil {
return fmt.Errorf("check requirenments: %w", err)
}
// Configure ppp
// what is better ASK If /etc/ppp/peers/annalistnet not exists
if err := c.configurePPP(); err != nil {
return fmt.Errorf("configure ppp: %w", err)
}
return nil
}
// Check requirenments
func (c *conn) checkReqs() error {
// Check AT port for sure
if c.port == nil || !c.port.IsConnected() {
return fmt.Errorf("AT port is not connect or nil")
}
// Ensure all necessary packages installed
if err := c.ensurePackage("ppp"); err != nil {
return fmt.Errorf("ensure ppp package: %w", err)
}
// if err := c.ensurePackage("net-tools"); err != nil {
// return fmt.Errorf("ensure net-tools package: %w", err)
// }
// Check SIM is valid
if err := utils.CheckPIN(c.port, c.logger); err != nil {
return fmt.Errorf("PIN check: %w", err)
}
return nil
}
func (c *conn) ensurePackage(pname string) error {
if c.checkPackageExist(pname) {
return nil
}
return fmt.Errorf("package %s not installed", pname)
// c.logger.Println("Installing", pname, "package...")
// resp, err := exec.Command("apt-get", "install", pname).Output()
// if err != nil {
// return fmt.Errorf("execute install cmd: %w", err)
// }
// c.logger.Println(resp)
// c.logger.Println("\x1b[38;2;255;0;0mComplete\x1b[38;2;255;255;255m")
// return nil
}
func (c *conn) checkPackageExist(pname string) bool {
resp, err := exec.Command("apt-mark", "showmanual", pname).Output()
if err != nil {
c.logger.Println("check package error: ", err.Error())
return false
}
return string(resp[:len(pname)]) == pname
}
func (c *conn) configurePPP() error {
// Get provider name and its APN
resp, err := c.port.Send("AT+CSPN?")
if err != nil {
return fmt.Errorf("AT+CSPN? request: %w", err)
}
if !resp.Check() {
return fmt.Errorf("get provider: error response: %s", resp)
}
strs := strings.Split(string(resp), "\"")
if len(strs) < 3 {
return fmt.Errorf("parse AT+CSPN response: %s", string(resp))
}
provider := strs[1]
apn := apns[provider]
if apn == "" {
return fmt.Errorf("no apn for provider: %s", provider)
}
// Make config
c.logger.Printf("Config ppp values: %s, %s, %d", apn, c.pppPort, c.port.GetBaudrate())
config := fmt.Sprintf(pppConfig, apn, c.pppPort, c.port.GetBaudrate())
// Write to file
f, err := os.OpenFile("/etc/ppp/peers/"+pppConfigName, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return fmt.Errorf("open ppp config file %w", err)
}
defer f.Close()
if _, err := f.Write([]byte(config)); err != nil {
return fmt.Errorf("write to ppp config file: %w", err)
}
return nil
}

View File

@ -1,13 +1,11 @@
package modem package modem
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -19,12 +17,6 @@ import (
"gitea.unprism.ru/KRBL/sim-modem/api/modem/sms" "gitea.unprism.ru/KRBL/sim-modem/api/modem/sms"
) )
// 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 { type ModemData struct {
Port string `json:"Port"` Port string `json:"Port"`
gps.Data gps.Data
@ -39,7 +31,6 @@ 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
@ -62,17 +53,14 @@ type Modem interface {
IsConnected() bool IsConnected() bool
Update() error Update() error
GetData() ModemData GetData() ModemData
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
At() at.Port // Send At() at.Port // Send
Gps() gps.Gps // Update, GetData, GetStatus Gps() gps.Gps // Update, GetData, GetStatus
Sms() sms.Sms // Send, ReadNew Sms() sms.Sms // Send, ReadNew
Ic() internet.Conn // Connect, Disconnect
io.Closer io.Closer
} }
@ -92,41 +80,29 @@ func (m *modem) Init() error {
defer m.mutex.Unlock() defer m.mutex.Unlock()
// Turn module on // Turn module on
m.logger.Println("=============================== Init gpio") m.logger.Println("=============================== Turn on module")
if err := m.onOffPin.Init(); err != nil { if err := m.onOffPin.Init(); err != nil {
return fmt.Errorf("gpio pin init: %w", err) return fmt.Errorf("gpio pin init: %w", err)
} }
// Search // Search
m.logger.Println("=============================== Search") m.logger.Println("=============================== Search")
ports, err := m.searchPort(true) // Soft search
if err != nil { if err := m.searchPort(true); err != nil {
return fmt.Errorf("soft port search: %w", err) return fmt.Errorf("soft port search: %w", err)
} }
if len(ports) == 0 {
// Wide search // Wide search
ports, err := m.searchPort(true) if m.port == nil {
if err != nil { if err := m.searchPort(false); err != nil {
return fmt.Errorf("wide port search: %w", err) return fmt.Errorf("not soft port search: %w", err)
}
if len(ports) == 0 {
return fmt.Errorf("no AT ports found")
} }
} }
if len(ports) == 1 { if m.port == nil {
// return fmt.Errorf("only one AT port found") return fmt.Errorf("no port is detected")
m.logger.Println("!!!!! only one AT port found")
} }
// !!!!
// Internet connection and serial connection on one port is impossible, so:
// port[0] is for serial
// port[1] is for internet(ppp)
m.logger.Println(ports)
// Connect // Connect
m.logger.Println("=============================== Connect") m.logger.Println("=============================== Connect")
m.port = at.New(m.logger, ports[0], m.baudrate)
if err := m.connect(); err != nil { if err := m.connect(); err != nil {
return fmt.Errorf("connect: %w", err) return fmt.Errorf("connect: %w", err)
} }
@ -137,18 +113,18 @@ 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[len(ports)-1]); err != nil { 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()) 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 {
// m.logger.Printf("\x1b[38;2;255;0;0mSMS: %s\x1b[38;2;255;255;255m\n", err.Error()) m.logger.Printf("\x1b[38;2;255;0;0mSMS: %s\x1b[38;2;255;255;255m\n", err.Error())
// } else { } else {
// m.logger.Println("\x1b[38;2;0;255;0mSMS OK\x1b[38;2;255;255;255m") 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) m.gps = gps.New(log.New(submodulesLogger, "modem-gps : ", log.LstdFlags), m.port)
if err := m.gps.Init(); err != nil { if err := m.gps.Init(); err != nil {
@ -194,58 +170,16 @@ func (m *modem) GetData() ModemData {
} }
} }
func (m *modem) GetTime() (time.Time, error) {
// Make request
resp, err := m.port.Send("AT+CCLK?")
if err != nil {
return time.Time{}, err
}
if !resp.Check() || !resp.CheckFront("+CCLK: ") {
return time.Time{}, fmt.Errorf("CCLK? error response: %s", resp.String())
}
// Extract time string
values := strings.Split(strings.Split(resp.String(), "\n")[0], "\"")
if len(values) < 2 {
return time.Time{}, fmt.Errorf("invalid values (len): [%s]", values)
}
timeStr := values[1]
m.logger.Println("Raw time:", timeStr)
if len(timeStr) != len("yy/MM/dd,hh:mm:ss+zz") {
return time.Time{}, fmt.Errorf("invalid time string: %s", timeStr)
}
// Convert time zone
timeZoneStr := timeStr[len(timeStr)-2:]
timeZone, err := strconv.Atoi(timeZoneStr)
if err != nil {
return time.Time{}, fmt.Errorf("parse time zone: %w", err)
}
timeStr = fmt.Sprintf("%s%02d%02d", timeStr[:len(timeStr)-2], timeZone/4, timeZone%4)
// Parse to golang time
return time.Parse(timeLayout, timeStr)
}
func (m *modem) PowerOn() error { func (m *modem) PowerOn() error {
m.onOffPin.PowerOn() // DEBUG do not want to wait 30 seconds m.onOffPin.PowerOn() // DEBUG do not want to wait 30 seconds
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
} }
func (m *modem) restart() error {
m.PowerOff()
time.Sleep(10 * time.Second)
m.PowerOn()
return nil
}
func (m *modem) Sms() sms.Sms { func (m *modem) Sms() sms.Sms {
return m.sms return m.sms
} }
@ -258,38 +192,24 @@ func (m *modem) At() at.Port {
return m.port return m.port
} }
func (m *modem) Ic() internet.Conn {
return m.ic
}
func (m *modem) Close() error { // I can't return error so I log it func (m *modem) Close() error { // I can't return error so I log it
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
// Close submodules
if m.sms != nil {
if err := m.sms.Close(); err != nil { if err := m.sms.Close(); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mclose sms error: %s\x1b[38;2;255;255;255m\n", err.Error()) m.logger.Printf("\x1b[38;2;255;0;0mclose sms error: %s\x1b[38;2;255;255;255m\n", err.Error())
} }
}
if m.ic != nil {
if err := m.ic.Close(); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mclose internet error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
}
if m.gps != nil {
if err := m.gps.Close(); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mclose gps error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
}
// Close gpio and serial
if err := m.onOffPin.Close(); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mclose gpio pin error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
if err := m.port.Close(); err != nil { if err := m.port.Close(); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mclose serial port error: %s\x1b[38;2;255;255;255m\n", err.Error()) m.logger.Printf("\x1b[38;2;255;0;0mclose serial port error: %s\x1b[38;2;255;255;255m\n", err.Error())
} }
if err := m.onOffPin.Close(); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mclose gpio pin error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
if err := m.ic.Close(); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mclose internet connection error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
return nil return nil
} }
@ -349,10 +269,10 @@ func (m *modem) saveGPS(path string) error {
// Short way to send command // Short way to send command
func (m *modem) printCmd(cmd string) { func (m *modem) printCmd(cmd string) {
if resp, err := m.port.Send(cmd); err != nil { if resp, err := m.port.Send(cmd); err != nil {
m.logger.Println("FAILED TO SEND REQ", cmd, "===>", err.Error()) m.logger.Println("FAILED TO SEND REQ", cmd, ":", err.Error())
} else { } else {
_ = resp _ = resp
// m.logger.Println("CMD", cmd, "===>", resp) // m.logger.Println("CMD", cmd, ":", resp)
} }
} }
@ -367,46 +287,14 @@ func (m *modem) setupPort() error {
return fmt.Errorf("reset output buffer: %w", err) return fmt.Errorf("reset output buffer: %w", err)
} }
// 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
//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 {
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 read: %w", err)
}
return fmt.Errorf("read 0")
}
return nil
}
func (m *modem) checkPort(portName string) (outErr error) { func (m *modem) checkPort(portName string) (outErr error) {
defer func() { defer func() {
if outErr != nil { // Clear port if there is error if outErr != nil { // Clear port if there is error
@ -427,12 +315,9 @@ func (m *modem) checkPort(portName string) (outErr error) {
} }
defer m.port.Disconnect() // Do not bother about errors... defer m.port.Disconnect() // Do not bother about errors...
// m.restart()
// To filter dead ports // To filter dead ports
if _, err := m.port.Send("AT"); err != nil {
if err := m.checkCurPortDead(); err != nil { return fmt.Errorf("ping error: %w", err)
return fmt.Errorf("echo: %w", err)
} }
if err := m.setupPort(); err != nil { if err := m.setupPort(); err != nil {
@ -459,23 +344,43 @@ 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"
// Check model // m.logger.Printf("[% x]\n [% x]", []byte("SIMCOM_SIM7600E-H"), []byte(model))
foundModel := "" if len(model) >= len(rightModel) && model[:len(rightModel)] != rightModel {
for _, rightModel := range availableModels { return fmt.Errorf("invalid modem model: %s", model)
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
} }
func (m *modem) searchPort(isSoft bool) error {
// Get ports
ports := []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}
if !isSoft {
ps, err := getTtyDevices()
if err != nil {
fmt.Errorf("get serial devices: %w", err)
}
ports = ps
}
// Check ports
SearchLoop:
for _, p := range ports {
m.logger.Printf("Checking port %s ...\n", p)
if err := m.checkPort("/dev/" + p); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mCheck failed: %s\x1b[38;2;255;255;255m\n", err.Error())
continue SearchLoop
}
m.logger.Print("Found modem on port: ", p)
m.port = at.New(m.logger, "/dev/"+p, m.baudrate)
return nil
}
return nil
}
func (m *modem) ping() error { func (m *modem) ping() error {
resp, err := m.port.Send("AT") resp, err := m.port.Send("AT")
if err != nil { if err != nil {
@ -487,39 +392,10 @@ func (m *modem) ping() error {
return nil return nil
} }
func (m *modem) searchPort(isSoft bool) ([]string, error) { func getTtyDevices() ([]string, error) {
// Get ports
ports, err := getTtyPorts(isSoft)
if err != nil {
fmt.Errorf("get devices: %w", err)
}
// Check ports
return m.getAtPorts(ports)
}
func (m *modem) getAtPorts(ports []string) ([]string, error) {
outPorts := make([]string, 0)
for _, p := range ports {
m.logger.Printf("Checking port %s ...\n", p)
if err := m.checkPort("/dev/" + p); err != nil {
m.logger.Printf("\x1b[38;2;255;0;0mCheck failed: %s\x1b[38;2;255;255;255m\n", err.Error())
continue
}
m.logger.Print("Found AT port: ", p)
outPorts = append(outPorts, "/dev/"+p)
}
return outPorts, nil
}
func getTtyPorts(isSoft bool) ([]string, error) {
if isSoft {
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)
} }

69
api/modem/sms/errors.go Normal file
View File

@ -0,0 +1,69 @@
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")
}

View File

@ -1,88 +0,0 @@
package sms
import (
"fmt"
"strconv"
"strings"
)
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

@ -7,7 +7,6 @@ import (
"strings" "strings"
"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"
) )
type dialer struct { type dialer struct {
@ -31,66 +30,55 @@ func New(logger *log.Logger, port at.Port) Sms {
} }
func (d *dialer) Init() error { func (d *dialer) Init() error {
// Ensure serial port is connected // Ensure serial port
if !d.port.IsConnected() { if !d.port.IsConnected() {
return fmt.Errorf("serial port is not connected") return fmt.Errorf("serial port is not connected")
} }
// Check ping
if err := utils.CheckPIN(d.port, d.logger); err != nil {
return fmt.Errorf("check PIN: %w", err)
}
// Setup prefered message storage
// if err := d.setupMsgSt(); err != nil { // Does not use store now
// d.logger.Printf("ERROR setup msg storage: %s\n", err.Error())
// }
// Set notifications into console // Check SIM an PIN
if resp, err := d.port.Send("AT+CNMI=2,2"); err != nil || !resp.Check() { if resp, err := d.port.Send("AT+CPIN?"); err != nil || !resp.Check() {
if err != nil { if err != nil {
return err return fmt.Errorf("check pin: %w", err)
} }
return fmt.Errorf("CNMI= error response: %s", resp.String()) return fmt.Errorf("check pin: error response: %s", resp)
} }
// Check number // Ensure text format
if resp, err := d.port.Send("AT+CNUM"); err != nil || !resp.Check() { d.logger.Println(d.port.Send("AT+CMGF"))
if resp, err := d.port.Send("AT+CMGF=1"); err != nil || !resp.Check() {
if err != nil { if err != nil {
return err return fmt.Errorf("set to text format request: %w", err)
} }
return fmt.Errorf("CNUM error response: %s", resp.String()) return fmt.Errorf("set SIM format: error response: %s", resp)
} }
return nil return nil
} }
func (d *dialer) Send(number, msg string) error { func (d *dialer) Send(number, msg string) error {
sresp, err := d.port.Send(fmt.Sprintf(`AT+CMGSEX="%s"`, number)) // Because it will throw 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
if err != nil { if err != nil {
return err
}
d.logger.Println(sresp)
// 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) d.logger.Println("SEND RESPONSE:", resp)
resp, err = d.port.RawSend("\x1A")
if err != nil { if err != nil {
return fmt.Errorf("message request read: %w", err) return fmt.Errorf("message request: %w", err)
} }
d.logger.Println("Send response:", resp) d.logger.Println("SEND RESPONSE:", resp)
if !at.Resp(resp).Check() { errCode, err := GetError([]byte(resp))
return fmt.Errorf("error response: %s", resp) if err != nil {
return fmt.Errorf("send sms failed and failed to get error: %w", err)
} }
return nil return fmt.Errorf("failed to send with SMS error: %d - %s", errCode, DecodeError(errCode))
} }
// Reads all new messages // Reads all new messages
func (d *dialer) ReadNew() ([]string, error) { func (d *dialer) ReadNew() ([]string, error) {
resp, err := d.port.Send("AT+CMGL=\"ALL\"") resp, err := d.port.Send("AT+CMGL=\"UNREAD\"")
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("AT+CMGL request: %w", err)
} }
d.logger.Println("raw sms:", resp)
msgs := strings.Split(strings.Replace(string(resp), "\r", "", -1), "\n") msgs := strings.Split(strings.Replace(string(resp), "\r", "", -1), "\n")
outMsgs := make([]string, 0) outMsgs := make([]string, 0)

View File

@ -1,37 +0,0 @@
package utils
import (
"fmt"
"log"
"strings"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
)
func CheckService(port at.Port, logger *log.Logger) (bool, error) {
srv, err := getService(port)
if err != nil {
return false, fmt.Errorf("get service: %w", err)
}
if srv == "NO SERVICE" {
return false, nil
}
logger.Println("Current service:", srv)
return true, nil
}
// Returns service
func getService(port at.Port) (string, error) {
resp, err := port.Send("AT+CPSI?")
if err != nil {
return "", err
}
if !resp.Check() || !resp.CheckFront("+CPSI: ") {
return "", fmt.Errorf("error response: %s", resp)
}
values := strings.Split(strings.ReplaceAll(strings.Split(resp.RmFront("+CPSI: ").String(), "\n")[0], "\r", ""), ",")
if len(values) < 2 {
return "", fmt.Errorf("invalid values(len): [% s]", values)
}
return values[0], nil
}

View File

@ -1,109 +0,0 @@
package utils
import (
"fmt"
"log"
"strconv"
"strings"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
)
func CheckSignal(port at.Port, logger *log.Logger) (bool, error) {
rssi, ber, err := getSignalQuality(port)
if err != nil {
return false, fmt.Errorf("get signal quality: %w", err)
}
logger.Printf("check signal: rssi=%d ber=%d\n", rssi, ber)
if err := checkRssi(rssi); err != nil {
return false, nil
}
if err := checkBer(ber); err != nil {
logger.Printf("bad ber(not critical): %s", err.Error()) // Happened not to be critical
}
return true, nil
}
func checkRssi(rssi int) error {
// rssi - Received signal strength indicator
// 0 -113 dBm or less
// 1 -111 dBm
// 2...30 -109... - 53 dBm
// 31 -51 dBm or greater
// 99 not known or not detectable
// 100 -116 dBm or less
// 101 -115 dBm
// 102…191 -114... - 26dBm
// 191 -25 dBm or greater
// 199 not known or not detectable
// 100…199 expand to TDSCDMA, indicate RSCPreceived
if rssi <= 2 { // Too poor
return fmt.Errorf("too poor <= -109dBm")
}
if rssi > 2 && rssi <= 31 {
return nil // Can live
}
if rssi == 99 {
return fmt.Errorf("not known or not detectable")
}
if rssi >= 100 && rssi <= 102 {
return fmt.Errorf("too poor <= -114dBm")
}
if rssi > 102 && rssi <= 191 {
return nil // Can live
}
if rssi == 199 {
return fmt.Errorf("not known or not detectable")
}
return fmt.Errorf("invalid code %d", rssi)
}
func checkBer(ber int) error {
// ber - Bit error rate
// 0 <0.01%
// 1 0.01% --- 0.1%
// 2 0.1% --- 0.5%
// 3 0.5% --- 1.0%
// 4 1.0% --- 2.0%
// 5 2.0% --- 4.0%
// 6 4.0% --- 8.0%
// 7 >=8.0%
// 99 not known or not detectable
if ber >= 0 && ber <= 3 {
// ber -> [0%;1%)
// Ok, can live
return nil
}
if ber >= 4 && ber <= 7 {
return fmt.Errorf("too high: %d code", ber)
}
if ber == 99 {
return fmt.Errorf("not known or not detectable")
}
return fmt.Errorf("invalid code %d", ber)
}
// Returns rssi and ber(look above)
func getSignalQuality(port at.Port) (int, int, error) {
resp, err := port.Send("AT+CSQ")
if err != nil {
return 99, 99, err
}
if !resp.Check() || !resp.CheckFront("+CSQ: ") {
return 99, 99, fmt.Errorf("error response: %s", resp)
}
values := strings.Split(strings.ReplaceAll(strings.Split(resp.RmFront("+CSQ: ").String(), "\n")[0], "\r", ""), ",")
if len(values) != 2 {
return 99, 99, fmt.Errorf("invalid values(len): [% s]", values)
}
rssi, err := strconv.Atoi(values[0])
if err != nil {
return 99, 99, fmt.Errorf("parse rssi: %w", err)
}
ber, err := strconv.Atoi(values[1])
if err != nil {
return 99, 99, fmt.Errorf("parse ber: %w", err)
}
return rssi, ber, nil
}

View File

@ -1,26 +0,0 @@
package utils
import (
"fmt"
"log"
"strings"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
)
func CheckPIN(port at.Port, logger *log.Logger) error {
// Get code
resp, err := 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)
}
logger.Println("PIN is ready")
return nil
}

194
main.go
View File

@ -1,198 +1,64 @@
package main package main
import ( import (
"context"
"fmt"
"log" "log"
"os" "os"
"os/signal"
"syscall"
"time"
"gitea.unprism.ru/KRBL/sim-modem/api/modem" "gitea.unprism.ru/KRBL/sim-modem/api/modem"
) )
func main() { func main() {
log.Println("CGSG forever!!!") log.Println("CGSG forever!!!")
if err := mainE(); err != nil {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
// Just for logs
go func(ctx context.Context) {
<-ctx.Done()
log.Println("GOT INTERUPT SIGNAL")
}(ctx)
if err := mainE(ctx); err != nil {
log.Println("MAIN finished with error:", err.Error()) log.Println("MAIN finished with error:", err.Error())
} }
log.Println("END") log.Println("END")
} }
var m modem.Modem func mainE() error {
var logger *log.Logger logger := log.New(os.Stdout, "main : ", log.LstdFlags)
m := modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
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 {
logger = log.New(os.Stdout, "main : ", 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:
for {
select {
case <-ctx.Done(): // For interupt
logger.Println("Break init loop")
return nil
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("Try to turn on")
// if err := m.PowerOnCtx(ctx); err != nil { if err := m.PowerOn(); err != nil {
// logger.Println("Turn on error:", err.Error()) return err
// }
time.Sleep(time.Second)
continue initLoop
} }
break initLoop logger.Println("Reinit")
if err := m.Init(); err != nil {
return err
} }
} }
// Final check for sure
if !m.IsConnected() { if !m.IsConnected() {
logger.Println("Modem is not connected") logger.Println("AAAAAAAAAAAAAAA Modem is not connected")
return nil return nil
} }
// Close() deinits everything recursively // m.PowerOff()
defer func() { // time.Sleep(10 * time.Second)
logger.Println("||||||||||||||||| CLOSE |||||||||||||||") // m.PowerOn()
m.Close()
}()
// Internet connect logger.Println("||||||||||||||||| GET INFO |||||||||||||||||")
// if err := InetInit(); err != nil { logger.Println(m.Update())
logger.Printf("DATA: %+v\n", m.GetData())
logger.Println("||||||||||||||||| SEND SMS |||||||||||||||||")
logger.Println(m.At().Send("AT+CNUM"))
// if err := m.Sms().Send("+79218937173", "CGSG forever"); err != nil {
// return err // return err
// } // }
if ms, err := m.Sms().ReadNew(); err != nil {
logger.Println("||||||||||||||||| SMS |||||||||||||||||") return err
} else {
// Select ME PMS logger.Println("NEW:", ms)
// logger.Println("SEND SMS")
// logger.Println(m.Sms().Send("+79218937173", "CGSG forever!!!"))
// m.At().RawSend("\r\n\x1A\r\n")
// Cmd("AT+CREG?")
// Cmd("AT+CNMI?")
Cmd("AT+CNETSCAN")
// Cmd("AT+CSCA?")
// Cmd("AT+CPOL?")
// Cmd("AT+COPS?")
// // Cmd("AT+COPS=?")
// Cmd("AT+CPSI?")
// resp, err = m.At().Send("AT+CNMI=2,2")
for {
select {
case <-ctx.Done():
logger.Println("Break main loop")
return nil
default:
// Cmd("AT+CPSI?")
// Cmd("AT+CIPGSMLOC=1")
// Cmd("AT+CSQ")
// Cmd("AT+CTZU?")
// Cmd("AT+CPIN?")
// Cmd("AT+CCLK?")
// logger.Println(m.Gps().GetStatus())
// m.Update()
// st, _ := m.Gps().GetStatus()
// 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(time.Second)
} }
// Cmd("AT+CSQ") logger.Println("||||||||||||||||| Checking gps status |||||||||||||||||")
// Cmd("AT+COPS?") st, err := m.Gps().GetStatus()
if err != nil {
// if err := m.CheckSignal(); err != nil { return err
// logger.Println(err)
// } else {
// logger.Println("AAAAAAAAAAA THERE IS SIGNAL")
// }
// readLen, err := m.At().SerialPort().Read(buf)
// if err != nil {
// return err
// }
// if readLen > 0 {
// logger.Println(string(buf[:readLen]))
// }
} }
logger.Printf("GPS Status:%+v\n", st)
// 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()) // logger.Println("Turn off", m.PowerOff())