Potencially working.

This commit is contained in:
Andrey Egorov 2024-07-21 16:05:09 +03:00
parent c8f30c0caf
commit f160282fe2
9 changed files with 440 additions and 209 deletions

View File

@ -5,7 +5,7 @@ build:
# @./out/annalist.exe
linux:
@$Env:GOOS="linux" $Env:GOARCH="arm" $Env:GOARM=5
@$Env:GOOS="linux"; $Env:GOARCH="arm"; $Env:GOARM=5
# .profile
# cgo_enabled 0

View File

@ -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
View 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
}

View File

@ -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 {
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)
if err := m.gpioLine.SetValue(1); err != nil {
return fmt.Errorf("gpio set value: %w", err)
}
log.Println("Power on 1/3 + 3s")
p.Pin.High()
p.Pin.Toggle()
time.Sleep(3 * time.Second)
if err := m.gpioLine.SetValue(0); err != nil {
return fmt.Errorf("gpio set value: %w", err)
}
log.Println("Power on 2/3 + 30s")
p.Pin.Low()
p.Pin.Toggle()
time.Sleep(30 * time.Second)
m.gpioLine = nil
return nil
log.Println("Power on 3/3")
}
func (p gpioPin) PowerOn() {
p.sendOnOffSignal()
}
func (p *gpioPin) PowerOff() {
p.sendOnOffSignal()
}

View File

@ -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
deviceName string
baudrate int
inputBuf []byte
serialPort serial.Port
deviceName string
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{}
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")
}
// 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
}
// Check ports
log.Println("Found ports: ", ports)
log.Println("Check...")
defer func() {
if m.serialPort != nil {
m.disconnect() // TODO maybe handle
}
}()
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
// 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 ok, err := m.Ping(); err != nil || !ok {
if err != nil {
if err := m.Ping(); 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")
model, err := port.Request(at.CmdGet, "CGMM")
if err != nil {
return fmt.Errorf("make serial request: %w", err)
return fmt.Errorf("get model: %w", err)
}
if !ok {
continue
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 = "/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")
}
// 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 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")
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")
}
func (m *modem) GetInfo() string {
return ""
// 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:
do many threads?
JSON why you clamp
*/
/*

3
go.mod
View File

@ -3,11 +3,12 @@ module annalist
go 1.22.5
require (
github.com/warthog618/go-gpiocdev v0.9.0
github.com/stianeikeland/go-rpio/v4 v4.6.0
go.bug.st/serial v1.6.2
)
require (
github.com/creack/goselect v0.1.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/sys v0.18.0 // indirect
)

8
go.sum
View File

@ -2,16 +2,12 @@ github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stianeikeland/go-rpio/v4 v4.6.0 h1:eAJgtw3jTtvn/CqwbC82ntcS+dtzUTgo5qlZKe677EY=
github.com/stianeikeland/go-rpio/v4 v4.6.0/go.mod h1:A3GvHxC1Om5zaId+HqB3HKqx4K/AqeckxB7qRjxMK7o=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/warthog618/go-gpiocdev v0.9.0 h1:AZWUq1WObgKCO9cJCACFpwWQw6yu8vJbIE6fRZ+6cbY=
github.com/warthog618/go-gpiocdev v0.9.0/go.mod h1:GV4NZC82fWJERqk7Gu0+KfLSDIBEDNm6aPGiHlmT5fY=
github.com/warthog618/go-gpiosim v0.1.0 h1:2rTMTcKUVZxpUuvRKsagnKAbKpd3Bwffp87xywEDVGI=
github.com/warthog618/go-gpiosim v0.1.0/go.mod h1:Ngx/LYI5toxHr4E+Vm6vTgCnt0of0tktsSuMUEJ2wCI=
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=

View File

@ -16,4 +16,10 @@ func main() {
func mainE() error {
m := modem.New()
return m.Init()
// ports, err := serial.GetPortsList()
// if err != nil {
// return err
// }
// log.Println(ports)
// return nil
}

2
readme.md Normal file
View File

@ -0,0 +1,2 @@
ISSUES:
- create input buffer every at port creation