Compare commits
22 Commits
fd9e999b5a
...
v0.1.6
Author | SHA1 | Date | |
---|---|---|---|
5065cfdfca | |||
5059814d39 | |||
d21ad0ce00 | |||
75d05f197c | |||
b3fb13b7b3 | |||
dde1411b18 | |||
6c110b9a8b | |||
cb07b6ac62 | |||
be99ed7fbf | |||
d625866d92 | |||
7ee03906f8 | |||
0b76884112 | |||
59bd2c5db3 | |||
0837d376f7 | |||
6a96656434 | |||
026c1aa3bb | |||
90a06e6afa | |||
e2e02ebbfe | |||
9bc94bfe7c | |||
061ba2a859 | |||
6498d20378 | |||
b831d44294 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
out/
|
out/
|
||||||
Makefile
|
|
||||||
go.sum
|
go.sum
|
||||||
.git/
|
.git/
|
||||||
*.swp
|
*.swp
|
||||||
|
7
Makefile
Normal file
7
Makefile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export GOOS=linux
|
||||||
|
export GOARCH=arm
|
||||||
|
export GOARM=6
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
|
build:
|
||||||
|
@go build -o out/modem main.go
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
@ -12,12 +13,14 @@ import (
|
|||||||
// Some constants
|
// Some constants
|
||||||
const (
|
const (
|
||||||
ReadTimeout = time.Second
|
ReadTimeout = time.Second
|
||||||
InputBufSize = 128
|
InputBufSize = 512
|
||||||
)
|
)
|
||||||
|
|
||||||
type atPort struct {
|
type atPort struct {
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
|
||||||
|
mutex sync.Mutex // Mutex for all operation with serial port
|
||||||
|
|
||||||
baudrate int
|
baudrate int
|
||||||
portName string
|
portName string
|
||||||
port serial.Port
|
port serial.Port
|
||||||
@ -27,14 +30,16 @@ type atPort struct {
|
|||||||
type Port interface {
|
type Port interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
GetBaudrate() int
|
GetBaudrate() int
|
||||||
GetSerialPort() serial.Port // For extra need
|
SerialPort() serial.Port // For extra need
|
||||||
|
Mutex() sync.Locker // retruns pointer to mutex for advanced use like readign NMEA reports
|
||||||
|
|
||||||
Connect() error
|
Connect() error
|
||||||
Disconnect() error
|
Disconnect() error
|
||||||
IsConnected() bool
|
IsConnected() bool
|
||||||
|
|
||||||
RawSend(msg string) (string, error)
|
RawSend(msg string, 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
|
||||||
}
|
}
|
||||||
@ -56,11 +61,18 @@ func (p *atPort) GetBaudrate() int {
|
|||||||
return p.baudrate
|
return p.baudrate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *atPort) GetSerialPort() serial.Port {
|
func (p *atPort) SerialPort() serial.Port {
|
||||||
return p.port
|
return p.port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *atPort) Mutex() sync.Locker {
|
||||||
|
return &p.mutex
|
||||||
|
}
|
||||||
|
|
||||||
func (p *atPort) Connect() error {
|
func (p *atPort) Connect() error {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
p.logger.Println("Connecting to", p.portName, "...")
|
p.logger.Println("Connecting to", p.portName, "...")
|
||||||
s, err := serial.Open(p.portName, &serial.Mode{BaudRate: p.baudrate})
|
s, err := serial.Open(p.portName, &serial.Mode{BaudRate: p.baudrate})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,8 +88,10 @@ func (p *atPort) Connect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *atPort) Disconnect() error {
|
func (p *atPort) Disconnect() error {
|
||||||
|
p.mutex.Lock()
|
||||||
defer func() {
|
defer func() {
|
||||||
p.port = nil
|
p.port = nil
|
||||||
|
p.mutex.Unlock()
|
||||||
}()
|
}()
|
||||||
if err := p.port.Close(); err != nil {
|
if err := p.port.Close(); err != nil {
|
||||||
return fmt.Errorf("close port: %w", err)
|
return fmt.Errorf("close port: %w", err)
|
||||||
@ -86,34 +100,63 @@ func (p *atPort) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *atPort) IsConnected() bool {
|
func (p *atPort) IsConnected() bool {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
return p.port != nil
|
return p.port != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Low level write/read function
|
// Low level write/read function
|
||||||
func (p *atPort) RawSend(msg string) (string, error) {
|
func (p *atPort) RawSend(msg string, timeout time.Duration) (string, error) {
|
||||||
|
p.mutex.Lock()
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(timeout)
|
||||||
// Read
|
// Read
|
||||||
|
outBuf := make([]byte, 0)
|
||||||
|
readLoop:
|
||||||
|
for {
|
||||||
readLen, err := p.port.Read(p.inputBuf)
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("port read: %w", err)
|
return "", fmt.Errorf("port read: %w", err)
|
||||||
}
|
}
|
||||||
|
if readLen == 0 {
|
||||||
|
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(p.inputBuf[:readLen]), nil
|
return string(outBuf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *atPort) Send(cmd string) (Resp, error) {
|
func (p *atPort) Send(cmd string) (Resp, error) {
|
||||||
cmd += "\r\n"
|
rawResp, err := p.RawSend(cmd+"\r\n", time.Microsecond)
|
||||||
rawResp, err := p.RawSend(cmd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return RespNil, fmt.Errorf("make request: %w", err)
|
return RespNil, fmt.Errorf("%s request: %w", cmd, err)
|
||||||
}
|
}
|
||||||
if len(rawResp) <= 4 {
|
if len(rawResp) <= 4 {
|
||||||
return RespNil, fmt.Errorf("read too small msg: %d byte - %s", len(rawResp), string(rawResp))
|
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
|
||||||
|
|
||||||
|
return Resp(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *atPort) SendWithTimeout(cmd string, timeout time.Duration) (Resp, error) {
|
||||||
|
rawResp, err := p.RawSend(cmd+"\r\n", 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
|
||||||
|
|
||||||
@ -121,5 +164,8 @@ func (p *atPort) Send(cmd string) (Resp, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *atPort) Close() error {
|
func (p *atPort) Close() error {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
return p.port.Close()
|
return p.port.Close()
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,10 @@ func (resp Resp) RmFront(str string) Resp {
|
|||||||
return Resp(string(resp)[len(str):])
|
return Resp(string(resp)[len(str):])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (resp Resp) CheckFront(str string) bool {
|
||||||
|
return len(resp) >= len(str) && resp[:len(str)].String() == str
|
||||||
|
}
|
||||||
|
|
||||||
func (resp Resp) String() string {
|
func (resp Resp) String() string {
|
||||||
return string(resp)
|
return string(resp)
|
||||||
}
|
}
|
||||||
|
137
api/modem/gps.go
137
api/modem/gps.go
@ -1,137 +0,0 @@
|
|||||||
package modem
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/CGSG-2021-AE4/modem-test/api/modem/at"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GpsData struct {
|
|
||||||
Latitude float64 `json:"Latitude"`
|
|
||||||
Longitude float64 `json:"Longitude"`
|
|
||||||
LatitudeIndicator string `json:"Latitude_indicator"` // North/South
|
|
||||||
LongitudeIndicator string `json:"Longitude_indicator"` // West/East
|
|
||||||
Speed float64 `json:"Speed"`
|
|
||||||
Course float64 `json:"-"`
|
|
||||||
Altitude float64 `json:"-"`
|
|
||||||
Date string `json:"-"`
|
|
||||||
Time string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var GpsInfoNil = GpsData{}
|
|
||||||
|
|
||||||
func deg2rad(deg float64) float64 {
|
|
||||||
return deg * (math.Pi / 180)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gps *GpsData) calculateSpeed(newLatitude, newLongitude float64, lastUpdateTime time.Time) {
|
|
||||||
earthRad := 6371.0 // TODO ?
|
|
||||||
dLat := deg2rad(math.Abs(newLatitude - gps.Latitude))
|
|
||||||
dLon := deg2rad(math.Abs(newLongitude - gps.Longitude))
|
|
||||||
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
|
|
||||||
math.Cos(deg2rad(newLatitude))*math.Cos(deg2rad(gps.Latitude))*math.Sin(dLon/2)*math.Sin(dLon/2)
|
|
||||||
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
|
||||||
|
|
||||||
gps.Speed = earthRad * c / (math.Abs(float64(time.Since(lastUpdateTime))))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse string from AT command that contains gps data
|
|
||||||
func (gps *GpsData) decode(str string) error {
|
|
||||||
var err error
|
|
||||||
newGpsInfo := GpsData{}
|
|
||||||
strs := strings.Split(strings.Split(strings.Replace(str, " ", "", -1), "\n")[0], ",")
|
|
||||||
|
|
||||||
newGpsInfo.Latitude, err = strconv.ParseFloat(strs[0], 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse latitude: %w", err)
|
|
||||||
}
|
|
||||||
newGpsInfo.Longitude, err = strconv.ParseFloat(strs[2], 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse longitude: %w", err)
|
|
||||||
}
|
|
||||||
newGpsInfo.Latitude /= 100
|
|
||||||
newGpsInfo.Longitude /= 100
|
|
||||||
newGpsInfo.LatitudeIndicator = strs[1]
|
|
||||||
newGpsInfo.LongitudeIndicator = strs[3]
|
|
||||||
newGpsInfo.Date = strs[4]
|
|
||||||
newGpsInfo.Time = strs[5]
|
|
||||||
newGpsInfo.Altitude, err = strconv.ParseFloat(strs[6], 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse altitude: %w", err)
|
|
||||||
}
|
|
||||||
newGpsInfo.Speed, err = strconv.ParseFloat(strs[7], 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse speed: %w", err)
|
|
||||||
}
|
|
||||||
// Course sometimes may be null
|
|
||||||
if len(strs[8]) > 0 {
|
|
||||||
newGpsInfo.Course, err = strconv.ParseFloat(strs[8], 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse course: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*gps = newGpsInfo
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gps *GpsData) Update(port at.Port) error {
|
|
||||||
if err := switchGpsMode(port, true); err != nil {
|
|
||||||
return fmt.Errorf("try to GPS mode: %w", err)
|
|
||||||
}
|
|
||||||
defer switchGpsMode(port, false)
|
|
||||||
|
|
||||||
resp, err := port.Send("AT+CGPSINFO")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("receive GPS data: %w", err)
|
|
||||||
}
|
|
||||||
if !resp.Check() {
|
|
||||||
return fmt.Errorf("error response")
|
|
||||||
}
|
|
||||||
if err := gps.decode(strings.Split(strings.Replace(resp.RmFront("+CGPSINFO:").String(), "\r", "", -1), "\n")[0]); err != nil {
|
|
||||||
return fmt.Errorf("decode: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func switchGpsMode(port at.Port, on bool) error {
|
|
||||||
onStr := "0"
|
|
||||||
if on {
|
|
||||||
onStr = "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset input
|
|
||||||
if err := port.GetSerialPort().ResetInputBuffer(); err != nil {
|
|
||||||
return fmt.Errorf("reset input buffer: %w", err)
|
|
||||||
}
|
|
||||||
// Reset output
|
|
||||||
if err := port.GetSerialPort().ResetOutputBuffer(); err != nil {
|
|
||||||
return fmt.Errorf("reset output buffer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check gps mode status
|
|
||||||
resp, err := port.Send("AT+CGPS?")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("make at ask: %w", err)
|
|
||||||
}
|
|
||||||
if !resp.Check() {
|
|
||||||
return fmt.Errorf("error response")
|
|
||||||
}
|
|
||||||
ans := strings.Replace(strings.Split(strings.Split(resp.RmFront("+CGPS:").String(), "\n")[0], ",")[0], " ", "", -1)
|
|
||||||
if ans == onStr {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modem is not in GPS mode
|
|
||||||
resp, err = port.Send("AT+CGPS=" + onStr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("try to switch to gps: %w", err)
|
|
||||||
}
|
|
||||||
if !resp.Check() {
|
|
||||||
return fmt.Errorf("switch tp GPS failed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
85
api/modem/gps/data.go
Normal file
85
api/modem/gps/data.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package gps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Latitude float64 `json:"Latitude"`
|
||||||
|
Longitude float64 `json:"Longitude"`
|
||||||
|
LatitudeIndicator string `json:"Latitude_indicator"` // North/South
|
||||||
|
LongitudeIndicator string `json:"Longitude_indicator"` // West/East
|
||||||
|
Speed float64 `json:"Speed"`
|
||||||
|
Course float64 `json:"-"`
|
||||||
|
Altitude float64 `json:"-"`
|
||||||
|
Date string `json:"-"`
|
||||||
|
Time string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var GpsInfoNil = Data{}
|
||||||
|
|
||||||
|
func deg2rad(deg float64) float64 {
|
||||||
|
return deg * (math.Pi / 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gps *Data) CalculateSpeed(newLatitude, newLongitude float64, lastUpdateTime time.Time) {
|
||||||
|
earthRad := 6371.0 // TODO ?
|
||||||
|
dLat := deg2rad(math.Abs(newLatitude - gps.Latitude))
|
||||||
|
dLon := deg2rad(math.Abs(newLongitude - gps.Longitude))
|
||||||
|
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
|
||||||
|
math.Cos(deg2rad(newLatitude))*math.Cos(deg2rad(gps.Latitude))*math.Sin(dLon/2)*math.Sin(dLon/2)
|
||||||
|
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||||||
|
|
||||||
|
gps.Speed = earthRad * c / (math.Abs(float64(time.Since(lastUpdateTime))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// To remove warning
|
||||||
|
|
||||||
|
// Parse string from AT command that contains gps data
|
||||||
|
func (gps *Data) decode(str string) error {
|
||||||
|
var err error
|
||||||
|
newGpsInfo := Data{}
|
||||||
|
strs := strings.Split(strings.Split(strings.Replace(str, " ", "", -1), "\n")[0], ",")
|
||||||
|
logger := log.New(io.Discard, "modem-gps", log.LstdFlags)
|
||||||
|
|
||||||
|
if len(strs) < 7 {
|
||||||
|
return fmt.Errorf("ERROR: too small msg: %s", strs)
|
||||||
|
}
|
||||||
|
newGpsInfo.Latitude, err = strconv.ParseFloat(strs[0], 64)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("ERROR parse latitude:", err.Error())
|
||||||
|
}
|
||||||
|
newGpsInfo.Longitude, err = strconv.ParseFloat(strs[2], 64)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("ERROR parse longitude:", err.Error())
|
||||||
|
}
|
||||||
|
newGpsInfo.Latitude /= 100
|
||||||
|
newGpsInfo.Longitude /= 100
|
||||||
|
newGpsInfo.LatitudeIndicator = strs[1]
|
||||||
|
newGpsInfo.LongitudeIndicator = strs[3]
|
||||||
|
newGpsInfo.Date = strs[4]
|
||||||
|
newGpsInfo.Time = strs[5]
|
||||||
|
newGpsInfo.Altitude, err = strconv.ParseFloat(strs[6], 64)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("ERROR parse altitude:", err.Error())
|
||||||
|
}
|
||||||
|
newGpsInfo.Speed, err = strconv.ParseFloat(strs[7], 64)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("ERROR parse speed:", err.Error())
|
||||||
|
}
|
||||||
|
// Course sometimes may be null
|
||||||
|
if len(strs[8]) > 0 {
|
||||||
|
newGpsInfo.Course, err = strconv.ParseFloat(strs[8], 64)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("ERROR parse course:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*gps = newGpsInfo
|
||||||
|
return nil
|
||||||
|
}
|
107
api/modem/gps/gps.go
Normal file
107
api/modem/gps/gps.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package gps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gps struct {
|
||||||
|
logger *log.Logger
|
||||||
|
port at.Port
|
||||||
|
data Data
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gps interface {
|
||||||
|
Init() error
|
||||||
|
Update() error
|
||||||
|
|
||||||
|
GetData() Data
|
||||||
|
GetStatus() (Status, error)
|
||||||
|
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(logger *log.Logger, port at.Port) Gps {
|
||||||
|
return &gps{
|
||||||
|
logger: logger,
|
||||||
|
port: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gps) Init() error {
|
||||||
|
if !g.port.IsConnected() {
|
||||||
|
return fmt.Errorf("at port is not connected")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gps) Update() error {
|
||||||
|
if err := g.switchGpsMode(true); err != nil {
|
||||||
|
return fmt.Errorf("try to GPS mode: %w", err)
|
||||||
|
}
|
||||||
|
defer g.switchGpsMode(false)
|
||||||
|
|
||||||
|
resp, err := g.port.Send("AT+CGPSINFO")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("receive GPS data: %w", err)
|
||||||
|
}
|
||||||
|
if !resp.Check() || !resp.CheckFront("+CGPSINFO:") {
|
||||||
|
return fmt.Errorf("get GPS info: error response: %s", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.data.decode(strings.Split(strings.Replace(resp.RmFront("+CGPSINFO:").String(), "\r", "", -1), "\n")[0]); err != nil {
|
||||||
|
g.logger.Printf("error decode: %s\n", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gps) GetData() Data {
|
||||||
|
return g.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gps) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gps) switchGpsMode(on bool) error {
|
||||||
|
onStr := "0"
|
||||||
|
if on {
|
||||||
|
onStr = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset input
|
||||||
|
if err := g.port.SerialPort().ResetInputBuffer(); err != nil {
|
||||||
|
return fmt.Errorf("reset input buffer: %w", err)
|
||||||
|
}
|
||||||
|
// Reset output
|
||||||
|
if err := g.port.SerialPort().ResetOutputBuffer(); err != nil {
|
||||||
|
return fmt.Errorf("reset output buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check gps mode status
|
||||||
|
resp, err := g.port.Send("AT+CGPS?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !resp.Check() || !resp.CheckFront("+CGPS:") {
|
||||||
|
return fmt.Errorf("get GPS mode: error response: %s", resp)
|
||||||
|
}
|
||||||
|
ans := strings.Replace(strings.Split(strings.Split(resp.RmFront("+CGPS:").String(), "\n")[0], ",")[0], " ", "", -1)
|
||||||
|
if ans == onStr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modem is not in GPS mode
|
||||||
|
resp, err = g.port.Send("AT+CGPS=" + onStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !resp.Check() {
|
||||||
|
return fmt.Errorf("set GPS mode: error response: %s", resp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
149
api/modem/gps/nmea.go
Normal file
149
api/modem/gps/nmea.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package gps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nmeaFlags int
|
||||||
|
|
||||||
|
const (
|
||||||
|
gga nmeaFlags = 1 << iota // global positioning systemfix data
|
||||||
|
rmc // recommended minimumspecific GPS/TRANSIT data
|
||||||
|
gpgsv // GPS satellites in view
|
||||||
|
gpgsa // GPS DOP and active satellites
|
||||||
|
vtg // track made good and ground speed
|
||||||
|
xfi // Global Positioning SystemExtended FixData.)Bit 6 – GLGSV (GLONASS satellites in view GLONASSfixesonly
|
||||||
|
glgsa // 1. GPS/2. Glonass/3. GALILE DOPandActiveSatellites.
|
||||||
|
gns // fix data for GNSS receivers; output for GPS, GLONASS, GALILEO
|
||||||
|
_ // Reserved
|
||||||
|
gagsv // GALILEO satellites in view
|
||||||
|
_ // Reserved
|
||||||
|
_ // Reserved
|
||||||
|
_ // Reserved
|
||||||
|
_ // Reserved
|
||||||
|
_ // Reserved
|
||||||
|
bdpqgsa // BEIDOU/QZSS DOP and activesatellites
|
||||||
|
bdpqgsv // BEIDOUQZSS satellites in view
|
||||||
|
|
||||||
|
nmeaFlagsMask = (1 << 18) - 1
|
||||||
|
|
||||||
|
collectTimeout = 1 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func secondCountDownTimer(title string, logger *log.Logger, t time.Duration) {
|
||||||
|
counter := 0
|
||||||
|
for {
|
||||||
|
if counter > int(t.Seconds()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logger.Printf("%s: %d/%f\n", title, counter, t.Seconds())
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
counter += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go... otherwise will throw warning
|
||||||
|
var nmeaFlagsAll = gga | rmc | gpgsv | gpgsa | vtg | xfi | glgsa | gns | gagsv | bdpqgsa | bdpqgsv
|
||||||
|
|
||||||
|
func (g *gps) rawCollect(flags nmeaFlags) (string, error) {
|
||||||
|
// Need to omplement low level write/read here because collect must be atomic operation
|
||||||
|
// If other command is executed(write/read) it will read a part of nmea report and cause an error
|
||||||
|
|
||||||
|
// Setup gps
|
||||||
|
|
||||||
|
// Set output rate to 10Hz
|
||||||
|
if resp, err := g.port.Send("AT+CGPSNMEARATE=1"); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
if !resp.Check() {
|
||||||
|
return "", fmt.Errorf("set output rate: error response: %s", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.switchGpsMode(true)
|
||||||
|
g.port.Mutex().Lock()
|
||||||
|
s := g.port.SerialPort()
|
||||||
|
defer func() {
|
||||||
|
g.port.Mutex().Unlock()
|
||||||
|
g.switchGpsMode(false)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Send AT+CGPSINFOCFG=255, flags
|
||||||
|
flags &= nmeaFlagsMask
|
||||||
|
if _, err := s.Write([]byte("AT+CGPSINFOCFG=1,31\r\n")); err != nil {
|
||||||
|
return "", fmt.Errorf("serial port write 1: %w", err)
|
||||||
|
}
|
||||||
|
// Do I need to read answer
|
||||||
|
|
||||||
|
// Wait
|
||||||
|
secondCountDownTimer("Collecting NMEA data", g.logger, collectTimeout)
|
||||||
|
|
||||||
|
// Send AT+CGPSINFOCFG=0, flags
|
||||||
|
if _, err := s.Write([]byte("AT+CGPSINFOCFG=0,31\r\n")); err != nil {
|
||||||
|
return "", fmt.Errorf("serial port write 2: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := s.Write([]byte("AT+CGPSFTM=0\r\n")); err != nil { // For sure because sometimes it cannot stop...
|
||||||
|
return "", fmt.Errorf("serial port write 2: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond) // To enshure
|
||||||
|
|
||||||
|
// Read
|
||||||
|
outBuf := make([]byte, 0)
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
|
||||||
|
readLoop:
|
||||||
|
for {
|
||||||
|
n, err := s.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return string(outBuf), fmt.Errorf("serial port read: %w", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
break readLoop
|
||||||
|
}
|
||||||
|
outBuf = append(outBuf, buf[:n]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(outBuf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gps) collectNmeaReports(flags nmeaFlags) ([]string, error) {
|
||||||
|
// Raw collect
|
||||||
|
resp, err := g.rawCollect(flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("raw collect: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
// g.logger.Println("NMEA raw collect:", resp)
|
||||||
|
|
||||||
|
// Right responce struct:
|
||||||
|
// \r\n
|
||||||
|
// OK
|
||||||
|
// \r\n
|
||||||
|
// (NMEA sentence)...
|
||||||
|
// \r\n
|
||||||
|
// OK
|
||||||
|
// \r\n
|
||||||
|
strs := strings.Split(strings.Replace(resp, "\r", "", -1), "\n")
|
||||||
|
|
||||||
|
// Check
|
||||||
|
// Now wait for:
|
||||||
|
// OK
|
||||||
|
// (NMEA sentence)...
|
||||||
|
// OK
|
||||||
|
// if len(strs) < 2 {
|
||||||
|
// return nil, fmt.Errorf("responce too few rows: %d", len(strs))
|
||||||
|
// }
|
||||||
|
// if !(strs[0] == "OK" && strs[len(strs)-1] == "OK") {
|
||||||
|
// return nil, fmt.Errorf("not OK responce: [% s]", strs)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// This... response is not stable
|
||||||
|
// Every time it gives one or two OK and in ramdom order
|
||||||
|
// So I will not check gor it
|
||||||
|
return strs, nil
|
||||||
|
}
|
151
api/modem/gps/status.go
Normal file
151
api/modem/gps/status.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package gps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
GotResponses bool `json:"gotResponses"`
|
||||||
|
IsValidData bool `json:"isValidData"`
|
||||||
|
FoundSatelitesCount int `json:"foundSatelitesCount"`
|
||||||
|
ActiveSatelitesCount int `json:"activeSatelitesCount"`
|
||||||
|
Rms float32 `json:"rms"` // Root mean square
|
||||||
|
}
|
||||||
|
|
||||||
|
var StatusNil = Status{
|
||||||
|
GotResponses: false,
|
||||||
|
IsValidData: false,
|
||||||
|
FoundSatelitesCount: 0,
|
||||||
|
ActiveSatelitesCount: 0,
|
||||||
|
Rms: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gps) GetStatus() (Status, error) {
|
||||||
|
// Provides more information about signal and possible problems using NMEA reports
|
||||||
|
|
||||||
|
// Collect reports
|
||||||
|
reports, err := g.collectNmeaReports(nmeaFlagsAll) // Now minimum
|
||||||
|
if err != nil {
|
||||||
|
return StatusNil, fmt.Errorf("collect nmea reports: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annalise
|
||||||
|
st := Status{}
|
||||||
|
|
||||||
|
checkLoop:
|
||||||
|
for _, s := range reports {
|
||||||
|
// Check for NMEA format
|
||||||
|
if len(s) < 1 || s[0] != '$' {
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
st.GotResponses = true
|
||||||
|
|
||||||
|
// g.logger.Println("NMEA check:", s)
|
||||||
|
values := strings.Split(s, ",")
|
||||||
|
if len(values[0]) != 6 {
|
||||||
|
return StatusNil, fmt.Errorf("nmea invalid sentence: %s", s)
|
||||||
|
}
|
||||||
|
switch values[0][3:] { // Switch by content
|
||||||
|
case "GSV": // Any satelites
|
||||||
|
// g.logger.Println("check GSV")
|
||||||
|
// Check len
|
||||||
|
if len(values) < 17 {
|
||||||
|
g.logger.Println("GSV too small values")
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
// 0 - msg type
|
||||||
|
// 1 - number of msgs
|
||||||
|
// 2 - index of this msg
|
||||||
|
// 3 - number of visible satelites
|
||||||
|
// 4: - other data
|
||||||
|
|
||||||
|
// Msg index
|
||||||
|
index, err := strconv.Atoi(values[3])
|
||||||
|
if err != nil {
|
||||||
|
g.logger.Println("GSV too small values")
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
_ = index
|
||||||
|
// if index != 0 {
|
||||||
|
// g.logger.Println("discard not first GSV msg")
|
||||||
|
// continue checkLoop
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Count
|
||||||
|
satCount, err := strconv.Atoi(values[4])
|
||||||
|
if err != nil {
|
||||||
|
g.logger.Println("GSV too small values")
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
st.FoundSatelitesCount = satCount
|
||||||
|
case "GSA": // Active satelites
|
||||||
|
// g.logger.Println("check GSA")
|
||||||
|
|
||||||
|
// Check len
|
||||||
|
if len(values) < 17 {
|
||||||
|
g.logger.Println("GSV too small values")
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
// 0 - msg type
|
||||||
|
// 1 - mode of selecting format
|
||||||
|
// 2 - mode of selected format
|
||||||
|
// 3:15 - IDs of active satelites
|
||||||
|
// 15: - other data
|
||||||
|
|
||||||
|
// Counting active satelites
|
||||||
|
count := 0
|
||||||
|
for _, v := range values[3:15] {
|
||||||
|
if _, err := strconv.Atoi(v); err == nil {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st.ActiveSatelitesCount = max(st.ActiveSatelitesCount, count)
|
||||||
|
case "RMC": // Minimum GPS data
|
||||||
|
// g.logger.Println("check RMC")
|
||||||
|
// Check len
|
||||||
|
if len(values) < 12 {
|
||||||
|
g.logger.Println("RMC too small values")
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
// 0 - msg type
|
||||||
|
// 1 - time
|
||||||
|
// 2 - is data valid or not
|
||||||
|
// 3: - other data
|
||||||
|
|
||||||
|
// Is valid value
|
||||||
|
if values[2] == "A" {
|
||||||
|
st.IsValidData = true
|
||||||
|
}
|
||||||
|
case "GST":
|
||||||
|
// g.logger.Println("check GST")
|
||||||
|
// Check len
|
||||||
|
if len(values) < 8 {
|
||||||
|
g.logger.Println("GST too small values")
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
// 0 - msg type
|
||||||
|
// 1 - time
|
||||||
|
// 2 - Root Mean Square
|
||||||
|
// 3: - other data
|
||||||
|
|
||||||
|
rms, err := strconv.ParseFloat(values[2], 32)
|
||||||
|
if err != nil {
|
||||||
|
g.logger.Println("RMS decode:", err.Error())
|
||||||
|
continue checkLoop
|
||||||
|
}
|
||||||
|
st.Rms = float32(rms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return st, nil
|
||||||
|
}
|
@ -4,48 +4,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/CGSG-2021-AE4/modem-test/api/modem/at"
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||||
)
|
)
|
||||||
|
|
||||||
var apns = map[string]string{
|
|
||||||
"Tinkoff": "m.tinkoff",
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
`
|
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
port at.Port
|
port at.Port
|
||||||
|
|
||||||
|
pppPort string
|
||||||
|
|
||||||
|
isConnectExecuted bool
|
||||||
|
isInited bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
Init() error
|
Init(pppPort string) error
|
||||||
ConfigurePPP() error
|
|
||||||
Ping() bool // Is connected
|
Connect() error
|
||||||
|
Disconnect() error
|
||||||
|
|
||||||
|
IsConnected() bool // Check interface existance
|
||||||
|
Ping() error
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,189 +36,120 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) checkPackageExist(pname string) bool {
|
func (c *conn) Init(pppPort string) error {
|
||||||
resp, err := exec.Command("apt-mark", "showmanual", pname).Output()
|
c.pppPort = pppPort
|
||||||
c.logger.Println("CHECK:", resp)
|
// Setup only setup
|
||||||
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("SIM card is not inserted")
|
|
||||||
}
|
|
||||||
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("failed to check SIM provider")
|
|
||||||
}
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
// Connect
|
c.isInited = true
|
||||||
c.logger.Println("Connect...")
|
|
||||||
if err := c.connect(); err != nil {
|
|
||||||
return fmt.Errorf("connect: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//DEBUG
|
|
||||||
resp, err := exec.Command("ifconfig").Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("execute ifconfig cmd: %w", err)
|
|
||||||
}
|
|
||||||
c.logger.Println("DEBUG ifconfig resp:", string(resp))
|
|
||||||
|
|
||||||
// Test connectin using Ping
|
|
||||||
c.logger.Println("Test...")
|
|
||||||
if !c.Ping() {
|
|
||||||
return fmt.Errorf("ping failed")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) Ping() bool {
|
func (c *conn) Connect() error {
|
||||||
|
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
|
||||||
|
// 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()
|
||||||
|
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
|
||||||
|
|
||||||
|
// Set default route
|
||||||
|
|
||||||
|
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
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Ping() error {
|
||||||
// Test - try to connect to Google DNS
|
// Test - try to connect to Google DNS
|
||||||
// ping -I ppp0 8.8.8.8
|
// ping -I ppp0 8.8.8.8
|
||||||
resp, err := exec.Command("ping", "8.8.8.8").Output()
|
resp, err := exec.Command("ping", "8.8.8.8").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Println("Ping 1 cmd error:", err)
|
c.logger.Println("Ping default interface cmd error:", err)
|
||||||
}
|
}
|
||||||
c.logger.Println("Ping 1 resp:", string(resp))
|
c.logger.Println("Ping default interface resp:", string(resp))
|
||||||
|
|
||||||
resp, err = exec.Command("ping", "-I", "ppp0", "8.8.8.8").Output()
|
resp, err = exec.Command("ping", "-I", "ppp0", "8.8.8.8").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Println("Ping 2 cmd error:", err)
|
c.logger.Println("Ping ppp0 interface cmd error:", err)
|
||||||
}
|
}
|
||||||
c.logger.Println("Ping 2 resp:", string(resp))
|
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
|
||||||
|
}
|
||||||
|
|
||||||
return !strings.Contains(string(resp), "Destination Host Unreachable") // tmp solution
|
func (c *conn) IsConnected() bool {
|
||||||
|
if !c.isConnectExecuted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Make "ifconfig" request
|
||||||
|
resp, err := exec.Command("ifconfig").Output()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Println("ifconfig cmd error:", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(resp), "\n")
|
||||||
|
for _, l := range lines {
|
||||||
|
if len(l) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if l[0] == ' ' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
interfaceName := strings.Split(l, ":")[0]
|
||||||
|
if interfaceName == "ppp0" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false // Did not found
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) Close() error {
|
func (c *conn) Close() error {
|
||||||
if err := c.diconnect(); err != nil {
|
c.isInited = false
|
||||||
|
if err := c.Disconnect(); err != nil {
|
||||||
return fmt.Errorf("diconnect: %w", err)
|
return fmt.Errorf("diconnect: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
131
api/modem/internet/setup.go
Normal file
131
api/modem/internet/setup.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -6,55 +6,67 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/CGSG-2021-AE4/modem-test/api/modem/at"
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||||
"github.com/CGSG-2021-AE4/modem-test/api/modem/gpio"
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gpio"
|
||||||
"github.com/CGSG-2021-AE4/modem-test/api/modem/internet"
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
|
||||||
"github.com/CGSG-2021-AE4/modem-test/api/modem/sms"
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/internet"
|
||||||
|
"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"
|
||||||
|
|
||||||
type ModemData struct {
|
type ModemData struct {
|
||||||
Port string `json:"Port"`
|
Port string `json:"Port"`
|
||||||
GpsData
|
gps.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
type modem struct {
|
type modem struct {
|
||||||
// Internal values
|
// Internal values
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
mutex sync.Mutex
|
||||||
|
|
||||||
// Serial values
|
// Serial values
|
||||||
baudrate int
|
baudrate int
|
||||||
|
|
||||||
deviceName string
|
deviceName string
|
||||||
port at.Port
|
port at.Port
|
||||||
|
|
||||||
// Gpio values
|
// Gpio values
|
||||||
onOffPin gpio.Pin
|
onOffPin gpio.Pin // For turning on and off
|
||||||
|
|
||||||
// Other values
|
// Other values
|
||||||
lastUpdateTime time.Time
|
lastUpdateTime time.Time
|
||||||
|
|
||||||
// GPS
|
// GPS
|
||||||
gpsInfo GpsData
|
gps gps.Gps
|
||||||
|
|
||||||
// Internet connection
|
// Internet connection
|
||||||
ic internet.Conn
|
ic internet.Conn
|
||||||
|
|
||||||
// Sms and calls
|
// Sms and calls
|
||||||
sms sms.Dialer
|
sms sms.Sms
|
||||||
}
|
}
|
||||||
|
|
||||||
type Modem interface {
|
type Modem interface {
|
||||||
Init() error
|
Init() error
|
||||||
Validate() bool
|
IsConnected() bool
|
||||||
Update() error
|
Update() error
|
||||||
GetInfo() ModemData
|
GetData() ModemData
|
||||||
|
GetTime() (time.Time, error)
|
||||||
|
|
||||||
// Temp access to SMS and AT interface
|
PowerOn() error
|
||||||
Sms() sms.Dialer
|
PowerOff() error
|
||||||
At() at.Port
|
|
||||||
|
// Access to SMS, GPS, AT interfaces mostly for debug
|
||||||
|
At() at.Port // Send
|
||||||
|
Gps() gps.Gps // Update, GetData, GetStatus
|
||||||
|
Sms() sms.Sms // Send, ReadNew
|
||||||
|
Ic() internet.Conn // Connect, Disconnect
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
@ -64,139 +76,213 @@ func New(logger *log.Logger) Modem {
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
baudrate: 115200,
|
baudrate: 115200,
|
||||||
onOffPin: gpio.New(log.New(logger.Writer(), "gpio", log.LstdFlags), 6),
|
onOffPin: gpio.New(log.New(logger.Writer(), "gpio", log.LstdFlags), 6),
|
||||||
|
|
||||||
lastUpdateTime: time.Now(),
|
lastUpdateTime: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) Init() error {
|
func (m *modem) Init() error {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
// Turn module on
|
// Turn module on
|
||||||
m.logger.Println("=============================== Turn on module")
|
m.logger.Println("=============================== Init gpio")
|
||||||
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)
|
||||||
}
|
}
|
||||||
// m.onOffPin.PowerOn()
|
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
m.logger.Println("=============================== Search")
|
m.logger.Println("=============================== Search")
|
||||||
// Soft search
|
ports, err := m.searchPort(true)
|
||||||
if err := m.searchPort(true); err != nil {
|
if 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
|
||||||
if m.port == nil {
|
ports, err := m.searchPort(true)
|
||||||
if err := m.searchPort(false); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("not soft port search: %w", err)
|
return fmt.Errorf("wide port search: %w", err)
|
||||||
|
}
|
||||||
|
if len(ports) == 0 {
|
||||||
|
return fmt.Errorf("no AT ports found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if m.port == nil {
|
if len(ports) == 1 {
|
||||||
return fmt.Errorf("no port is detected")
|
// return fmt.Errorf("only one AT port found")
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests
|
// Init submodules
|
||||||
m.logger.Println("=============================== Test")
|
submodulesLogger := m.logger.Writer() // FOR more logs
|
||||||
if err := m.testGPS(); err != nil {
|
// submodulesLogger := io.Discard // FOR less logs
|
||||||
return fmt.Errorf("testGPS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Establish internet connection
|
m.logger.Println("=============================== Init submodules")
|
||||||
// m.logger.Println("=============================== Internet")
|
// m.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port)
|
||||||
// m.ic = internet.New(log.New(m.logger.Writer(), "internet", log.LstdFlags), m.port)
|
// if err := m.ic.Init(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())
|
||||||
// return fmt.Errorf("internet connection init: %w", err)
|
// } else {
|
||||||
|
// m.logger.Println("\x1b[38;2;0;255;0mInternet OK\x1b[38;2;255;255;255m")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Init sms dialer
|
// m.sms = sms.New(log.New(submodulesLogger, "modem-sms : ", log.LstdFlags), m.port)
|
||||||
m.sms = sms.New(log.New(m.logger.Writer(), "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())
|
||||||
return fmt.Errorf("sms dialer init %w", err)
|
// } else {
|
||||||
}
|
// m.logger.Println("\x1b[38;2;0;255;0mSMS OK\x1b[38;2;255;255;255m")
|
||||||
return nil
|
// }
|
||||||
|
|
||||||
|
m.gps = gps.New(log.New(submodulesLogger, "modem-gps : ", log.LstdFlags), m.port)
|
||||||
|
if err := m.gps.Init(); err != nil {
|
||||||
|
m.logger.Printf("\x1b[38;2;255;0;0mgps init %w\x1b[38;2;255;255;255m\n", err)
|
||||||
|
} else {
|
||||||
|
m.logger.Println("\x1b[38;2;0;255;0mGPS OK\x1b[38;2;255;255;255m")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) Validate() bool {
|
// Tests
|
||||||
return m.isConnected()
|
// GPS works fine but almost always there is no signal
|
||||||
|
// m.logger.Println("=============================== Test")
|
||||||
|
// if err := m.testGPS(); err != nil {
|
||||||
|
// return fmt.Errorf("testGPS: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) Update() error {
|
func (m *modem) Update() error {
|
||||||
if !m.isConnected() {
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
if !m.IsConnected() {
|
||||||
m.logger.Println("No connection to module")
|
m.logger.Println("No connection to module")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.logger.Println("Update")
|
m.logger.Println("Update", m.gps)
|
||||||
// ans, err := m.port.Request(at.CmdQuestion, "CGPSINFO")
|
if err := m.gps.Update(); err != nil {
|
||||||
// if err != nil {
|
m.logger.Println("gps update:", err.Error())
|
||||||
// return fmt.Errorf("check GPS info mode: %w", err)
|
}
|
||||||
// }
|
// Read new messages
|
||||||
// ok := ans == "1"
|
|
||||||
// if !ok {
|
|
||||||
// _, err := m.port.Request(at.CmdCheck, "CGPSINFO")
|
|
||||||
// if err != nil {
|
|
||||||
// return fmt.Errorf("switch to GPS info mode: %w", err)
|
|
||||||
// }
|
|
||||||
// m.logger.Println("switched to GPS mode")
|
|
||||||
// } else {
|
|
||||||
// m.logger.Println("mode in right GPS mode")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Update
|
|
||||||
m.logger.Println("Receiving GPS data...")
|
|
||||||
resp, err := m.port.Send("AT+CGPSINFO")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("receive GPS data: %w", err)
|
|
||||||
}
|
|
||||||
if !resp.Check() {
|
|
||||||
return fmt.Errorf("error response")
|
|
||||||
}
|
|
||||||
m.logger.Println("Decoding data...")
|
|
||||||
|
|
||||||
if err := m.gpsInfo.decode(strings.Split(strings.Replace(resp.RmFront("+CGPSINFO:").String(), "\r", "", -1), "\n")[0]); err != nil {
|
|
||||||
m.logger.Println("Gps info decode error:", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.logger.Println("Decoded successfully")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) GetInfo() ModemData {
|
func (m *modem) GetData() ModemData {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
return ModemData{
|
return ModemData{
|
||||||
Port: m.port.GetName(),
|
Port: m.port.GetName(),
|
||||||
GpsData: m.gpsInfo,
|
Data: m.gps.GetData(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) Sms() sms.Dialer {
|
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]
|
||||||
|
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 {
|
||||||
|
m.onOffPin.PowerOn() // DEBUG do not want to wait 30 seconds
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modem) PowerOff() error {
|
||||||
|
_, err := m.At().Send("AT+CPOF")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modem) restart() error {
|
||||||
|
m.PowerOff()
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
m.PowerOn()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modem) Sms() sms.Sms {
|
||||||
return m.sms
|
return m.sms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *modem) Gps() gps.Gps {
|
||||||
|
return m.gps
|
||||||
|
}
|
||||||
|
|
||||||
func (m *modem) At() at.Port {
|
func (m *modem) At() at.Port {
|
||||||
return m.port
|
return m.port
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) Close() error {
|
func (m *modem) Ic() internet.Conn {
|
||||||
|
return m.ic
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modem) Close() error { // I can't return error so I log it
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
// Close submodules
|
||||||
|
if m.sms != nil {
|
||||||
if err := m.sms.Close(); err != nil {
|
if err := m.sms.Close(); err != nil {
|
||||||
return fmt.Errorf("sms: %w", err)
|
m.logger.Printf("\x1b[38;2;255;0;0mclose sms error: %s\x1b[38;2;255;255;255m\n", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not right way I think
|
|
||||||
if err := m.port.Close(); err != nil {
|
|
||||||
return fmt.Errorf("serial port: %w", err)
|
|
||||||
}
|
|
||||||
if err := m.onOffPin.Close(); err != nil {
|
|
||||||
return fmt.Errorf("gpio pin: %w", err)
|
|
||||||
}
|
}
|
||||||
|
if m.ic != nil {
|
||||||
if err := m.ic.Close(); err != nil {
|
if err := m.ic.Close(); err != nil {
|
||||||
return fmt.Errorf("internet connection: %w", err)
|
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 {
|
||||||
|
m.logger.Printf("\x1b[38;2;255;0;0mclose serial port error: %s\x1b[38;2;255;255;255m\n", err.Error())
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////// Private functions
|
||||||
|
|
||||||
func (m *modem) connect() error {
|
func (m *modem) connect() error {
|
||||||
if m.port == nil {
|
if m.port == nil {
|
||||||
return fmt.Errorf("port is not defined")
|
return fmt.Errorf("port is not defined")
|
||||||
@ -211,7 +297,7 @@ func (m *modem) disconnect() error {
|
|||||||
return m.port.Disconnect()
|
return m.port.Disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) isConnected() bool {
|
func (m *modem) IsConnected() bool {
|
||||||
if m.port != nil {
|
if m.port != nil {
|
||||||
return m.port.IsConnected()
|
return m.port.IsConnected()
|
||||||
}
|
}
|
||||||
@ -231,7 +317,8 @@ func (m *modem) testGPS() error {
|
|||||||
|
|
||||||
// Difference: I do not set \n at the end of string
|
// Difference: I do not set \n at the end of string
|
||||||
func (m *modem) getShortInfo() string {
|
func (m *modem) getShortInfo() string {
|
||||||
return fmt.Sprintf("%f,%s,%f,%s", m.gpsInfo.Latitude, m.gpsInfo.LatitudeIndicator, m.gpsInfo.Longitude, m.gpsInfo.LongitudeIndicator)
|
d := m.gps.GetData()
|
||||||
|
return fmt.Sprintf("%f,%s,%f,%s", d.Latitude, d.LatitudeIndicator, d.Longitude, d.LongitudeIndicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) saveGPS(path string) error {
|
func (m *modem) saveGPS(path string) error {
|
||||||
@ -247,20 +334,45 @@ func (m *modem) saveGPS(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) testConsole() {
|
// Short way to send command
|
||||||
for {
|
func (m *modem) printCmd(cmd string) {
|
||||||
var inStr string
|
if resp, err := m.port.Send(cmd); err != nil {
|
||||||
fmt.Scanln(&inStr)
|
m.logger.Println("FAILED TO SEND REQ", cmd, "===>", err.Error())
|
||||||
if inStr == "exit" {
|
} else {
|
||||||
return
|
_ = resp
|
||||||
|
// m.logger.Println("CMD", cmd, "===>", resp)
|
||||||
}
|
}
|
||||||
resp, err := m.port.Send(inStr)
|
}
|
||||||
|
|
||||||
|
// Some required commands before checking port
|
||||||
|
func (m *modem) setupPort() error {
|
||||||
|
// Reset input
|
||||||
|
if err := m.port.SerialPort().ResetInputBuffer(); err != nil {
|
||||||
|
return fmt.Errorf("reset input buffer: %w", err)
|
||||||
|
}
|
||||||
|
// Reset output
|
||||||
|
if err := m.port.SerialPort().ResetOutputBuffer(); err != nil {
|
||||||
|
return fmt.Errorf("reset output buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// m.restart()
|
||||||
|
// These commands ensure that correct modes are set
|
||||||
|
// m.port.RawSend("\r\n\x1A\r\n") // Sometimes enables echo mode
|
||||||
|
m.printCmd("ATE0") // Sometimes enables echo mode
|
||||||
|
m.printCmd("AT+CGPSFTM=0") // Sometimes does not turn off nmea
|
||||||
|
m.printCmd("AT+CMEE=2") // Turn on errors describtion
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modem) checkCurPortDead() error {
|
||||||
|
if resp, err := m.port.RawSend("AT\r\n", 20*time.Millisecond); err != nil || len(resp) == 0 {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Println("ERROR:", err.Error())
|
return fmt.Errorf("raw send: %w", err)
|
||||||
}
|
}
|
||||||
m.logger.Println(resp)
|
return fmt.Errorf("read 0")
|
||||||
m.logger.Println("------------------")
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) checkPort(portName string) (outErr error) {
|
func (m *modem) checkPort(portName string) (outErr error) {
|
||||||
@ -283,16 +395,17 @@ 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...
|
||||||
|
|
||||||
// Reset input
|
// m.restart()
|
||||||
if err := m.port.GetSerialPort().ResetInputBuffer(); err != nil {
|
|
||||||
return fmt.Errorf("reset input buffer: %w", err)
|
// To filter dead ports
|
||||||
|
|
||||||
|
if err := m.checkCurPortDead(); err != nil {
|
||||||
|
return fmt.Errorf("echo: %w", err)
|
||||||
}
|
}
|
||||||
// Reset output
|
|
||||||
if err := m.port.GetSerialPort().ResetOutputBuffer(); err != nil {
|
if err := m.setupPort(); err != nil {
|
||||||
return fmt.Errorf("reset output buffer: %w", err)
|
return fmt.Errorf("setup port: %w", err)
|
||||||
}
|
}
|
||||||
m.port.Send("ATE0") // This shit sometimes enables echo mode... why... when... but it can
|
|
||||||
// m.port.Send("\r\n")
|
|
||||||
|
|
||||||
// Ping
|
// Ping
|
||||||
m.logger.Println("Ping...")
|
m.logger.Println("Ping...")
|
||||||
@ -308,7 +421,7 @@ func (m *modem) checkPort(portName string) (outErr error) {
|
|||||||
return fmt.Errorf("get model: %w", err)
|
return fmt.Errorf("get model: %w", err)
|
||||||
}
|
}
|
||||||
if !resp.Check() {
|
if !resp.Check() {
|
||||||
return fmt.Errorf("error response: %s", resp)
|
return fmt.Errorf("get model: error response: %s", resp)
|
||||||
}
|
}
|
||||||
model := strings.Split(resp.String(), "\n")[0]
|
model := strings.Split(resp.String(), "\n")[0]
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -323,46 +436,47 @@ func (m *modem) checkPort(portName string) (outErr error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) searchPort(isSoft bool) error {
|
func (m *modem) ping() error {
|
||||||
// Get ports
|
resp, err := m.port.Send("AT")
|
||||||
ports := []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}
|
|
||||||
if !isSoft {
|
|
||||||
ps, err := getTtyDevices()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Errorf("get serial devices: %w", err)
|
return fmt.Errorf("AT request: %w", err)
|
||||||
}
|
}
|
||||||
ports = ps
|
if !resp.Check() {
|
||||||
|
return fmt.Errorf("AT request: error response: %s", resp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *modem) searchPort(isSoft bool) ([]string, error) {
|
||||||
|
// Get ports
|
||||||
|
ports, err := getTtyPorts(isSoft)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Errorf("get devices: %w", err)
|
||||||
|
}
|
||||||
// Check ports
|
// Check ports
|
||||||
SearchLoop:
|
return m.getAtPorts(ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modem) getAtPorts(ports []string) ([]string, error) {
|
||||||
|
outPorts := make([]string, 0)
|
||||||
for _, p := range ports {
|
for _, p := range ports {
|
||||||
m.logger.Printf("Checking port %s ...\n", p)
|
m.logger.Printf("Checking port %s ...\n", p)
|
||||||
|
|
||||||
if err := m.checkPort("/dev/" + p); err != nil {
|
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())
|
m.logger.Printf("\x1b[38;2;255;0;0mCheck failed: %s\x1b[38;2;255;255;255m\n", err.Error())
|
||||||
continue SearchLoop
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
m.logger.Print("Found modem on port: ", p)
|
m.logger.Print("Found AT port: ", p)
|
||||||
m.port = at.New(m.logger, "/dev/"+p, m.baudrate)
|
outPorts = append(outPorts, "/dev/"+p)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return nil
|
return outPorts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modem) ping() error {
|
func getTtyPorts(isSoft bool) ([]string, error) {
|
||||||
resp, err := m.port.Send("AT")
|
if isSoft {
|
||||||
if err != nil {
|
return []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}, nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if !resp.Check() {
|
|
||||||
return fmt.Errorf("connection lost")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTtyDevices() ([]string, error) {
|
|
||||||
// Get ports
|
// Get ports
|
||||||
/**/
|
/**/
|
||||||
out, err := exec.Command("ls", "--", "/dev/tty[!0-9]*").Output()
|
out, err := exec.Command("ls", "--", "/dev/tty[!0-9]*").Output()
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
88
api/modem/sms/setup.go
Normal file
88
api/modem/sms/setup.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -5,8 +5,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/CGSG-2021-AE4/modem-test/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 {
|
||||||
@ -14,14 +16,15 @@ type dialer struct {
|
|||||||
port at.Port
|
port at.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialer interface {
|
type Sms interface {
|
||||||
Init() error
|
Init() error
|
||||||
Send(number, msg string) error
|
Send(number, msg string) error // Send sms
|
||||||
ReadNew() ([]string, error)
|
ReadNew() ([]string, error) // Read new smses
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(logger *log.Logger, port at.Port) Dialer {
|
func New(logger *log.Logger, port at.Port) Sms {
|
||||||
return &dialer{
|
return &dialer{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
port: port,
|
port: port,
|
||||||
@ -29,42 +32,61 @@ func New(logger *log.Logger, port at.Port) Dialer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *dialer) Init() error {
|
func (d *dialer) Init() error {
|
||||||
// Ensure serial port
|
// Ensure serial port is connected
|
||||||
if !d.port.IsConnected() {
|
if !d.port.IsConnected() {
|
||||||
return fmt.Errorf("serial port is not connected")
|
return fmt.Errorf("serial port is not connected")
|
||||||
}
|
}
|
||||||
// Ensure text format
|
// Check ping
|
||||||
if resp, err := d.port.Send("AT+CMGF=1"); err != nil || !resp.Check() {
|
if err := utils.CheckPIN(d.port, d.logger); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("check PIN: %w", err)
|
||||||
return fmt.Errorf("AT+CMGF=1 request: %w", err)
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed to set SMS format")
|
// 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
|
||||||
|
if resp, err := d.port.Send("AT+CNMI=2,2"); err != nil || !resp.Check() {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("CNMI= error response: %s", resp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check number
|
||||||
|
if resp, err := d.port.Send("AT+CNUM"); err != nil || !resp.Check() {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("CNUM error response: %s", resp.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dialer) Send(number, msg string) error {
|
func (d *dialer) Send(number, msg string) error {
|
||||||
d.port.Send(fmt.Sprintf("AT+CMGS=\"%s\"", number)) // Because it will throw error
|
sresp, err := d.port.Send(fmt.Sprintf(`AT+CMGSEX="%s"`, number)) // Because it will throw error
|
||||||
resp, err := d.port.RawSend(fmt.Sprintf("%s\x1A", msg)) // Add additional \r\n because there is not supposed to be
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.logger.Println(sresp)
|
||||||
|
resp, err := d.port.RawSend(fmt.Sprintf("%s\x1A", msg), time.Millisecond) // Add additional \r\n because there is not supposed to be
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("message request: %w", err)
|
return fmt.Errorf("message request: %w", err)
|
||||||
}
|
}
|
||||||
if at.Resp(resp).Check() {
|
d.logger.Println("Send response:", resp)
|
||||||
|
if !at.Resp(resp).Check() {
|
||||||
|
return fmt.Errorf("error response: %s", resp)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errCode, err := GetError([]byte(resp))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("send sms failed and failed to get error: %w", err)
|
|
||||||
}
|
|
||||||
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")
|
resp, err := d.port.Send("AT+CMGL=\"ALL\"")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("AT+CMGL request: %w", err)
|
return nil, 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)
|
||||||
|
37
api/modem/utils/service.go
Normal file
37
api/modem/utils/service.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
|
}
|
109
api/modem/utils/signal.go
Normal file
109
api/modem/utils/signal.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
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 strenght 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
|
||||||
|
}
|
26
api/modem/utils/sim.go
Normal file
26
api/modem/utils/sim.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -1,4 +1,4 @@
|
|||||||
module github.com/CGSG-2021-AE4/modem-test
|
module gitea.unprism.ru/KRBL/sim-modem
|
||||||
|
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
|
156
main.go
156
main.go
@ -1,43 +1,165 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/CGSG-2021-AE4/modem-test/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")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainE() error {
|
func mainE(ctx context.Context) error {
|
||||||
m := modem.New(log.New(os.Stdout, "modem:", log.LstdFlags))
|
logger := log.New(os.Stdout, "main : ", log.LstdFlags)
|
||||||
log.Println("||||||||||||||||| INIT |||||||||||||||")
|
m := modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
|
||||||
|
|
||||||
|
logger.Println("||||||||||||||||| INIT |||||||||||||||")
|
||||||
|
initLoop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
logger.Println("Break init loop")
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
if err := m.Init(); err != nil {
|
if err := m.Init(); err != nil {
|
||||||
return err
|
logger.Println("Init ended with error:", err.Error())
|
||||||
|
// logger.Println("Turn on...")
|
||||||
|
// if err := m.PowerOn(); err != nil {
|
||||||
|
// logger.Println("Turn on error:", err.Error())
|
||||||
|
// }
|
||||||
|
continue initLoop
|
||||||
}
|
}
|
||||||
if !m.Validate() {
|
break initLoop
|
||||||
log.Println("AAAAAAAAAAAAAAA Validation failed")
|
}
|
||||||
|
}
|
||||||
|
if !m.IsConnected() {
|
||||||
|
logger.Println("Modem is not connected")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Println("||||||||||||||||| GET INFO |||||||||||||||||")
|
defer func() {
|
||||||
log.Println(m.GetInfo())
|
logger.Println("||||||||||||||||| CLOSE |||||||||||||||")
|
||||||
|
m.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
log.Println("||||||||||||||||| SEND SMS |||||||||||||||||")
|
// Connect to internet
|
||||||
log.Println(m.At().Send("AT+CNUM"))
|
// if err := m.Ic().Connect(); err != nil {
|
||||||
// if err := m.Sms().Send("+79218937173", "CGSG forever"); err != nil {
|
// return fmt.Errorf("connect to internet: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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+CSQ")
|
||||||
|
// 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+CSQ")
|
||||||
|
// Cmd("AT+CCLK?")
|
||||||
|
// logger.Println(m.Gps().GetStatus())
|
||||||
|
// m.Update()
|
||||||
|
// st, _ := m.Gps().GetStatus()
|
||||||
|
// logger.Printf("GPS STATUS: %+v", st)
|
||||||
|
// logger.Printf("GPS DATA: %+v", m.GetData())
|
||||||
|
logger.Println(m.GetTime())
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
// Cmd("AT+CSQ")
|
||||||
|
// Cmd("AT+COPS?")
|
||||||
|
|
||||||
|
// if err := m.CheckSignal(); err != nil {
|
||||||
|
// logger.Println(err)
|
||||||
|
// } else {
|
||||||
|
// logger.Println("AAAAAAAAAAA THERE IS SIGNAL")
|
||||||
|
// }
|
||||||
|
// readLen, err := m.At().SerialPort().Read(buf)
|
||||||
|
// if err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
if ms, err := m.Sms().ReadNew(); err != nil {
|
// if readLen > 0 {
|
||||||
return err
|
// logger.Println(string(buf[:readLen]))
|
||||||
} else {
|
// }
|
||||||
log.Println("NEW:", ms)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user