package modem import ( "encoding/json" "errors" "fmt" "log" "math" "os" "os/exec" "slices" "strconv" "strings" "time" "github.com/CGSG-2021-AE4/modem-test/api/modem/at" ) 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 m.port = at.New(portName, m.baudrate) if err := m.port.Connect(); err != nil { return fmt.Errorf("connect: %w", err) } defer m.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 := m.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 */ /* */