2024-07-18 16:34:26 +00:00
|
|
|
package modem
|
|
|
|
|
|
|
|
import (
|
2024-07-21 13:05:09 +00:00
|
|
|
"annalist/api/modem/at"
|
|
|
|
"encoding/json"
|
2024-07-18 16:34:26 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"math"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2024-07-21 13:05:09 +00:00
|
|
|
"slices"
|
|
|
|
"strconv"
|
2024-07-18 16:34:26 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
type GpsInfo 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:"-"`
|
|
|
|
}
|
|
|
|
|
2024-07-18 16:34:26 +00:00
|
|
|
type modem struct {
|
|
|
|
// Serial stuff
|
2024-07-21 13:05:09 +00:00
|
|
|
baudrate int
|
|
|
|
|
2024-07-18 16:34:26 +00:00
|
|
|
deviceName string
|
2024-07-21 13:05:09 +00:00
|
|
|
port at.Port
|
2024-07-18 16:34:26 +00:00
|
|
|
isAvailable bool
|
|
|
|
|
|
|
|
// Gpio stuff
|
2024-07-21 13:05:09 +00:00
|
|
|
onOffPin gpioPin
|
2024-07-18 16:34:26 +00:00
|
|
|
|
|
|
|
// Other values
|
2024-07-21 13:05:09 +00:00
|
|
|
gpsInfo GpsInfo
|
2024-07-18 16:34:26 +00:00
|
|
|
lastUpdateTime time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
type Modem interface {
|
|
|
|
Init() error
|
|
|
|
SearchPort(isSoft bool) error
|
|
|
|
Connect() error
|
2024-07-21 13:05:09 +00:00
|
|
|
Ping() error
|
2024-07-18 16:34:26 +00:00
|
|
|
SwitchToGpsMode() error
|
|
|
|
CalculateSpeed(newLatitude, newlongitude float64)
|
|
|
|
Update() error
|
2024-07-21 13:05:09 +00:00
|
|
|
GetInfo() (string, error)
|
2024-07-18 16:34:26 +00:00
|
|
|
TestGPS() error
|
|
|
|
}
|
|
|
|
|
|
|
|
func New() Modem {
|
|
|
|
return &modem{
|
|
|
|
baudrate: 115200,
|
2024-07-21 13:05:09 +00:00
|
|
|
onOffPin: gpioPin{Pin: 6},
|
2024-07-18 16:34:26 +00:00
|
|
|
lastUpdateTime: time.Now(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *modem) Init() error {
|
2024-07-21 13:05:09 +00:00
|
|
|
// Turn module on
|
|
|
|
if err := m.onOffPin.Init(); err != nil {
|
|
|
|
return fmt.Errorf("gpio pin init: %w", err)
|
|
|
|
}
|
|
|
|
m.onOffPin.PowerOn()
|
|
|
|
|
|
|
|
// Soft search
|
2024-07-18 16:34:26 +00:00
|
|
|
if err := m.SearchPort(true); err != nil {
|
|
|
|
return fmt.Errorf("soft port search: %w", err)
|
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
// Common search
|
|
|
|
if m.port == nil {
|
2024-07-18 16:34:26 +00:00
|
|
|
if err := m.SearchPort(false); err != nil {
|
|
|
|
return fmt.Errorf("not soft port search: %w", err)
|
|
|
|
}
|
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
if m.port == nil {
|
2024-07-18 16:34:26 +00:00
|
|
|
return errors.New("no port is detected")
|
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
|
|
|
|
// Connect
|
2024-07-18 16:34:26 +00:00
|
|
|
if err := m.Connect(); err != nil {
|
|
|
|
return fmt.Errorf("connect: %w", err)
|
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
// Tests
|
2024-07-18 16:34:26 +00:00
|
|
|
if err := m.TestGPS(); err != nil {
|
|
|
|
return fmt.Errorf("testGPS: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
func (m *modem) checkPort(portName string) error {
|
|
|
|
// Check device for existance
|
|
|
|
if _, err := os.Stat(portName); err != nil {
|
|
|
|
return fmt.Errorf("device does not exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check serial connection
|
|
|
|
// Connect
|
|
|
|
port := at.New(portName, m.baudrate)
|
|
|
|
if err := port.Connect(); err != nil {
|
|
|
|
return fmt.Errorf("connect: %w", err)
|
|
|
|
}
|
|
|
|
defer port.Disconnect() // Do not bother about errors...
|
|
|
|
|
|
|
|
// Ping
|
|
|
|
log.Println("Ping...")
|
|
|
|
|
|
|
|
if err := m.Ping(); err != nil {
|
|
|
|
return fmt.Errorf("ping error: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check model
|
|
|
|
model, err := port.Request(at.CmdGet, "CGMM")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("get model: %w", err)
|
|
|
|
}
|
|
|
|
if model != "SIMCOM_SIM7600E-H" {
|
|
|
|
return fmt.Errorf("invalid modem model: %s", model)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2024-07-18 16:34:26 +00:00
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
func (m *modem) SearchPort(isSoft bool) error {
|
2024-07-18 16:34:26 +00:00
|
|
|
// Get ports
|
2024-07-21 13:05:09 +00:00
|
|
|
ports := []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}
|
|
|
|
if !isSoft {
|
|
|
|
ps, err := GetTtyDevices()
|
2024-07-18 16:34:26 +00:00
|
|
|
if err != nil {
|
2024-07-21 13:05:09 +00:00
|
|
|
fmt.Errorf("get serial devices: %w", err)
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
ports = ps
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
|
2024-07-18 16:34:26 +00:00
|
|
|
// Check ports
|
2024-07-21 13:05:09 +00:00
|
|
|
SearchLoop:
|
2024-07-18 16:34:26 +00:00
|
|
|
for _, p := range ports {
|
2024-07-21 13:05:09 +00:00
|
|
|
log.Printf("Checking port %s ...\n", p)
|
2024-07-18 16:34:26 +00:00
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
if err := m.checkPort("/dev/" + p); err != nil {
|
|
|
|
log.Printf("Check failed: %s\n", err.Error())
|
|
|
|
continue SearchLoop
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Print("Found modem on port: ", p)
|
2024-07-21 13:05:09 +00:00
|
|
|
m.port = at.New("/dev/"+p, m.baudrate)
|
2024-07-18 16:34:26 +00:00
|
|
|
m.isAvailable = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *modem) Connect() error {
|
2024-07-21 13:05:09 +00:00
|
|
|
if m.port == nil {
|
|
|
|
return fmt.Errorf("port is not defined")
|
|
|
|
}
|
|
|
|
return m.port.Connect()
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
func (m *modem) Ping() error {
|
|
|
|
_, err := m.port.Request(at.CmdTest, "")
|
|
|
|
return err
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *modem) SwitchToGpsMode() error {
|
2024-07-21 13:05:09 +00:00
|
|
|
log.Println("Enabling GPS mode...")
|
|
|
|
// Reset intput
|
|
|
|
if err := m.port.GetSerialPort().ResetInputBuffer(); err != nil {
|
2024-07-18 16:34:26 +00:00
|
|
|
return fmt.Errorf("reset input buffer: %w", err)
|
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
|
|
|
|
// Check gps mode status
|
|
|
|
ans, err := m.port.Request(at.CmdQuestion, "CGPS")
|
2024-07-18 16:34:26 +00:00
|
|
|
if err != nil {
|
2024-07-21 13:05:09 +00:00
|
|
|
return fmt.Errorf("make at ask: %w", err)
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
if ans == "1" {
|
2024-07-18 16:34:26 +00:00
|
|
|
log.Println("GPS already enabled")
|
|
|
|
return nil
|
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
|
|
|
|
// Modem is not in GPS mode
|
|
|
|
_, err = m.port.Request(at.CmdCheck, "CGPS=1")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("try to switch to gps: %w", err)
|
|
|
|
}
|
|
|
|
log.Println("GPS mode enabled")
|
2024-07-18 16:34:26 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func deg2rad(deg float64) float64 {
|
|
|
|
return deg * (math.Pi / 180)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *modem) CalculateSpeed(newLatitude, newLongitude float64) {
|
|
|
|
log.Println("Calculate speed")
|
|
|
|
earthRad := 6371.0 // TODO ?
|
2024-07-21 13:05:09 +00:00
|
|
|
dLat := deg2rad(math.Abs(newLatitude - m.gpsInfo.Latitude))
|
|
|
|
dLon := deg2rad(math.Abs(newLongitude - m.gpsInfo.Longitude))
|
2024-07-18 16:34:26 +00:00
|
|
|
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
|
2024-07-21 13:05:09 +00:00
|
|
|
math.Cos(deg2rad(newLatitude))*math.Cos(deg2rad(m.gpsInfo.Latitude))*math.Sin(dLon/2)*math.Sin(dLon/2)
|
2024-07-18 16:34:26 +00:00
|
|
|
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
m.gpsInfo.Speed = earthRad * c / (math.Abs(float64(time.Since(m.lastUpdateTime))))
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *modem) Update() error {
|
|
|
|
log.Println("Update")
|
|
|
|
if !m.isAvailable {
|
|
|
|
log.Println("No connection to module")
|
|
|
|
return nil
|
|
|
|
}
|
2024-07-21 13:05:09 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
log.Println("switched to GPS mode")
|
|
|
|
} else {
|
|
|
|
log.Println("mode in right GPS mode")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update
|
|
|
|
|
|
|
|
log.Println("Receiving GPS data...")
|
|
|
|
resp, err := m.port.Request(at.CmdGet, "GPSINFO")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("receive GPS data: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("Decoding data...")
|
|
|
|
coordinates := strings.Split(resp, ",")
|
|
|
|
|
|
|
|
m.gpsInfo.Latitude, err = strconv.ParseFloat(coordinates[0], 64)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parse latitude: %w", err)
|
|
|
|
}
|
|
|
|
m.gpsInfo.Longitude, err = strconv.ParseFloat(coordinates[2], 64)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parse longitude: %w", err)
|
|
|
|
}
|
|
|
|
m.gpsInfo.LatitudeIndicator = coordinates[1]
|
|
|
|
m.gpsInfo.LatitudeIndicator = coordinates[3]
|
|
|
|
m.gpsInfo.Date = coordinates[4]
|
|
|
|
m.gpsInfo.Time = coordinates[5]
|
|
|
|
m.gpsInfo.Altitude, err = strconv.ParseFloat(coordinates[6], 64)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parse altitude: %w", err)
|
|
|
|
}
|
|
|
|
m.gpsInfo.Speed, err = strconv.ParseFloat(coordinates[7], 64)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parse speed: %w", err)
|
|
|
|
}
|
|
|
|
m.gpsInfo.Course, err = strconv.ParseFloat(coordinates[8], 64)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parse course: %w", err)
|
|
|
|
}
|
|
|
|
log.Println("Decoded successfully")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type ModemInfo struct {
|
|
|
|
Port string `json:"Port"`
|
|
|
|
GpsInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
type Info struct {
|
|
|
|
Modem ModemInfo `json:"Modem"`
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
func (m *modem) GetInfo() (string, error) {
|
|
|
|
info := Info{
|
|
|
|
Modem: ModemInfo{
|
|
|
|
Port: m.port.GetName(),
|
|
|
|
GpsInfo: m.gpsInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
buf, err := json.Marshal(info)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Errorf("marshal info: %w", err)
|
|
|
|
}
|
|
|
|
return string(buf), nil // why you clamp
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *modem) SaveGPS(path string) error {
|
|
|
|
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("open file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
if _, err = f.WriteString(m.GetShortInfo()); err != nil {
|
|
|
|
return fmt.Errorf("write file: %W", err)
|
|
|
|
}
|
|
|
|
return nil
|
2024-07-18 16:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *modem) TestGPS() error {
|
2024-07-21 13:05:09 +00:00
|
|
|
log.Println("Testing GPS")
|
|
|
|
|
|
|
|
if err := m.SwitchToGpsMode(); err != nil {
|
|
|
|
return fmt.Errorf("switch to GPS: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := m.Update(); err != nil {
|
|
|
|
return fmt.Errorf("update: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("Current coords:", m.GetShortInfo())
|
2024-07-18 16:34:26 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-21 13:05:09 +00:00
|
|
|
func GetTtyDevices() ([]string, error) {
|
|
|
|
devices := []string{}
|
|
|
|
|
|
|
|
// Get ports
|
|
|
|
/**/
|
|
|
|
log.Print("Search for ports...")
|
|
|
|
out, err := exec.Command("/bin/ls", "/dev").Output()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("execute ls command: %w", err)
|
|
|
|
}
|
|
|
|
allPorts := strings.Split(string(out), "\n")
|
|
|
|
for _, p := range allPorts {
|
|
|
|
if len(p) > 3 && p[:3] == "tty" {
|
|
|
|
devices = append(devices, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
slices.Reverse(devices) // ASK why
|
|
|
|
|
|
|
|
return devices, nil
|
|
|
|
}
|
|
|
|
|
2024-07-18 16:34:26 +00:00
|
|
|
/*
|
|
|
|
TODOs:
|
|
|
|
maybe to store read/write buf in obj
|
|
|
|
QUESTIONS:
|
2024-07-21 13:05:09 +00:00
|
|
|
JSON why you clamp
|
2024-07-18 16:34:26 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
*/
|