363 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package modem
 | |
| 
 | |
| import (
 | |
| 	"annalist/api/modem/at"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"math"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"slices"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| 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:"-"`
 | |
| }
 | |
| 
 | |
| type modem struct {
 | |
| 	// Serial stuff
 | |
| 	baudrate int
 | |
| 
 | |
| 	deviceName  string
 | |
| 	port        at.Port
 | |
| 	isAvailable bool
 | |
| 
 | |
| 	// Gpio stuff
 | |
| 	onOffPin gpioPin
 | |
| 
 | |
| 	// Other values
 | |
| 	gpsInfo        GpsInfo
 | |
| 	lastUpdateTime time.Time
 | |
| }
 | |
| 
 | |
| type Modem interface {
 | |
| 	Init() error
 | |
| 	SearchPort(isSoft bool) error
 | |
| 	Connect() error
 | |
| 	Ping() error
 | |
| 	SwitchToGpsMode() error
 | |
| 	CalculateSpeed(newLatitude, newlongitude float64)
 | |
| 	Update() error
 | |
| 	GetInfo() (string, error)
 | |
| 	TestGPS() error
 | |
| }
 | |
| 
 | |
| func New() Modem {
 | |
| 	return &modem{
 | |
| 		baudrate:       115200,
 | |
| 		onOffPin:       gpioPin{Pin: 6},
 | |
| 		lastUpdateTime: time.Now(),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (m *modem) Init() error {
 | |
| 	// Turn module on
 | |
| 	if err := m.onOffPin.Init(); err != nil {
 | |
| 		return fmt.Errorf("gpio pin init: %w", err)
 | |
| 	}
 | |
| 	m.onOffPin.PowerOn()
 | |
| 
 | |
| 	// Soft search
 | |
| 	if err := m.SearchPort(true); err != nil {
 | |
| 		return fmt.Errorf("soft port search: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| 
 | |
| 	// Common search
 | |
| 	if m.port == nil {
 | |
| 		if err := m.SearchPort(false); err != nil {
 | |
| 			return fmt.Errorf("not soft port search: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 	if m.port == nil {
 | |
| 		return errors.New("no port is detected")
 | |
| 	}
 | |
| 
 | |
| 	// Connect
 | |
| 	if err := m.Connect(); err != nil {
 | |
| 		return fmt.Errorf("connect: %w", err)
 | |
| 	}
 | |
| 	// Tests
 | |
| 	if err := m.TestGPS(); err != nil {
 | |
| 		return fmt.Errorf("testGPS: %w", err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| func (m *modem) SearchPort(isSoft bool) error {
 | |
| 	// Get ports
 | |
| 	ports := []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"}
 | |
| 	if !isSoft {
 | |
| 		ps, err := GetTtyDevices()
 | |
| 		if err != nil {
 | |
| 			fmt.Errorf("get serial devices: %w", err)
 | |
| 		}
 | |
| 		ports = ps
 | |
| 	}
 | |
| 
 | |
| 	// Check ports
 | |
| SearchLoop:
 | |
| 	for _, p := range ports {
 | |
| 		log.Printf("Checking port %s ...\n", p)
 | |
| 
 | |
| 		if err := m.checkPort("/dev/" + p); err != nil {
 | |
| 			log.Printf("Check failed: %s\n", err.Error())
 | |
| 			continue SearchLoop
 | |
| 		}
 | |
| 
 | |
| 		log.Print("Found modem on port: ", p)
 | |
| 		m.port = at.New("/dev/"+p, m.baudrate)
 | |
| 		m.isAvailable = true
 | |
| 		return nil
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *modem) Connect() error {
 | |
| 	if m.port == nil {
 | |
| 		return fmt.Errorf("port is not defined")
 | |
| 	}
 | |
| 	return m.port.Connect()
 | |
| }
 | |
| 
 | |
| func (m *modem) Ping() error {
 | |
| 	_, err := m.port.Request(at.CmdTest, "")
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (m *modem) SwitchToGpsMode() error {
 | |
| 	log.Println("Enabling GPS mode...")
 | |
| 	// Reset intput
 | |
| 	if err := m.port.GetSerialPort().ResetInputBuffer(); err != nil {
 | |
| 		return fmt.Errorf("reset input buffer: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Check gps mode status
 | |
| 	ans, err := m.port.Request(at.CmdQuestion, "CGPS")
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("make at ask: %w", err)
 | |
| 	}
 | |
| 	if ans == "1" {
 | |
| 		log.Println("GPS already enabled")
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// 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")
 | |
| 	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 ?
 | |
| 	dLat := deg2rad(math.Abs(newLatitude - m.gpsInfo.Latitude))
 | |
| 	dLon := deg2rad(math.Abs(newLongitude - m.gpsInfo.Longitude))
 | |
| 	a := math.Sin(dLat/2)*math.Sin(dLat/2) +
 | |
| 		math.Cos(deg2rad(newLatitude))*math.Cos(deg2rad(m.gpsInfo.Latitude))*math.Sin(dLon/2)*math.Sin(dLon/2)
 | |
| 	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
 | |
| 
 | |
| 	m.gpsInfo.Speed = earthRad * c / (math.Abs(float64(time.Since(m.lastUpdateTime))))
 | |
| }
 | |
| 
 | |
| func (m *modem) Update() error {
 | |
| 	log.Println("Update")
 | |
| 	if !m.isAvailable {
 | |
| 		log.Println("No connection to module")
 | |
| 		return nil
 | |
| 	}
 | |
| 	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"`
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| func (m *modem) TestGPS() error {
 | |
| 	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())
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| /*
 | |
| 	TODOs:
 | |
| 		maybe to store read/write buf in obj
 | |
| 	QUESTIONS:
 | |
| 		JSON why you clamp
 | |
| */
 | |
| 
 | |
| /*
 | |
|  */
 |