Potencially working.
This commit is contained in:
		| @@ -1,53 +0,0 @@ | ||||
| package modem | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strings" | ||||
|  | ||||
| 	"go.bug.st/serial" | ||||
| ) | ||||
|  | ||||
| func (m *modem) makeAtReq(msg string) (string, error) { | ||||
| 	log.Println("Write...") | ||||
| 	if _, err := m.serialPort.Write([]byte(msg)); err != nil { | ||||
| 		return "", fmt.Errorf("serial port write: %w", err) | ||||
| 	} | ||||
| 	log.Println("Read...") | ||||
| 	readLen, err := m.serialPort.Read(m.inputBuf) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("port read: %w", err) | ||||
| 	} | ||||
| 	return strings.Split(string(m.inputBuf[:readLen]), "\n")[1], nil | ||||
| } | ||||
|  | ||||
| func (m *modem) makeAtEchoReqAndCheck(msg, checkMsg string) (bool, error) { | ||||
| 	ans, err := m.makeAtReq(msg) | ||||
| 	log.Println(msg, checkMsg) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	return (len(ans) >= len(checkMsg) && ans[:len(checkMsg)] == checkMsg), nil | ||||
| } | ||||
|  | ||||
| func (m *modem) connect(port string) error { | ||||
| 	log.Println("Connecting to", port, "...") | ||||
| 	s, err := serial.Open(port, &serial.Mode{BaudRate: m.baudrate}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("open port: %w", err) | ||||
| 	} | ||||
| 	// s.Close() There is no open f | ||||
| 	// s.Open() | ||||
| 	m.serialPort = s | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *modem) disconnect() error { | ||||
| 	defer func() { | ||||
| 		m.serialPort = nil | ||||
| 	}() | ||||
| 	if err := m.serialPort.Close(); err != nil { | ||||
| 		return fmt.Errorf("close port: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										154
									
								
								api/modem/at/at.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								api/modem/at/at.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| package at | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.bug.st/serial" | ||||
| ) | ||||
|  | ||||
| // Some constants | ||||
| const ( | ||||
| 	ReadTimeout  = time.Second | ||||
| 	InputBufSize = 128 | ||||
| ) | ||||
|  | ||||
| // Command types | ||||
| type CmdType byte | ||||
|  | ||||
| // Command types base on request/answer semantic: | ||||
| const ( | ||||
| 	CmdTest CmdType = iota | ||||
| 	// AT\r\n | ||||
| 	// OK | ||||
| 	CmdCheck | ||||
| 	// AT+<CMD>\r\n | ||||
| 	// OK | ||||
| 	CmdGet | ||||
| 	// AT+<CMD>\r\n | ||||
| 	// +<CMD>: <ANS> | ||||
| 	CmdQuestion | ||||
| 	// AT+<CMD>?\r\n | ||||
| 	// +<CMD>: <ANS> | ||||
| ) | ||||
|  | ||||
| type atPort struct { | ||||
| 	baudrate int | ||||
| 	portName string | ||||
| 	port     serial.Port | ||||
| 	inputBuf []byte | ||||
| } | ||||
|  | ||||
| type Port interface { | ||||
| 	Connect() error | ||||
| 	Disconnect() error | ||||
| 	Request(cmdType CmdType, cmd string) (string, error) | ||||
| 	GetName() string | ||||
| 	IsConnected() bool | ||||
| 	GetSerialPort() serial.Port // For extra need | ||||
| } | ||||
|  | ||||
| func New(portName string, baudrate int) Port { | ||||
| 	return &atPort{ | ||||
| 		portName: portName, | ||||
| 		baudrate: baudrate, | ||||
| 		inputBuf: make([]byte, InputBufSize), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *atPort) Connect() error { | ||||
| 	log.Println("Connecting to", p.portName, "...") | ||||
| 	s, err := serial.Open(p.portName, &serial.Mode{BaudRate: p.baudrate}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("open port: %w", err) | ||||
| 	} | ||||
| 	// s.Close() There is no open f | ||||
| 	// s.Open() | ||||
| 	p.port = s | ||||
| 	p.port.SetReadTimeout(ReadTimeout) | ||||
| 	p.port.ResetInputBuffer() | ||||
| 	p.port.ResetOutputBuffer() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *atPort) Disconnect() error { | ||||
| 	defer func() { | ||||
| 		p.port = nil | ||||
| 	}() | ||||
| 	if err := p.port.Close(); err != nil { | ||||
| 		return fmt.Errorf("close port: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Low level write/read function | ||||
| func (p *atPort) makeReq(msg string) (string, error) { | ||||
| 	// Write | ||||
| 	p.port.ResetInputBuffer() | ||||
| 	log.Println("Write...") // DEBUG | ||||
| 	if written, err := p.port.Write([]byte(msg)); err != nil { | ||||
| 		return "", fmt.Errorf("serial port write: %w", err) | ||||
| 	} else { | ||||
| 		log.Println("Written:", written) // DEBUG | ||||
| 	} | ||||
| 	// Read | ||||
| 	log.Println("Read...") // DEBUG | ||||
| 	readLen, err := p.port.Read(p.inputBuf) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("port read: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	log.Println("Read: ", readLen, string(p.inputBuf[:readLen])) // DEBUG | ||||
| 	return string(p.inputBuf[:readLen]), nil | ||||
| } | ||||
|  | ||||
| func (p *atPort) Request(cmdType CmdType, cmd string) (string, error) { | ||||
| 	msg := "AT" | ||||
| 	// Make command | ||||
| 	// By default it just will be AT check cmd | ||||
| 	switch cmdType { | ||||
| 	case CmdGet, CmdCheck: | ||||
| 		msg += cmd | ||||
| 	case CmdQuestion: | ||||
| 		msg += cmd + "?" | ||||
| 	} | ||||
| 	msg += "\r\n" | ||||
| 	rawResp, err := p.makeReq(msg) | ||||
| 	log.Println("Got") | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("make at request: %w", err) | ||||
| 	} | ||||
| 	if len(rawResp) == 0 { | ||||
| 		return "", fmt.Errorf("read nothing") | ||||
| 	} | ||||
| 	resp := strings.Split(rawResp, "\n") | ||||
| 	switch cmdType { | ||||
| 	case CmdTest, CmdCheck: | ||||
| 		// Check and test cmds do not suppose anything but OK | ||||
| 		if len(resp[1]) >= 2 && resp[1][:2] == "OK" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		return "", fmt.Errorf("connection lost") | ||||
| 	case CmdGet, CmdQuestion: | ||||
| 		checkL := len(cmd) + 1 | ||||
| 		if len(resp[1]) >= checkL && resp[1][:checkL] != "+"+cmd { | ||||
| 			return "", fmt.Errorf("connetion lost") | ||||
| 		} | ||||
| 		return strings.Split(resp[1], ":")[1], nil | ||||
| 	} | ||||
| 	return "", fmt.Errorf("undefined command type") | ||||
| } | ||||
|  | ||||
| func (p *atPort) GetName() string { | ||||
| 	return p.portName | ||||
| } | ||||
|  | ||||
| func (p *atPort) IsConnected() bool { | ||||
| 	return p.port != nil | ||||
| } | ||||
|  | ||||
| func (p *atPort) GetSerialPort() serial.Port { | ||||
| 	return p.port | ||||
| } | ||||
| @@ -1,44 +1,44 @@ | ||||
| package modem | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"time" | ||||
|  | ||||
| 	gpio "github.com/warthog618/go-gpiocdev" | ||||
| 	gpio "github.com/stianeikeland/go-rpio/v4" | ||||
| ) | ||||
|  | ||||
| func (m *modem) PowerOn() error { | ||||
| 	c, err := gpio.NewChip(m.deviceName) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("gpio new chip: %w", err) | ||||
| 	} | ||||
| 	l, err := c.RequestLine(m.powerKey, gpio.AsOutput(0)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("gpio request line: %w", err) | ||||
| 	} | ||||
| 	m.gpioLine = l | ||||
| 	time.Sleep(100 * time.Millisecond) | ||||
| 	if err := m.gpioLine.SetValue(1); err != nil { | ||||
| 		return fmt.Errorf("gpio set value: %w", err) | ||||
| 	} | ||||
| 	time.Sleep(3 * time.Second) | ||||
| 	if err := m.gpioLine.SetValue(0); err != nil { | ||||
| 		return fmt.Errorf("gpio set value: %w", err) | ||||
| 	} | ||||
| 	time.Sleep(30 * time.Second) | ||||
| 	return nil | ||||
| type gpioPin struct { | ||||
| 	Pin gpio.Pin | ||||
| } | ||||
|  | ||||
| func (m *modem) PowerOff() error { | ||||
| 	time.Sleep(100 * time.Millisecond) | ||||
| 	if err := m.gpioLine.SetValue(1); err != nil { | ||||
| 		return fmt.Errorf("gpio set value: %w", err) | ||||
| 	} | ||||
| 	time.Sleep(3 * time.Second) | ||||
| 	if err := m.gpioLine.SetValue(0); err != nil { | ||||
| 		return fmt.Errorf("gpio set value: %w", err) | ||||
| 	} | ||||
| 	time.Sleep(30 * time.Second) | ||||
| 	m.gpioLine = nil | ||||
| 	return nil | ||||
| func (p gpioPin) Init() error { | ||||
| 	return gpio.Open() | ||||
| } | ||||
|  | ||||
| func (p gpioPin) sendOnOffSignal() { | ||||
| 	p.Pin.Output() | ||||
| 	log.Println("Power on 0/3 + 100ms") | ||||
| 	p.Pin.Low() | ||||
| 	p.Pin.Toggle() | ||||
| 	time.Sleep(100 * time.Millisecond) | ||||
|  | ||||
| 	log.Println("Power on 1/3 + 3s") | ||||
| 	p.Pin.High() | ||||
| 	p.Pin.Toggle() | ||||
| 	time.Sleep(3 * time.Second) | ||||
|  | ||||
| 	log.Println("Power on 2/3 + 30s") | ||||
| 	p.Pin.Low() | ||||
| 	p.Pin.Toggle() | ||||
| 	time.Sleep(30 * time.Second) | ||||
|  | ||||
| 	log.Println("Power on 3/3") | ||||
| } | ||||
|  | ||||
| func (p gpioPin) PowerOn() { | ||||
| 	p.sendOnOffSignal() | ||||
| } | ||||
|  | ||||
| func (p *gpioPin) PowerOff() { | ||||
| 	p.sendOnOffSignal() | ||||
| } | ||||
|   | ||||
| @@ -1,194 +1,199 @@ | ||||
| package modem | ||||
|  | ||||
| import ( | ||||
| 	"annalist/api/modem/at" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"math" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"slices" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	gpio "github.com/warthog618/go-gpiocdev" | ||||
| 	"go.bug.st/serial" | ||||
| ) | ||||
|  | ||||
| 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 | ||||
| 	port        string | ||||
| 	baudrate int | ||||
|  | ||||
| 	deviceName  string | ||||
| 	baudrate    int | ||||
| 	inputBuf    []byte | ||||
| 	serialPort  serial.Port | ||||
| 	port        at.Port | ||||
| 	isAvailable bool | ||||
|  | ||||
| 	// Gpio stuff | ||||
| 	powerKey int | ||||
| 	gpioLine *gpio.Line | ||||
| 	onOffPin gpioPin | ||||
|  | ||||
| 	// Other values | ||||
| 	speed        float64 | ||||
| 	latitude_ns  float64 | ||||
| 	latitude     float64 | ||||
| 	longitude_we float64 | ||||
| 	longitude    float64 | ||||
|  | ||||
| 	gpsInfo        GpsInfo | ||||
| 	lastUpdateTime time.Time | ||||
| } | ||||
|  | ||||
| type Modem interface { | ||||
| 	Init() error | ||||
| 	SearchPort(isSoft bool) error | ||||
| 	PowerOn() error | ||||
| 	PowerOff() error | ||||
| 	Connect() error | ||||
| 	Ping() (bool, error) | ||||
| 	Ping() error | ||||
| 	SwitchToGpsMode() error | ||||
| 	CalculateSpeed(newLatitude, newlongitude float64) | ||||
| 	Update() error | ||||
| 	GetInfo() string | ||||
| 	GetInfo() (string, error) | ||||
| 	TestGPS() error | ||||
| } | ||||
|  | ||||
| func New() Modem { | ||||
| 	return &modem{ | ||||
| 		baudrate:       115200, | ||||
| 		powerKey:       6, | ||||
| 		onOffPin:       gpioPin{Pin: 6}, | ||||
| 		lastUpdateTime: time.Now(), | ||||
| 		inputBuf:       make([]byte, 128), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 	} | ||||
| 	if m.port == "" { | ||||
|  | ||||
| 	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 == "" { | ||||
| 	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) SearchPort(isSoft bool) error { | ||||
| 	ports := []string{} | ||||
|  | ||||
| 	// Get ports | ||||
| 	if isSoft { | ||||
| 		ports = []string{"ttyUSB1", "ttyUSB2", "ttyUSB3"} | ||||
| 	} else { | ||||
| 		/**/ | ||||
| 		log.Print("Search for ports...") | ||||
| 		out, err := exec.Command("/bin/ls", "/dev").Output() | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("execute ls command: %w", err) | ||||
| 		} | ||||
| 		allPorts := strings.Split(string(out), "\n") | ||||
| 		for _, p := range allPorts { | ||||
| 			if len(p) > 2 && p[:3] == "tty" { | ||||
| 				ports = append(ports, p) | ||||
| 			} | ||||
| 		} | ||||
| 		// slices.Reverse(ports) // TODO why | ||||
| 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 | ||||
| 	log.Println("Found ports: ", ports) | ||||
| 	log.Println("Check...") | ||||
| 	defer func() { | ||||
| 		if m.serialPort != nil { | ||||
| 			m.disconnect() // TODO maybe handle | ||||
| 		} | ||||
| 	}() | ||||
| SearchLoop: | ||||
| 	for _, p := range ports { | ||||
| 		if m.serialPort != nil { | ||||
| 			if err := m.disconnect(); err != nil { | ||||
| 				return fmt.Errorf("disconnect: %w", err) | ||||
| 			} | ||||
| 		} | ||||
| 		log.Println("Checking port: ", p) | ||||
| 		if _, err := os.Stat("/dev/" + p); err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		if err := m.connect("/dev/" + p); err != nil { | ||||
| 			log.Println("Error:", fmt.Errorf("connect: %w", err).Error()) | ||||
| 			continue | ||||
| 		} | ||||
| 		log.Printf("Checking port %s ...\n", p) | ||||
|  | ||||
| 		// Ping | ||||
| 		log.Println("Ping...") | ||||
| 		if ok, err := m.Ping(); err != nil || !ok { | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("ping error: %w", err) | ||||
| 			} | ||||
| 			return errors.New("modem does not ping") | ||||
| 		} | ||||
|  | ||||
| 		// Check model | ||||
| 		{ | ||||
| 			ok, err := m.makeAtEchoReqAndCheck("AT+CGMM\r\n", "SIMCOM_SIM7600E-H") | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("make serial request: %w", err) | ||||
| 			} | ||||
| 			if !ok { | ||||
| 				continue | ||||
| 			} | ||||
| 		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 = "/dev/" + p | ||||
| 		m.port = at.New("/dev/"+p, m.baudrate) | ||||
| 		m.isAvailable = true | ||||
| 		return nil | ||||
| 	} | ||||
| 	// return errors.New("no a compatible modem port found") | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *modem) Connect() error { | ||||
| 	return m.connect(m.port) | ||||
| 	if m.port == nil { | ||||
| 		return fmt.Errorf("port is not defined") | ||||
| 	} | ||||
| 	return m.port.Connect() | ||||
| } | ||||
|  | ||||
| func (m *modem) Ping() (bool, error) { | ||||
| 	return m.makeAtEchoReqAndCheck("AT\r\n", "OK") | ||||
| func (m *modem) Ping() error { | ||||
| 	_, err := m.port.Request(at.CmdTest, "") | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (m *modem) SwitchToGpsMode() error { | ||||
| 	if err := m.serialPort.ResetInputBuffer(); err != nil { | ||||
| 	log.Println("Enabling GPS mode...") | ||||
| 	// Reset intput | ||||
| 	if err := m.port.GetSerialPort().ResetInputBuffer(); err != nil { | ||||
| 		return fmt.Errorf("reset input buffer: %w", err) | ||||
| 	} | ||||
| 	ans, err := m.makeAtReq("AT+CGPS?\r\n") | ||||
|  | ||||
| 	// Check gps mode status | ||||
| 	ans, err := m.port.Request(at.CmdQuestion, "CGPS") | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("make serial request: %w", err) | ||||
| 		return fmt.Errorf("make at ask: %w", err) | ||||
| 	} | ||||
| 	resp := strings.Split(ans, ":") | ||||
| 	if !(len(resp) > 1 && resp[0] == "+CGPS") { | ||||
| 		return errors.New("lost connection while checking gps status") | ||||
| 	} | ||||
| 	switch resp[1][1] { | ||||
| 	case '1': | ||||
| 	if ans == "1" { | ||||
| 		log.Println("GPS already enabled") | ||||
| 		return nil | ||||
| 	case '0': | ||||
| 		ok, err := m.makeAtEchoReqAndCheck("AT+CGPS=1", "OK") | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("try to switch to gps mode echo reqest: %w", err) | ||||
| 		} | ||||
| 		if !ok { | ||||
| 			return errors.New("lost connection while trying to switch to gps mode") | ||||
| 		} | ||||
| 	default: | ||||
| 		return errors.New("unexpected response of gps status") | ||||
| 	} | ||||
|  | ||||
| 	// 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 | ||||
| } | ||||
|  | ||||
| @@ -199,13 +204,13 @@ func deg2rad(deg float64) float64 { | ||||
| func (m *modem) CalculateSpeed(newLatitude, newLongitude float64) { | ||||
| 	log.Println("Calculate speed") | ||||
| 	earthRad := 6371.0 // TODO ? | ||||
| 	dLat := deg2rad(math.Abs(newLatitude - m.latitude)) | ||||
| 	dLon := deg2rad(math.Abs(newLongitude - m.longitude)) | ||||
| 	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.latitude))*math.Sin(dLon/2)*math.Sin(dLon/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.speed = earthRad * c / (math.Abs(float64(time.Since(m.lastUpdateTime)))) | ||||
| 	m.gpsInfo.Speed = earthRad * c / (math.Abs(float64(time.Since(m.lastUpdateTime)))) | ||||
| } | ||||
|  | ||||
| func (m *modem) Update() error { | ||||
| @@ -214,23 +219,143 @@ func (m *modem) Update() error { | ||||
| 		log.Println("No connection to module") | ||||
| 		return nil | ||||
| 	} | ||||
| 	m.serialPort.ResetInputBuffer() | ||||
| 	// MAKE | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| func (m *modem) GetInfo() string { | ||||
| 	return "" | ||||
| 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: | ||||
| 		do many threads? | ||||
| 		JSON why you clamp | ||||
| */ | ||||
|  | ||||
| /* | ||||
|   | ||||
		Reference in New Issue
	
	Block a user