Compare commits
No commits in common. "master" and "v0.1.6" have entirely different histories.
2
Makefile
2
Makefile
@ -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
|
||||||
|
@ -37,8 +37,7 @@ type Port interface {
|
|||||||
Disconnect() error
|
Disconnect() error
|
||||||
IsConnected() bool
|
IsConnected() bool
|
||||||
|
|
||||||
RawSend(msg string) error
|
RawSend(msg string, timeout time.Duration) (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)
|
||||||
|
|
||||||
@ -107,53 +106,42 @@ func (p *atPort) IsConnected() bool {
|
|||||||
return p.port != nil
|
return p.port != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *atPort) RawRead(timeout time.Duration) (string, error) {
|
// Low level write/read function
|
||||||
|
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()
|
||||||
|
|
||||||
deadline := time.Now().Add(timeout)
|
// Write
|
||||||
|
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 && time.Now().After(deadline) {
|
if readLen == 0 {
|
||||||
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) {
|
||||||
err := p.RawSend(cmd + "\r\n")
|
rawResp, err := p.RawSend(cmd+"\r\n", time.Microsecond)
|
||||||
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))
|
||||||
}
|
}
|
||||||
@ -163,15 +151,10 @@ 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) {
|
||||||
err := p.RawSend(cmd + "\r\n")
|
rawResp, err := p.RawSend(cmd+"\r\n", timeout)
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
@ -1,27 +1,15 @@
|
|||||||
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
|
||||||
@ -30,10 +18,6 @@ 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 {
|
||||||
@ -42,9 +26,6 @@ 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
|
||||||
|
|
||||||
@ -53,12 +34,10 @@ type Conn interface {
|
|||||||
|
|
||||||
func New(logger *log.Logger, port at.Port) Conn {
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +60,12 @@ 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 {
|
||||||
@ -89,15 +74,11 @@ 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()
|
|
||||||
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")
|
|
||||||
|
|
||||||
|
// Set default route
|
||||||
|
|
||||||
|
c.logger.Println("\x1b[38;2;0;255;0mInternet connected.\x1b[38;2;255;255;255m")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,154 +97,28 @@ 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) 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 {
|
func (c *conn) Ping() error {
|
||||||
return c.PingDefault()
|
// 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 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()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Println("Ping ppp0 interface cmd error:", err)
|
||||||
|
}
|
||||||
|
c.logger.Println("Ping ppp0 interface resp:", string(resp))
|
||||||
|
|
||||||
|
if strings.Contains(string(resp), "Destination Host Unreachable") || strings.Contains(string(resp), "Destination Net Unreachable") {
|
||||||
|
return fmt.Errorf("ping response: %s", string(resp))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) IsConnected() bool {
|
func (c *conn) IsConnected() bool {
|
||||||
@ -285,7 +140,7 @@ func (c *conn) IsConnected() bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
interfaceName := strings.Split(l, ":")[0]
|
interfaceName := strings.Split(l, ":")[0]
|
||||||
if interfaceName == ifName {
|
if interfaceName == "ppp0" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,17 +149,8 @@ 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 err := c.Disconnect(); err != nil {
|
||||||
if c.isRouteSet {
|
return fmt.Errorf("diconnect: %w", err)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package modem
|
package modem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -22,9 +21,6 @@ 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
|
||||||
@ -39,7 +35,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
|
||||||
@ -65,7 +60,6 @@ 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
|
||||||
@ -136,12 +130,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[len(ports)-1]); err != nil {
|
// if err := m.ic.Init(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 {
|
||||||
@ -209,7 +203,6 @@ 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)
|
||||||
}
|
}
|
||||||
@ -229,11 +222,6 @@ 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
|
||||||
@ -370,37 +358,17 @@ 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 err := m.port.RawSend("AT\r\n"); err != nil {
|
if resp, err := m.port.RawSend("AT\r\n", 20*time.Millisecond); err != nil || len(resp) == 0 {
|
||||||
return fmt.Errorf("raw send: %w", err)
|
|
||||||
}
|
|
||||||
if resp, err := m.port.RawRead(time.Second); err != nil || len(resp) == 0 {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("raw read: %w", err)
|
return fmt.Errorf("raw send: %w", err)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("read 0")
|
return fmt.Errorf("read 0")
|
||||||
}
|
}
|
||||||
@ -459,19 +427,11 @@ 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
|
||||||
}
|
}
|
||||||
@ -515,11 +475,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 ttyPorts, nil
|
return []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}, 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)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ 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"
|
||||||
@ -68,14 +69,9 @@ 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
|
||||||
// Message body
|
|
||||||
if err := d.port.RawSend(fmt.Sprintf("%s\x1A", msg)); err != nil {
|
|
||||||
return fmt.Errorf("message request: %w", err)
|
|
||||||
}
|
|
||||||
resp, err := d.port.RawRead(at.ReadTimeout)
|
|
||||||
if err != nil {
|
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() {
|
if !at.Resp(resp).Check() {
|
||||||
|
@ -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 strength indicator
|
// rssi - Received signal strenght 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
69
main.go
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -29,78 +28,49 @@ 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(): // For interupt
|
case <-ctx.Done():
|
||||||
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.PowerOnCtx(ctx); err != nil {
|
// if err := m.PowerOn(); 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()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Internet connect
|
// Connect to internet
|
||||||
// if err := InetInit(); err != nil {
|
// if err := m.Ic().Connect(); err != nil {
|
||||||
// return err
|
// return fmt.Errorf("connect to internet: %w", 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")
|
||||||
@ -109,7 +79,7 @@ initLoop:
|
|||||||
|
|
||||||
// Cmd("AT+CREG?")
|
// Cmd("AT+CREG?")
|
||||||
// Cmd("AT+CNMI?")
|
// Cmd("AT+CNMI?")
|
||||||
Cmd("AT+CNETSCAN")
|
// Cmd("AT+CSQ")
|
||||||
// Cmd("AT+CSCA?")
|
// Cmd("AT+CSCA?")
|
||||||
// Cmd("AT+CPOL?")
|
// Cmd("AT+CPOL?")
|
||||||
// Cmd("AT+COPS?")
|
// Cmd("AT+COPS?")
|
||||||
@ -123,20 +93,15 @@ 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("FOUND SATELITES: %d\n", st.FoundSatelitesCount)
|
// logger.Printf("GPS STATUS: %+v", st)
|
||||||
// data := m.GetData()
|
// logger.Printf("GPS DATA: %+v", 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())
|
time.Sleep(2 * time.Second)
|
||||||
// logger.Println(m.Ic().Ping())
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
}
|
||||||
// Cmd("AT+CSQ")
|
// Cmd("AT+CSQ")
|
||||||
// Cmd("AT+COPS?")
|
// Cmd("AT+COPS?")
|
||||||
|
Loading…
Reference in New Issue
Block a user