17 Commits

Author SHA1 Message Date
dde1411b18 Fixed RmFront panic. 2024-08-09 11:21:02 +03:00
6c110b9a8b Add: check in RmFront 2024-08-08 15:56:23 +03:00
cb07b6ac62 Fixed GPS info parsing. 2024-08-08 15:52:52 +03:00
be99ed7fbf Fix: import statements 2024-08-08 13:32:55 +03:00
d625866d92 Fix: import statements 2024-08-08 13:26:33 +03:00
7ee03906f8 Fix: go mod (AL1 told to) 2024-08-08 09:56:32 +00:00
0b76884112 Added RMS. 2024-08-07 17:34:56 +03:00
59bd2c5db3 Improve gps status. 2024-08-07 11:35:06 +03:00
0837d376f7 Added sync. 2024-08-06 21:10:24 +03:00
6a96656434 Added local compilation. Cleaning. 2024-08-06 20:37:20 +03:00
026c1aa3bb Fixed error logging. Added more logging of SIM info. 2024-08-04 15:59:58 +03:00
90a06e6afa Added GPS status. 2024-08-02 19:43:15 +03:00
e2e02ebbfe Fixed NMEA report parsing. 2024-08-02 19:16:39 +03:00
9bc94bfe7c Debugged collecting nmea data. 2024-08-02 18:32:33 +03:00
061ba2a859 Some debug. 2024-08-01 19:34:58 +03:00
6498d20378 Added draft GPS checks though NMEA reports. 2024-07-31 22:18:44 +03:00
b831d44294 Refactored gps. Prepared for adding advanced status checks. 2024-07-31 14:51:29 +03:00
15 changed files with 730 additions and 279 deletions

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
out/
Makefile
go.sum
.git/
*.swp

7
Makefile Normal file
View 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

View File

@ -4,6 +4,7 @@ import (
"fmt"
"io"
"log"
"sync"
"time"
"go.bug.st/serial"
@ -12,12 +13,14 @@ import (
// Some constants
const (
ReadTimeout = time.Second
InputBufSize = 128
InputBufSize = 512
)
type atPort struct {
logger *log.Logger
mutex sync.Mutex // Mutex for all operation with serial port
baudrate int
portName string
port serial.Port
@ -27,7 +30,8 @@ type atPort struct {
type Port interface {
GetName() string
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
Disconnect() error
@ -56,11 +60,18 @@ func (p *atPort) GetBaudrate() int {
return p.baudrate
}
func (p *atPort) GetSerialPort() serial.Port {
func (p *atPort) SerialPort() serial.Port {
return p.port
}
func (p *atPort) Mutex() sync.Locker {
return &p.mutex
}
func (p *atPort) Connect() error {
p.mutex.Lock()
defer p.mutex.Unlock()
p.logger.Println("Connecting to", p.portName, "...")
s, err := serial.Open(p.portName, &serial.Mode{BaudRate: p.baudrate})
if err != nil {
@ -76,8 +87,10 @@ func (p *atPort) Connect() error {
}
func (p *atPort) Disconnect() error {
p.mutex.Lock()
defer func() {
p.port = nil
p.mutex.Unlock()
}()
if err := p.port.Close(); err != nil {
return fmt.Errorf("close port: %w", err)
@ -86,16 +99,22 @@ func (p *atPort) Disconnect() error {
}
func (p *atPort) IsConnected() bool {
p.mutex.Lock()
defer p.mutex.Unlock()
return p.port != nil
}
// Low level write/read function
func (p *atPort) RawSend(msg string) (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)
}
time.Sleep(time.Millisecond)
// time.Sleep(time.Millisecond)
// Read
readLen, err := p.port.Read(p.inputBuf)
// p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(p.inputBuf[:readLen]), "\x1b[38;2;255;255;255m")
@ -121,5 +140,8 @@ func (p *atPort) Send(cmd string) (Resp, error) {
}
func (p *atPort) Close() error {
p.mutex.Lock()
defer p.mutex.Unlock()
return p.port.Close()
}

View File

@ -12,6 +12,10 @@ func (resp Resp) RmFront(str string) Resp {
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 {
return string(resp)
}

View File

@ -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
View 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
View 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 fmt.Errorf("make at ask: %w", 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 fmt.Errorf("set GPS mode: %w", err)
}
if !resp.Check() {
return fmt.Errorf("set GPS mode: error response: %s", resp)
}
return nil
}

149
api/modem/gps/nmea.go Normal file
View 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 "", fmt.Errorf("AT+CGPSNMEARATE= request: %w", 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
View 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
}

View File

@ -8,7 +8,7 @@ import (
"os/exec"
"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{
@ -103,7 +103,7 @@ func (c *conn) checkReqs() error {
return fmt.Errorf("AT+CPIN? request: %w", err)
}
if !resp.Check() {
return fmt.Errorf("SIM card is not inserted")
return fmt.Errorf("validate SIM: error response: %s", resp)
}
return nil
}
@ -115,7 +115,7 @@ func (c *conn) ConfigurePPP() error {
return fmt.Errorf("AT+CSPN? request: %w", err)
}
if !resp.Check() {
return fmt.Errorf("failed to check SIM provider")
return fmt.Errorf("get provider: error response: %s", resp)
}
strs := strings.Split(string(resp), "\"")
if len(strs) < 3 {
@ -132,7 +132,7 @@ func (c *conn) ConfigurePPP() error {
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)
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)
}
@ -230,8 +230,7 @@ func (c *conn) Ping() bool {
}
c.logger.Println("Ping 2 resp:", string(resp))
return !strings.Contains(string(resp), "Destination Host Unreachable") // tmp solution
return !(strings.Contains(string(resp), "Destination Host Unreachable") || strings.Contains(string(resp), "Destination Net Unreachable")) // tmp solution
}
func (c *conn) Close() error {

View File

@ -7,54 +7,60 @@ import (
"os"
"os/exec"
"strings"
"sync"
"time"
"github.com/CGSG-2021-AE4/modem-test/api/modem/at"
"github.com/CGSG-2021-AE4/modem-test/api/modem/gpio"
"github.com/CGSG-2021-AE4/modem-test/api/modem/internet"
"github.com/CGSG-2021-AE4/modem-test/api/modem/sms"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gpio"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/internet"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/sms"
)
type ModemData struct {
Port string `json:"Port"`
GpsData
gps.Data
}
type modem struct {
// Internal values
logger *log.Logger
mutex sync.Mutex
// Serial values
baudrate int
deviceName string
port at.Port
// Gpio values
onOffPin gpio.Pin
onOffPin gpio.Pin // For turning on and off
// Other values
lastUpdateTime time.Time
// GPS
gpsInfo GpsData
gps gps.Gps
// Internet connection
ic internet.Conn
// Sms and calls
sms sms.Dialer
sms sms.Sms
}
type Modem interface {
Init() error
Validate() bool
IsConnected() bool
Update() error
GetInfo() ModemData
GetData() ModemData
// Temp access to SMS and AT interface
Sms() sms.Dialer
At() at.Port
PowerOn() error
PowerOff() error
// Access to SMS, GPS, AT interfaces mostly for debug
At() at.Port // Send
Gps() gps.Gps // Update, GetData, GetStatus
Sms() sms.Sms // Send, ReadNew
io.Closer
}
@ -64,17 +70,20 @@ func New(logger *log.Logger) Modem {
logger: logger,
baudrate: 115200,
onOffPin: gpio.New(log.New(logger.Writer(), "gpio", log.LstdFlags), 6),
lastUpdateTime: time.Now(),
}
}
func (m *modem) Init() error {
m.mutex.Lock()
defer m.mutex.Unlock()
// Turn module on
m.logger.Println("=============================== Turn on module")
if err := m.onOffPin.Init(); err != nil {
return fmt.Errorf("gpio pin init: %w", err)
}
// m.onOffPin.PowerOn()
// Search
m.logger.Println("=============================== Search")
@ -98,105 +107,114 @@ func (m *modem) Init() error {
return fmt.Errorf("connect: %w", err)
}
// Tests
m.logger.Println("=============================== Test")
if err := m.testGPS(); err != nil {
return fmt.Errorf("testGPS: %w", err)
// Init submodules
submodulesLogger := m.logger.Writer() // FOR more logs
// submodulesLogger := io.Discard // FOR less logs
m.logger.Println("=============================== Init submodules")
m.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port)
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())
} else {
m.logger.Println("\x1b[38;2;0;255;0mInternet OK\x1b[38;2;255;255;255m")
}
// // Establish internet connection
// m.logger.Println("=============================== Internet")
// m.ic = internet.New(log.New(m.logger.Writer(), "internet", log.LstdFlags), m.port)
// if err := m.ic.Init(); err != nil {
// return fmt.Errorf("internet connection init: %w", err)
m.sms = sms.New(log.New(submodulesLogger, "modem-sms : ", log.LstdFlags), m.port)
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())
} else {
m.logger.Println("\x1b[38;2;0;255;0mSMS OK\x1b[38;2;255;255;255m")
}
m.gps = gps.New(log.New(submodulesLogger, "modem-gps : ", log.LstdFlags), m.port)
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")
}
// Tests
// 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)
// }
// Init sms dialer
m.sms = sms.New(log.New(m.logger.Writer(), "sms", log.LstdFlags), m.port)
if err := m.sms.Init(); err != nil {
return fmt.Errorf("sms dialer init %w", err)
}
return nil
}
func (m *modem) Validate() bool {
return m.isConnected()
}
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")
return nil
}
m.logger.Println("Update")
// ans, err := m.port.Request(at.CmdQuestion, "CGPSINFO")
// if err != nil {
// return fmt.Errorf("check GPS info mode: %w", err)
// }
// 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")
// }
m.logger.Println("Update", m.gps)
if err := m.gps.Update(); err != nil {
m.logger.Println("gps update:", err.Error())
}
// Read new messages
// 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
}
func (m *modem) GetInfo() ModemData {
func (m *modem) GetData() ModemData {
m.mutex.Lock()
defer m.mutex.Unlock()
return ModemData{
Port: m.port.GetName(),
GpsData: m.gpsInfo,
Data: m.gps.GetData(),
}
}
func (m *modem) Sms() sms.Dialer {
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) Sms() sms.Sms {
return m.sms
}
func (m *modem) Gps() gps.Gps {
return m.gps
}
func (m *modem) At() at.Port {
return m.port
}
func (m *modem) Close() error {
func (m *modem) Close() error { // I can't return error so I log it
m.mutex.Lock()
defer m.mutex.Unlock()
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)
m.logger.Printf("\x1b[38;2;255;0;0mclose serial port error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
if err := m.onOffPin.Close(); err != nil {
return fmt.Errorf("gpio pin: %w", err)
m.logger.Printf("\x1b[38;2;255;0;0mclose gpio pin error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
if err := m.ic.Close(); err != nil {
return fmt.Errorf("internet connection: %w", err)
m.logger.Printf("\x1b[38;2;255;0;0mclose internet connection error: %s\x1b[38;2;255;255;255m\n", err.Error())
}
return nil
}
///////////// Private functions
func (m *modem) connect() error {
if m.port == nil {
return fmt.Errorf("port is not defined")
@ -211,7 +229,7 @@ func (m *modem) disconnect() error {
return m.port.Disconnect()
}
func (m *modem) isConnected() bool {
func (m *modem) IsConnected() bool {
if m.port != nil {
return m.port.IsConnected()
}
@ -231,7 +249,8 @@ func (m *modem) testGPS() error {
// Difference: I do not set \n at the end of 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 {
@ -247,20 +266,33 @@ func (m *modem) saveGPS(path string) error {
return nil
}
func (m *modem) testConsole() {
for {
var inStr string
fmt.Scanln(&inStr)
if inStr == "exit" {
return
// Short way to send command
func (m *modem) printCmd(cmd string) {
if resp, err := m.port.Send(cmd); err != nil {
m.logger.Println("FAILED TO SEND REQ", cmd, ":", err.Error())
} else {
_ = resp
// m.logger.Println("CMD", cmd, ":", resp)
}
resp, err := m.port.Send(inStr)
if err != nil {
m.logger.Println("ERROR:", err.Error())
}
// 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)
}
m.logger.Println(resp)
m.logger.Println("------------------")
// Reset output
if err := m.port.SerialPort().ResetOutputBuffer(); err != nil {
return fmt.Errorf("reset output buffer: %w", err)
}
// These commands ensure that correct modes are set
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) checkPort(portName string) (outErr error) {
@ -283,16 +315,14 @@ func (m *modem) checkPort(portName string) (outErr error) {
}
defer m.port.Disconnect() // Do not bother about errors...
// Reset input
if err := m.port.GetSerialPort().ResetInputBuffer(); err != nil {
return fmt.Errorf("reset input buffer: %w", err)
// To filter dead ports
if _, err := m.port.Send("AT"); err != nil {
return fmt.Errorf("ping error: %w", err)
}
// Reset output
if err := m.port.GetSerialPort().ResetOutputBuffer(); err != nil {
return fmt.Errorf("reset output buffer: %w", err)
if err := m.setupPort(); err != nil {
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
m.logger.Println("Ping...")
@ -308,7 +338,7 @@ func (m *modem) checkPort(portName string) (outErr error) {
return fmt.Errorf("get model: %w", err)
}
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]
if err != nil {
@ -354,10 +384,10 @@ SearchLoop:
func (m *modem) ping() error {
resp, err := m.port.Send("AT")
if err != nil {
return err
return fmt.Errorf("AT request: %w", err)
}
if !resp.Check() {
return fmt.Errorf("connection lost")
return fmt.Errorf("AT request: error response: %s", resp)
}
return nil
}

View File

@ -6,7 +6,7 @@ import (
"log"
"strings"
"github.com/CGSG-2021-AE4/modem-test/api/modem/at"
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
)
type dialer struct {
@ -14,14 +14,15 @@ type dialer struct {
port at.Port
}
type Dialer interface {
type Sms interface {
Init() error
Send(number, msg string) error
ReadNew() ([]string, error)
Send(number, msg string) error // Send sms
ReadNew() ([]string, error) // Read new smses
io.Closer
}
func New(logger *log.Logger, port at.Port) Dialer {
func New(logger *log.Logger, port at.Port) Sms {
return &dialer{
logger: logger,
port: port,
@ -33,25 +34,38 @@ func (d *dialer) Init() error {
if !d.port.IsConnected() {
return fmt.Errorf("serial port is not connected")
}
// Check SIM an PIN
if resp, err := d.port.Send("AT+CPIN?"); err != nil || !resp.Check() {
if err != nil {
return fmt.Errorf("check pin: %w", err)
}
return fmt.Errorf("check pin: error response: %s", resp)
}
// Ensure text format
d.logger.Println(d.port.Send("AT+CMGF"))
if resp, err := d.port.Send("AT+CMGF=1"); err != nil || !resp.Check() {
if err != nil {
return fmt.Errorf("AT+CMGF=1 request: %w", err)
return fmt.Errorf("set to text format request: %w", err)
}
return fmt.Errorf("failed to set SMS format")
return fmt.Errorf("set SIM format: error response: %s", resp)
}
return nil
}
func (d *dialer) Send(number, msg string) error {
d.port.Send(fmt.Sprintf("AT+CMGS=\"%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
d.port.Send(fmt.Sprintf(`AT+CMGS="%s"`, number)) // Because it will throw error
resp, err := d.port.RawSend(fmt.Sprintf("%s\n\r", msg)) // Add additional \r\n because there is not supposed to be
if err != nil {
return fmt.Errorf("message request: %w", err)
}
if at.Resp(resp).Check() {
return nil
d.logger.Println("SEND RESPONSE:", resp)
resp, err = d.port.RawSend("\x1A")
if err != nil {
return fmt.Errorf("message request: %w", err)
}
d.logger.Println("SEND RESPONSE:", resp)
errCode, err := GetError([]byte(resp))
if err != nil {
return fmt.Errorf("send sms failed and failed to get error: %w", err)
@ -61,7 +75,7 @@ func (d *dialer) Send(number, msg string) error {
// Reads all new messages
func (d *dialer) ReadNew() ([]string, error) {
resp, err := d.port.Send("AT+CMGL")
resp, err := d.port.Send("AT+CMGL=\"UNREAD\"")
if err != nil {
return nil, fmt.Errorf("AT+CMGL request: %w", err)
}

2
go.mod
View File

@ -1,4 +1,4 @@
module github.com/CGSG-2021-AE4/modem-test
module gitea.unprism.ru/KRBL/sim-modem
go 1.22.5

43
main.go
View File

@ -4,7 +4,7 @@ import (
"log"
"os"
"github.com/CGSG-2021-AE4/modem-test/api/modem"
"gitea.unprism.ru/KRBL/sim-modem/api/modem"
)
func main() {
@ -16,28 +16,51 @@ func main() {
}
func mainE() error {
m := modem.New(log.New(os.Stdout, "modem:", log.LstdFlags))
log.Println("||||||||||||||||| INIT |||||||||||||||")
logger := log.New(os.Stdout, "main : ", log.LstdFlags)
m := modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
logger.Println("||||||||||||||||| INIT |||||||||||||||")
if err := m.Init(); err != nil {
logger.Println("Init ended with error:", err.Error())
logger.Println("Try to turn on")
if err := m.PowerOn(); err != nil {
return err
}
logger.Println("Reinit")
if err := m.Init(); err != nil {
return err
}
if !m.Validate() {
log.Println("AAAAAAAAAAAAAAA Validation failed")
}
if !m.IsConnected() {
logger.Println("AAAAAAAAAAAAAAA Modem is not connected")
return nil
}
log.Println("||||||||||||||||| GET INFO |||||||||||||||||")
log.Println(m.GetInfo())
// m.PowerOff()
// time.Sleep(10 * time.Second)
// m.PowerOn()
log.Println("||||||||||||||||| SEND SMS |||||||||||||||||")
log.Println(m.At().Send("AT+CNUM"))
logger.Println("||||||||||||||||| GET INFO |||||||||||||||||")
logger.Println(m.Update())
logger.Printf("DATA: %+v\n", m.GetData())
logger.Println("||||||||||||||||| SEND SMS |||||||||||||||||")
logger.Println(m.At().Send("AT+CNUM"))
// if err := m.Sms().Send("+79218937173", "CGSG forever"); err != nil {
// return err
// }
if ms, err := m.Sms().ReadNew(); err != nil {
return err
} else {
log.Println("NEW:", ms)
logger.Println("NEW:", ms)
}
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
}

View File

@ -1,2 +0,0 @@
ISSUES:
- create input buffer every at port creation