package modem import ( "fmt" "io" "log" "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/gps" "github.com/CGSG-2021-AE4/modem-test/api/modem/internet" "github.com/CGSG-2021-AE4/modem-test/api/modem/sms" ) type ModemData struct { Port string `json:"Port"` 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 // For turning on and off // Other values lastUpdateTime time.Time // GPS gps gps.Gps // Internet connection ic internet.Conn // Sms and calls sms sms.Sms } type Modem interface { Init() error IsConnected() bool Update() error GetData() ModemData 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 } func New(logger *log.Logger) Modem { return &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) } // Search m.logger.Println("=============================== Search") // Soft search if err := m.searchPort(true); err != nil { return fmt.Errorf("soft port search: %w", err) } // Wide 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 fmt.Errorf("no port is detected") } // Connect m.logger.Println("=============================== Connect") if err := m.connect(); err != nil { return fmt.Errorf("connect: %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") } 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) // } return nil } func (m *modem) Update() error { m.mutex.Lock() defer m.mutex.Unlock() if !m.IsConnected() { m.logger.Println("No connection to module") return nil } m.logger.Println("Update", m.gps) if err := m.gps.Update(); err != nil { m.logger.Println("gps update:", err.Error()) } // Read new messages return nil } func (m *modem) GetData() ModemData { m.mutex.Lock() defer m.mutex.Unlock() return ModemData{ Port: m.port.GetName(), Data: m.gps.GetData(), } } 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 { // I can't return error so I log it m.mutex.Lock() defer m.mutex.Unlock() if err := m.sms.Close(); err != nil { m.logger.Printf("\x1b[38;2;255;0;0mclose sms 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()) } 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.ic.Close(); err != nil { 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") } return m.port.Connect() } func (m *modem) disconnect() error { if m.port == nil { return fmt.Errorf("port is not defined") } return m.port.Disconnect() } func (m *modem) IsConnected() bool { if m.port != nil { return m.port.IsConnected() } return false } func (m *modem) testGPS() error { m.logger.Println("Testing GPS") if err := m.Update(); err != nil { return fmt.Errorf("update: %w", err) } m.logger.Println("Current coords:", m.getShortInfo()) return nil } // Difference: I do not set \n at the end of string func (m *modem) getShortInfo() string { 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 { 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 } // 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) } } // 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) } // 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) { defer func() { if outErr != nil { // Clear port if there is error m.port = nil } }() // 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(m.logger, 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... // To filter dead ports if _, err := m.port.Send("AT"); err != nil { return fmt.Errorf("ping error: %w", err) } if err := m.setupPort(); err != nil { return fmt.Errorf("setup port: %w", err) } // Ping m.logger.Println("Ping...") if err := m.ping(); err != nil { return fmt.Errorf("ping error: %w", err) } m.logger.Println("\x1b[38;2;0;255;0mOK\x1b[38;2;255;255;255m") // Check model m.logger.Println("Check model...") resp, err := m.port.Send("AT+CGMM") if err != nil { return fmt.Errorf("get model: %w", err) } if !resp.Check() { return fmt.Errorf("get model: error response: %s", resp) } model := strings.Split(resp.String(), "\n")[0] if err != nil { return fmt.Errorf("get model: %w", err) } rightModel := "SIMCOM_SIM7600E-H" // m.logger.Printf("[% x]\n [% x]", []byte("SIMCOM_SIM7600E-H"), []byte(model)) if len(model) >= len(rightModel) && model[:len(rightModel)] != rightModel { return fmt.Errorf("invalid modem model: %s", model) } m.logger.Println("\x1b[38;2;0;255;0mOK\x1b[38;2;255;255;255m") 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 { m.logger.Printf("Checking port %s ...\n", p) 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()) continue SearchLoop } m.logger.Print("Found modem on port: ", p) m.port = at.New(m.logger, "/dev/"+p, m.baudrate) return nil } return nil } func (m *modem) ping() error { resp, err := m.port.Send("AT") if err != nil { return fmt.Errorf("AT request: %w", err) } if !resp.Check() { return fmt.Errorf("AT request: error response: %s", resp) } return nil } func getTtyDevices() ([]string, error) { // Get ports /**/ out, err := exec.Command("ls", "--", "/dev/tty[!0-9]*").Output() if err != nil { return nil, fmt.Errorf("execute ls command: %w", err) } ports := strings.Split(string(out), "\n") return ports, nil } /* TODOs: maybe to store read/write buf in obj QUESTIONS: JSON why you clamp */ /* */