Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4f89f15146 | ||
|
509006fae1 | ||
|
3f5412fac0 | ||
|
5065cfdfca | ||
|
5059814d39 | ||
|
d21ad0ce00 | ||
|
75d05f197c | ||
|
b3fb13b7b3 |
2
Makefile
2
Makefile
@ -4,4 +4,4 @@ export GOARM=6
|
||||
export CGO_ENABLED=0
|
||||
|
||||
build:
|
||||
@go build -o out/modem main.go
|
||||
@go build -o out/out main.go
|
||||
|
@ -37,8 +37,10 @@ type Port interface {
|
||||
Disconnect() error
|
||||
IsConnected() bool
|
||||
|
||||
RawSend(msg string) (string, error)
|
||||
RawSend(msg string) error
|
||||
RawRead(timeout time.Duration) (string, error)
|
||||
Send(cmd string) (Resp, error)
|
||||
SendWithTimeout(cmd string, timeout time.Duration) (Resp, error)
|
||||
|
||||
io.Closer
|
||||
}
|
||||
@ -105,34 +107,73 @@ func (p *atPort) IsConnected() bool {
|
||||
return p.port != nil
|
||||
}
|
||||
|
||||
func (p *atPort) RawRead(timeout time.Duration) (string, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
outBuf := make([]byte, 0)
|
||||
|
||||
readLoop:
|
||||
for {
|
||||
readLen, err := p.port.Read(p.inputBuf)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("port read: %w", err)
|
||||
}
|
||||
if readLen == 0 && time.Now().After(deadline) {
|
||||
break readLoop
|
||||
}
|
||||
outBuf = append(outBuf, p.inputBuf[:readLen]...)
|
||||
// if readLen < len(p.inputBuf) {
|
||||
// break readLoop
|
||||
// }
|
||||
}
|
||||
// p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(outBuf), "\x1b[38;2;255;255;255m")
|
||||
return string(outBuf), nil
|
||||
}
|
||||
|
||||
// Low level write/read function
|
||||
func (p *atPort) RawSend(msg string) (string, error) {
|
||||
func (p *atPort) RawSend(msg string) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
// Write
|
||||
if _, err := p.port.Write([]byte(msg)); err != nil {
|
||||
return "", fmt.Errorf("serial port write: %w", err)
|
||||
return fmt.Errorf("serial port write: %w", err)
|
||||
}
|
||||
// time.Sleep(time.Millisecond)
|
||||
// Read
|
||||
readLen, err := p.port.Read(p.inputBuf)
|
||||
// p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(p.inputBuf[:readLen]), "\x1b[38;2;255;255;255m")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("port read: %w", err)
|
||||
}
|
||||
|
||||
return string(p.inputBuf[:readLen]), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *atPort) Send(cmd string) (Resp, error) {
|
||||
cmd += "\r\n"
|
||||
rawResp, err := p.RawSend(cmd)
|
||||
err := p.RawSend(cmd + "\r\n")
|
||||
if err != nil {
|
||||
return RespNil, fmt.Errorf("make request: %w", err)
|
||||
return RespNil, fmt.Errorf("%s request: %w", cmd, err)
|
||||
}
|
||||
rawResp, err := p.RawRead(ReadTimeout)
|
||||
if err != nil {
|
||||
return RespNil, fmt.Errorf("%s request: %w", cmd, err)
|
||||
}
|
||||
|
||||
if len(rawResp) <= 4 {
|
||||
return RespNil, fmt.Errorf("read too small msg: %d byte - %s", len(rawResp), string(rawResp))
|
||||
return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp))
|
||||
}
|
||||
resp := rawResp[2 : len(rawResp)-2] // Cut \r\n
|
||||
|
||||
return Resp(resp), nil
|
||||
}
|
||||
|
||||
func (p *atPort) SendWithTimeout(cmd string, timeout time.Duration) (Resp, error) {
|
||||
err := p.RawSend(cmd + "\r\n")
|
||||
if err != nil {
|
||||
return RespNil, fmt.Errorf("%s request: %w", cmd, err)
|
||||
}
|
||||
rawResp, err := p.RawRead(timeout)
|
||||
if err != nil {
|
||||
return RespNil, fmt.Errorf("%s request: %w", cmd, err)
|
||||
}
|
||||
|
||||
if len(rawResp) <= 4 {
|
||||
return RespNil, fmt.Errorf("%s request: read too small msg: %d byte - %s", cmd, len(rawResp), string(rawResp))
|
||||
}
|
||||
resp := rawResp[2 : len(rawResp)-2] // Cut \r\n
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package gpio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
@ -8,6 +9,10 @@ import (
|
||||
gpio "github.com/stianeikeland/go-rpio/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
waitCtxTimeout = 100 * time.Microsecond
|
||||
)
|
||||
|
||||
type gpioPin struct {
|
||||
logger *log.Logger
|
||||
pin gpio.Pin
|
||||
@ -16,7 +21,7 @@ type gpioPin struct {
|
||||
type Pin interface {
|
||||
Init() error
|
||||
PowerOn()
|
||||
PowerOff()
|
||||
PowerOnCtx(ctx context.Context)
|
||||
io.Closer
|
||||
}
|
||||
|
||||
@ -31,32 +36,47 @@ func (p gpioPin) Init() error {
|
||||
return gpio.Open()
|
||||
}
|
||||
|
||||
func (p gpioPin) sendOnOffSignal() {
|
||||
func waitCtx(ctx context.Context, timeout time.Duration) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
if time.Now().After(deadline) {
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(waitCtxTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (p gpioPin) sendOnOffSignal(ctx context.Context) {
|
||||
p.pin.Output()
|
||||
p.logger.Println("Power on 0/3 + 100ms")
|
||||
p.pin.Low()
|
||||
p.pin.Toggle()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
waitCtx(ctx, 100*time.Millisecond)
|
||||
|
||||
p.logger.Println("Power on 1/3 + 3s")
|
||||
p.pin.High()
|
||||
p.pin.Toggle()
|
||||
time.Sleep(3 * time.Second)
|
||||
waitCtx(ctx, 3*time.Second)
|
||||
|
||||
p.logger.Println("Power on 2/3 + 30s")
|
||||
p.pin.Low()
|
||||
p.pin.Toggle()
|
||||
time.Sleep(30 * time.Second)
|
||||
waitCtx(ctx, 30*time.Second)
|
||||
|
||||
p.logger.Println("Power on 3/3")
|
||||
}
|
||||
|
||||
func (p gpioPin) PowerOn() {
|
||||
p.sendOnOffSignal()
|
||||
p.sendOnOffSignal(context.Background())
|
||||
}
|
||||
|
||||
func (p gpioPin) PowerOff() {
|
||||
p.sendOnOffSignal()
|
||||
func (p gpioPin) PowerOnCtx(ctx context.Context) {
|
||||
p.sendOnOffSignal(ctx)
|
||||
}
|
||||
|
||||
func (p gpioPin) Close() error {
|
||||
|
@ -11,10 +11,10 @@ import (
|
||||
)
|
||||
|
||||
type Data struct {
|
||||
Latitude float64 `json:"Latitude"`
|
||||
Longitude float64 `json:"Longitude"`
|
||||
LatitudeIndicator string `json:"Latitude_indicator"` // North/South
|
||||
LongitudeIndicator string `json:"Longitude_indicator"` // West/East
|
||||
Latitude float64 `json:"Latitude"` // ddmm.mmmmmm
|
||||
Longitude float64 `json:"Longitude"` // dddmm.mmmmmm
|
||||
LatitudeIndicator string `json:"Latitude_indicator"` // N/S - North/South
|
||||
LongitudeIndicator string `json:"Longitude_indicator"` // W/E - West/East
|
||||
Speed float64 `json:"Speed"`
|
||||
Course float64 `json:"-"`
|
||||
Altitude float64 `json:"-"`
|
||||
@ -59,8 +59,6 @@ func (gps *Data) decode(str string) error {
|
||||
if err != nil {
|
||||
logger.Println("ERROR parse longitude:", err.Error())
|
||||
}
|
||||
newGpsInfo.Latitude /= 100
|
||||
newGpsInfo.Longitude /= 100
|
||||
newGpsInfo.LatitudeIndicator = strs[1]
|
||||
newGpsInfo.LongitudeIndicator = strs[3]
|
||||
newGpsInfo.Date = strs[4]
|
||||
|
@ -85,7 +85,7 @@ func (g *gps) switchGpsMode(on bool) error {
|
||||
// Check gps mode status
|
||||
resp, err := g.port.Send("AT+CGPS?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("make at ask: %w", err)
|
||||
return err
|
||||
}
|
||||
if !resp.Check() || !resp.CheckFront("+CGPS:") {
|
||||
return fmt.Errorf("get GPS mode: error response: %s", resp)
|
||||
@ -98,7 +98,7 @@ func (g *gps) switchGpsMode(on bool) error {
|
||||
// Modem is not in GPS mode
|
||||
resp, err = g.port.Send("AT+CGPS=" + onStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set GPS mode: %w", err)
|
||||
return err
|
||||
}
|
||||
if !resp.Check() {
|
||||
return fmt.Errorf("set GPS mode: error response: %s", resp)
|
||||
|
@ -34,12 +34,14 @@ const (
|
||||
)
|
||||
|
||||
func secondCountDownTimer(title string, logger *log.Logger, t time.Duration) {
|
||||
_ = title
|
||||
_ = logger
|
||||
counter := 0
|
||||
for {
|
||||
if counter > int(t.Seconds()) {
|
||||
break
|
||||
}
|
||||
logger.Printf("%s: %d/%f\n", title, counter, t.Seconds())
|
||||
// logger.Printf("%s: %d/%f\n", title, counter, t.Seconds())
|
||||
time.Sleep(time.Second)
|
||||
counter += 1
|
||||
}
|
||||
@ -56,7 +58,7 @@ func (g *gps) rawCollect(flags nmeaFlags) (string, error) {
|
||||
|
||||
// Set output rate to 10Hz
|
||||
if resp, err := g.port.Send("AT+CGPSNMEARATE=1"); err != nil {
|
||||
return "", fmt.Errorf("AT+CGPSNMEARATE= request: %w", err)
|
||||
return "", err
|
||||
} else {
|
||||
if !resp.Check() {
|
||||
return "", fmt.Errorf("set output rate: error response: %s", resp)
|
||||
@ -118,7 +120,7 @@ func (g *gps) collectNmeaReports(flags nmeaFlags) ([]string, error) {
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
// g.logger.Println("NMEA raw collect:", resp)
|
||||
g.logger.Println("NMEA raw collect:", resp)
|
||||
|
||||
// Right responce struct:
|
||||
// \r\n
|
||||
|
@ -42,14 +42,14 @@ checkLoop:
|
||||
}
|
||||
st.GotResponses = true
|
||||
|
||||
g.logger.Println("NMEA check:", s)
|
||||
// g.logger.Println("NMEA check:", s)
|
||||
values := strings.Split(s, ",")
|
||||
if len(values[0]) != 6 {
|
||||
return StatusNil, fmt.Errorf("nmea invalid sentence: %s", s)
|
||||
}
|
||||
switch values[0][3:] { // Switch by content
|
||||
case "GSV": // Any satelites
|
||||
g.logger.Println("check GSV")
|
||||
// g.logger.Println("check GSV")
|
||||
// Check len
|
||||
if len(values) < 17 {
|
||||
g.logger.Println("GSV too small values")
|
||||
@ -83,7 +83,7 @@ checkLoop:
|
||||
}
|
||||
st.FoundSatelitesCount = satCount
|
||||
case "GSA": // Active satelites
|
||||
g.logger.Println("check GSA")
|
||||
// g.logger.Println("check GSA")
|
||||
|
||||
// Check len
|
||||
if len(values) < 17 {
|
||||
@ -107,7 +107,7 @@ checkLoop:
|
||||
}
|
||||
st.ActiveSatelitesCount = max(st.ActiveSatelitesCount, count)
|
||||
case "RMC": // Minimum GPS data
|
||||
g.logger.Println("check RMC")
|
||||
// g.logger.Println("check RMC")
|
||||
// Check len
|
||||
if len(values) < 12 {
|
||||
g.logger.Println("RMC too small values")
|
||||
@ -125,7 +125,7 @@ checkLoop:
|
||||
st.IsValidData = true
|
||||
}
|
||||
case "GST":
|
||||
g.logger.Println("check GST")
|
||||
// g.logger.Println("check GST")
|
||||
// Check len
|
||||
if len(values) < 8 {
|
||||
g.logger.Println("GST too small values")
|
||||
|
@ -1,51 +1,53 @@
|
||||
package internet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||
)
|
||||
|
||||
var apns = map[string]string{
|
||||
"Tinkoff": "m.tinkoff",
|
||||
}
|
||||
|
||||
const pppConfigName = "annalistnet"
|
||||
const pppConfig = `
|
||||
# Annalist project custom internet connection
|
||||
|
||||
# APN:
|
||||
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T %s"
|
||||
|
||||
# Port:
|
||||
%s
|
||||
|
||||
# Baudrate:
|
||||
%d
|
||||
|
||||
noipdefault
|
||||
usepeerdns
|
||||
defaultroute
|
||||
persist
|
||||
noauth
|
||||
nocrtscts
|
||||
local
|
||||
`
|
||||
const (
|
||||
pingPacketsCount = 3
|
||||
pingTimeout = 5
|
||||
inetConnectedTimeout = 4 * time.Second
|
||||
pingAddr = "8.8.8.8"
|
||||
ifName = "ppp0" // Interface name
|
||||
inetMetric = 2000
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
logger *log.Logger
|
||||
port at.Port
|
||||
|
||||
pppPort string
|
||||
|
||||
isConnectExecuted bool
|
||||
isInited bool
|
||||
isRouteSet bool
|
||||
|
||||
connectTime time.Time
|
||||
gw string // Gateway
|
||||
}
|
||||
|
||||
type Conn interface {
|
||||
Init() error
|
||||
ConfigurePPP() error
|
||||
Ping() bool // Is connected
|
||||
Init(pppPort string) error
|
||||
|
||||
Connect() error
|
||||
Disconnect() error
|
||||
|
||||
SetDefaultRouteTable() error
|
||||
UnsetDefaultRouteTable() error
|
||||
|
||||
IsConnected() bool // Check interface existance
|
||||
Ping() error
|
||||
|
||||
io.Closer
|
||||
}
|
||||
|
||||
@ -53,189 +55,256 @@ func New(logger *log.Logger, port at.Port) Conn {
|
||||
return &conn{
|
||||
logger: logger,
|
||||
port: port,
|
||||
|
||||
isConnectExecuted: false,
|
||||
isInited: false,
|
||||
isRouteSet: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) checkPackageExist(pname string) bool {
|
||||
resp, err := exec.Command("apt-mark", "showmanual", pname).Output()
|
||||
c.logger.Println("CHECK:", resp)
|
||||
if err != nil {
|
||||
c.logger.Println("CHECK PACKAGE ERROR: ", err.Error())
|
||||
return false
|
||||
}
|
||||
return string(resp[:len(pname)]) == pname
|
||||
}
|
||||
|
||||
func (c *conn) ensurePackage(pname string) error {
|
||||
if c.checkPackageExist(pname) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("package %s not installed", pname)
|
||||
// c.logger.Println("Installing", pname, "package...")
|
||||
// resp, err := exec.Command("apt-get", "install", pname).Output()
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("execute install cmd: %w", err)
|
||||
// }
|
||||
// c.logger.Println(resp)
|
||||
// c.logger.Println("\x1b[38;2;255;0;0mComplete\x1b[38;2;255;255;255m")
|
||||
// return nil
|
||||
}
|
||||
|
||||
// Check requirenments
|
||||
func (c *conn) checkReqs() error {
|
||||
// Check AT port for sure
|
||||
if c.port == nil || !c.port.IsConnected() {
|
||||
return fmt.Errorf("AT port is not connect or nil")
|
||||
}
|
||||
|
||||
// Ensure all necessary packages installed
|
||||
if err := c.ensurePackage("ppp"); err != nil {
|
||||
return fmt.Errorf("ensure ppp package: %w", err)
|
||||
}
|
||||
// if err := c.ensurePackage("net-tools"); err != nil {
|
||||
// return fmt.Errorf("ensure net-tools package: %w", err)
|
||||
// }
|
||||
|
||||
// Check SIM is valid
|
||||
// AT+CPIN? and just check
|
||||
resp, err := c.port.Send("AT+CPIN?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("AT+CPIN? request: %w", err)
|
||||
}
|
||||
if !resp.Check() {
|
||||
return fmt.Errorf("validate SIM: error response: %s", resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) ConfigurePPP() error {
|
||||
// Get provider name and its APN
|
||||
resp, err := c.port.Send("AT+CSPN?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("AT+CSPN? request: %w", err)
|
||||
}
|
||||
if !resp.Check() {
|
||||
return fmt.Errorf("get provider: error response: %s", resp)
|
||||
}
|
||||
strs := strings.Split(string(resp), "\"")
|
||||
if len(strs) < 3 {
|
||||
return fmt.Errorf("parse AT+CSPN response: %s", string(resp))
|
||||
}
|
||||
provider := strs[1]
|
||||
apn := apns[provider]
|
||||
if apn == "" {
|
||||
return fmt.Errorf("no apn for provider: %s", provider)
|
||||
}
|
||||
|
||||
// Make config
|
||||
c.logger.Printf("Config values: %s, %s, %d", apn, c.port.GetName(), c.port.GetBaudrate())
|
||||
config := fmt.Sprintf(pppConfig, apn, c.port.GetName(), c.port.GetBaudrate())
|
||||
|
||||
// Write to file
|
||||
f, err := os.OpenFile("/etc/ppp/peers/"+pppConfigName, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open ppp config file %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.Write([]byte(config)); err != nil {
|
||||
return fmt.Errorf("write to ppp config file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) setup() error {
|
||||
c.logger.Println("Check requirenments...")
|
||||
if err := c.checkReqs(); err != nil {
|
||||
return fmt.Errorf("check requirenments: %w", err)
|
||||
}
|
||||
// DEBUG show networks and signal
|
||||
resp, err := c.port.Send("AT+COPS?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("AT+COPS? request: %w", err)
|
||||
}
|
||||
c.logger.Println("DEBUG networks:", resp)
|
||||
|
||||
resp, err = c.port.Send("AT+CSQ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("AT+CSQ request: %w", err)
|
||||
}
|
||||
c.logger.Println("DEBUG signal quality:", resp)
|
||||
|
||||
// Configure ppp
|
||||
// what is better ASK If /etc/ppp/peers/annalistnet not exists
|
||||
c.logger.Println("Configure ppp...")
|
||||
if err := c.ConfigurePPP(); err != nil {
|
||||
return fmt.Errorf("configure ppp: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) connect() error {
|
||||
resp, err := exec.Command("pon", pppConfigName).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute connect cmd: %w", err)
|
||||
}
|
||||
c.logger.Println("DEBUG pon response:", string(resp))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) diconnect() error {
|
||||
resp, err := exec.Command("poff", pppConfigName).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute disconnect cmd: %w", err)
|
||||
}
|
||||
c.logger.Println("DEBUG poff response:", string(resp))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Init() error {
|
||||
// Setup
|
||||
c.logger.Println("Setup...")
|
||||
func (c *conn) Init(pppPort string) error {
|
||||
c.pppPort = pppPort
|
||||
// Setup only setup
|
||||
if err := c.setup(); err != nil {
|
||||
return fmt.Errorf("setup: %w", err)
|
||||
}
|
||||
// Connect
|
||||
c.logger.Println("Connect...")
|
||||
if err := c.connect(); err != nil {
|
||||
return fmt.Errorf("connect: %w", err)
|
||||
}
|
||||
c.isInited = true
|
||||
return nil
|
||||
}
|
||||
|
||||
//DEBUG
|
||||
resp, err := exec.Command("ifconfig").Output()
|
||||
func (c *conn) Connect() error {
|
||||
if !c.isInited {
|
||||
return fmt.Errorf("internet submodule is not inited")
|
||||
}
|
||||
// Check is already connected
|
||||
if c.isConnectExecuted {
|
||||
return fmt.Errorf("already connected")
|
||||
}
|
||||
// Check signal
|
||||
|
||||
resp, err := exec.Command("pon", pppConfigName).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute ifconfig cmd: %w", err)
|
||||
return fmt.Errorf("execute pon cmd: %w", err)
|
||||
}
|
||||
if len(resp) > 0 {
|
||||
c.logger.Println("pon response:", string(resp))
|
||||
}
|
||||
c.logger.Println("DEBUG ifconfig resp:", string(resp))
|
||||
|
||||
// Test connectin using Ping
|
||||
c.logger.Println("Test...")
|
||||
if !c.Ping() {
|
||||
return fmt.Errorf("ping failed")
|
||||
c.isConnectExecuted = true
|
||||
c.connectTime = time.Now()
|
||||
c.gw, err = c.GetHostIp()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get host ip: %w", err)
|
||||
}
|
||||
c.logger.Println("\x1b[38;2;0;255;0mInternet connected.\x1b[38;2;255;255;255m")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Disconnect() error {
|
||||
// return nil // Temporary do not turn off inet
|
||||
if !c.isConnectExecuted {
|
||||
return nil
|
||||
// return fmt.Errorf("internet is not connected")
|
||||
}
|
||||
c.isConnectExecuted = false
|
||||
resp, err := exec.Command("poff", pppConfigName).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute poff cmd: %w", err)
|
||||
}
|
||||
if len(resp) > 0 {
|
||||
c.logger.Println("poff response:", string(resp))
|
||||
}
|
||||
c.isConnectExecuted = false
|
||||
c.logger.Println("\x1b[38;2;0;255;0mInternet disconnected.\x1b[38;2;255;255;255m")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) SetDefaultRouteTable() error {
|
||||
// route add -net default gw 10.64.64.64 metric 2000 dev ppp0
|
||||
resp, err := exec.Command("route", "add", "-net", "default", "gw", c.gw, "metric", strconv.Itoa(inetMetric), "dev", ifName).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute add cmd: %w", err)
|
||||
}
|
||||
// Check response
|
||||
if len(resp) != 0 {
|
||||
c.logger.Println("Not nil response:", string(resp))
|
||||
}
|
||||
c.isRouteSet = true
|
||||
c.logger.Println("\x1b[38;2;0;255;0mInternet route table set.\x1b[38;2;255;255;255m")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) UnsetDefaultRouteTable() error {
|
||||
if !c.isRouteSet {
|
||||
return fmt.Errorf("route table is not set")
|
||||
}
|
||||
resp, err := exec.Command("route", "del", "-net", "default", "gw", c.gw, "metric", strconv.Itoa(inetMetric), "dev", ifName).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute del cmd: %w", err)
|
||||
}
|
||||
// Check response
|
||||
if len(resp) != 0 {
|
||||
c.logger.Println("Not nil response:", string(resp))
|
||||
}
|
||||
c.logger.Println("\x1b[38;2;0;255;0mInternet route table unset.\x1b[38;2;255;255;255m")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) ping(flags []string, timeout int) error {
|
||||
c.logger.Println("Ping", flags[len(flags)-1])
|
||||
|
||||
// Just counter
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func(c *conn, ctx context.Context) {
|
||||
for i := 0; i < timeout; i++ {
|
||||
c.logger.Printf("Ping %d/%d", i, timeout)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}
|
||||
}(c, ctx)
|
||||
|
||||
// Executing cmd
|
||||
cmd := exec.Command("ping", flags...)
|
||||
resp, err := cmd.Output()
|
||||
cancel()
|
||||
if err != nil {
|
||||
c.logger.Println("Ping default interface cmd error:", err)
|
||||
}
|
||||
|
||||
// Parse
|
||||
lines := strings.Split(string(resp), "\n")
|
||||
|
||||
// Look for string "--- *.*.*.* ping statistics ---" by first simbol '-'
|
||||
stLineI := 0
|
||||
searchStLineLoop:
|
||||
for i, l := range lines {
|
||||
if len(l) > 0 && l[0] == '-' {
|
||||
stLineI = i + 1
|
||||
break searchStLineLoop
|
||||
}
|
||||
}
|
||||
if stLineI == 0 || stLineI >= len(lines) {
|
||||
return fmt.Errorf("failed to find statistics line: %d", stLineI)
|
||||
}
|
||||
stStr := lines[stLineI]
|
||||
|
||||
// Get third value "packet lost"
|
||||
values := strings.Split(stStr, ",")
|
||||
if len(values) < 3 {
|
||||
return fmt.Errorf("invalid statistic values(len): [%s]", values)
|
||||
}
|
||||
|
||||
// Get number
|
||||
words := strings.Split(values[2], " ")
|
||||
if len(words) < 2 {
|
||||
return fmt.Errorf("invalid \"packets lost\" value(words count): [%s]", words)
|
||||
}
|
||||
// First is ''
|
||||
// Second is '...%'
|
||||
packetsLost, err := strconv.Atoi(words[1][:len(words[1])-1]) // Without '%' char
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse \"packets lost\" value: %w", err)
|
||||
}
|
||||
if packetsLost == 100 {
|
||||
return fmt.Errorf("lost all packages")
|
||||
}
|
||||
if packetsLost > 0 {
|
||||
c.logger.Printf("lost some packets: %d%%\n", packetsLost)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Ping() bool {
|
||||
// Test - try to connect to Google DNS
|
||||
// ping -I ppp0 8.8.8.8
|
||||
resp, err := exec.Command("ping", "8.8.8.8").Output()
|
||||
if err != nil {
|
||||
c.logger.Println("Ping 1 cmd error:", err)
|
||||
func (c *conn) GetHostIp() (string, error) {
|
||||
if !c.isConnectExecuted {
|
||||
return "", fmt.Errorf("internet not connected")
|
||||
}
|
||||
c.logger.Println("Ping 1 resp:", string(resp))
|
||||
// Wait some time for system to setup route table
|
||||
time.Sleep(time.Until(c.connectTime.Add(inetConnectedTimeout)))
|
||||
|
||||
resp, err = exec.Command("ping", "-I", "ppp0", "8.8.8.8").Output()
|
||||
// Execute cmd
|
||||
resp, err := exec.Command("route").Output()
|
||||
if err != nil {
|
||||
c.logger.Println("Ping 2 cmd error:", err)
|
||||
return "", fmt.Errorf("exec route cmd: %w", err)
|
||||
}
|
||||
c.logger.Println("Ping 2 resp:", string(resp))
|
||||
// Check and split to lines
|
||||
lines := strings.Split(string(resp), "\n")
|
||||
if len(lines) <= 3 || lines[0] != "Kernel IP routing table" {
|
||||
return "", fmt.Errorf("invalid route response: [% s]", lines)
|
||||
}
|
||||
// Search line about ppp interface
|
||||
searchLoop:
|
||||
for _, l := range lines[1:] {
|
||||
words := strings.Fields(l)
|
||||
if len(words) != 8 {
|
||||
/// c.logger.Printf("invalid route line(words number): [%s]\n", words)
|
||||
continue searchLoop
|
||||
}
|
||||
if words[7] == ifName {
|
||||
if words[3] != "UH" {
|
||||
// c.logger.Println("invalid flags")
|
||||
continue searchLoop
|
||||
}
|
||||
return words[0], nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("found no suitable ppp interface")
|
||||
}
|
||||
|
||||
return !(strings.Contains(string(resp), "Destination Host Unreachable") || strings.Contains(string(resp), "Destination Net Unreachable")) // tmp solution
|
||||
func (c *conn) PingDefault() error {
|
||||
return c.ping([]string{"-c", strconv.Itoa(pingPacketsCount), "-w", strconv.Itoa(pingTimeout), pingAddr}, pingTimeout)
|
||||
}
|
||||
|
||||
func (c *conn) PingPPP() error {
|
||||
return c.ping([]string{"-I", ifName, "-c", string(pingPacketsCount), "-w", string(pingTimeout), pingAddr}, pingTimeout)
|
||||
}
|
||||
|
||||
func (c *conn) Ping() error {
|
||||
return c.PingDefault()
|
||||
}
|
||||
|
||||
func (c *conn) IsConnected() bool {
|
||||
if !c.isConnectExecuted {
|
||||
return false
|
||||
}
|
||||
// Make "ifconfig" request
|
||||
resp, err := exec.Command("ifconfig").Output()
|
||||
if err != nil {
|
||||
c.logger.Println("ifconfig cmd error:", err.Error())
|
||||
return false
|
||||
}
|
||||
lines := strings.Split(string(resp), "\n")
|
||||
for _, l := range lines {
|
||||
if len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
if l[0] == ' ' {
|
||||
continue
|
||||
}
|
||||
interfaceName := strings.Split(l, ":")[0]
|
||||
if interfaceName == ifName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false // Did not found
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
if err := c.diconnect(); err != nil {
|
||||
return fmt.Errorf("diconnect: %w", err)
|
||||
c.isInited = false
|
||||
// Unset route table
|
||||
if c.isRouteSet {
|
||||
if err := c.UnsetDefaultRouteTable(); err != nil {
|
||||
c.logger.Println("unset route table error:", err.Error())
|
||||
}
|
||||
}
|
||||
// Disconnect
|
||||
if c.isConnectExecuted {
|
||||
if err := c.Disconnect(); err != nil {
|
||||
c.logger.Println("diconnect error:", err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
131
api/modem/internet/setup.go
Normal file
131
api/modem/internet/setup.go
Normal file
@ -0,0 +1,131 @@
|
||||
package internet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/utils"
|
||||
)
|
||||
|
||||
var apns = map[string]string{
|
||||
"Tinkoff": "m.tinkoff",
|
||||
"Megafon": "internet",
|
||||
"BeeLine": "internet.beeline.ru",
|
||||
}
|
||||
|
||||
const pppConfigName = "annalistnet"
|
||||
const pppConfig = `
|
||||
# Annalist project custom internet connection
|
||||
|
||||
# APN:
|
||||
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T %s"
|
||||
|
||||
# Port:
|
||||
%s
|
||||
|
||||
# Baudrate:
|
||||
%d
|
||||
|
||||
noipdefault
|
||||
usepeerdns
|
||||
defaultroute
|
||||
persist
|
||||
noauth
|
||||
nocrtscts
|
||||
local
|
||||
`
|
||||
|
||||
func (c *conn) setup() error {
|
||||
if err := c.checkReqs(); err != nil {
|
||||
return fmt.Errorf("check requirenments: %w", err)
|
||||
}
|
||||
// Configure ppp
|
||||
// what is better ASK If /etc/ppp/peers/annalistnet not exists
|
||||
if err := c.configurePPP(); err != nil {
|
||||
return fmt.Errorf("configure ppp: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check requirenments
|
||||
func (c *conn) checkReqs() error {
|
||||
// Check AT port for sure
|
||||
if c.port == nil || !c.port.IsConnected() {
|
||||
return fmt.Errorf("AT port is not connect or nil")
|
||||
}
|
||||
|
||||
// Ensure all necessary packages installed
|
||||
if err := c.ensurePackage("ppp"); err != nil {
|
||||
return fmt.Errorf("ensure ppp package: %w", err)
|
||||
}
|
||||
// if err := c.ensurePackage("net-tools"); err != nil {
|
||||
// return fmt.Errorf("ensure net-tools package: %w", err)
|
||||
// }
|
||||
|
||||
// Check SIM is valid
|
||||
if err := utils.CheckPIN(c.port, c.logger); err != nil {
|
||||
return fmt.Errorf("PIN check: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) ensurePackage(pname string) error {
|
||||
if c.checkPackageExist(pname) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("package %s not installed", pname)
|
||||
// c.logger.Println("Installing", pname, "package...")
|
||||
// resp, err := exec.Command("apt-get", "install", pname).Output()
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("execute install cmd: %w", err)
|
||||
// }
|
||||
// c.logger.Println(resp)
|
||||
// c.logger.Println("\x1b[38;2;255;0;0mComplete\x1b[38;2;255;255;255m")
|
||||
// return nil
|
||||
}
|
||||
|
||||
func (c *conn) checkPackageExist(pname string) bool {
|
||||
resp, err := exec.Command("apt-mark", "showmanual", pname).Output()
|
||||
if err != nil {
|
||||
c.logger.Println("check package error: ", err.Error())
|
||||
return false
|
||||
}
|
||||
return string(resp[:len(pname)]) == pname
|
||||
}
|
||||
|
||||
func (c *conn) configurePPP() error {
|
||||
// Get provider name and its APN
|
||||
resp, err := c.port.Send("AT+CSPN?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("AT+CSPN? request: %w", err)
|
||||
}
|
||||
if !resp.Check() {
|
||||
return fmt.Errorf("get provider: error response: %s", resp)
|
||||
}
|
||||
strs := strings.Split(string(resp), "\"")
|
||||
if len(strs) < 3 {
|
||||
return fmt.Errorf("parse AT+CSPN response: %s", string(resp))
|
||||
}
|
||||
provider := strs[1]
|
||||
apn := apns[provider]
|
||||
if apn == "" {
|
||||
return fmt.Errorf("no apn for provider: %s", provider)
|
||||
}
|
||||
|
||||
// Make config
|
||||
c.logger.Printf("Config ppp values: %s, %s, %d", apn, c.pppPort, c.port.GetBaudrate())
|
||||
config := fmt.Sprintf(pppConfig, apn, c.pppPort, c.port.GetBaudrate())
|
||||
|
||||
// Write to file
|
||||
f, err := os.OpenFile("/etc/ppp/peers/"+pppConfigName, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open ppp config file %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.Write([]byte(config)); err != nil {
|
||||
return fmt.Errorf("write to ppp config file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
package modem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -17,6 +19,12 @@ import (
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/sms"
|
||||
)
|
||||
|
||||
// yy/MM/dd,hh:mm:ss+zzzz
|
||||
const timeLayout = "06/01/02,15:04:05-0700"
|
||||
|
||||
var ttyPorts = []string{"ttyUSB1", "ttyUSB2", "ttyUSB3", "ttyS0", "ttyAMA2"}
|
||||
var availableModels = []string{"SIMCOM_SIM7600E-H", "SIMCOM_SIM808"}
|
||||
|
||||
type ModemData struct {
|
||||
Port string `json:"Port"`
|
||||
gps.Data
|
||||
@ -31,6 +39,7 @@ type modem struct {
|
||||
baudrate int
|
||||
deviceName string
|
||||
port at.Port
|
||||
model string
|
||||
|
||||
// Gpio values
|
||||
onOffPin gpio.Pin // For turning on and off
|
||||
@ -53,14 +62,17 @@ type Modem interface {
|
||||
IsConnected() bool
|
||||
Update() error
|
||||
GetData() ModemData
|
||||
GetTime() (time.Time, error)
|
||||
|
||||
PowerOn() error
|
||||
PowerOnCtx(ctx context.Context) error // Because it takes ~30 seconds
|
||||
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
|
||||
At() at.Port // Send
|
||||
Gps() gps.Gps // Update, GetData, GetStatus
|
||||
Sms() sms.Sms // Send, ReadNew
|
||||
Ic() internet.Conn // Connect, Disconnect
|
||||
|
||||
io.Closer
|
||||
}
|
||||
@ -80,29 +92,41 @@ func (m *modem) Init() error {
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Turn module on
|
||||
m.logger.Println("=============================== Turn on module")
|
||||
m.logger.Println("=============================== Init gpio")
|
||||
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 {
|
||||
ports, err := m.searchPort(true)
|
||||
if 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 len(ports) == 0 {
|
||||
// Wide search
|
||||
ports, err := m.searchPort(true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wide port search: %w", err)
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
return fmt.Errorf("no AT ports found")
|
||||
}
|
||||
}
|
||||
if m.port == nil {
|
||||
return fmt.Errorf("no port is detected")
|
||||
if len(ports) == 1 {
|
||||
// return fmt.Errorf("only one AT port found")
|
||||
m.logger.Println("!!!!! only one AT port found")
|
||||
}
|
||||
|
||||
// !!!!
|
||||
// Internet connection and serial connection on one port is impossible, so:
|
||||
// port[0] is for serial
|
||||
// port[1] is for internet(ppp)
|
||||
m.logger.Println(ports)
|
||||
|
||||
// Connect
|
||||
m.logger.Println("=============================== Connect")
|
||||
m.port = at.New(m.logger, ports[0], m.baudrate)
|
||||
if err := m.connect(); err != nil {
|
||||
return fmt.Errorf("connect: %w", err)
|
||||
}
|
||||
@ -113,18 +137,18 @@ func (m *modem) Init() error {
|
||||
|
||||
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 {
|
||||
if err := m.ic.Init(ports[len(ports)-1]); 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.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 {
|
||||
@ -170,16 +194,58 @@ func (m *modem) GetData() ModemData {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *modem) GetTime() (time.Time, error) {
|
||||
// Make request
|
||||
resp, err := m.port.Send("AT+CCLK?")
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
if !resp.Check() || !resp.CheckFront("+CCLK: ") {
|
||||
return time.Time{}, fmt.Errorf("CCLK? error response: %s", resp.String())
|
||||
}
|
||||
// Extract time string
|
||||
values := strings.Split(strings.Split(resp.String(), "\n")[0], "\"")
|
||||
if len(values) < 2 {
|
||||
return time.Time{}, fmt.Errorf("invalid values (len): [%s]", values)
|
||||
}
|
||||
timeStr := values[1]
|
||||
m.logger.Println("Raw time:", timeStr)
|
||||
if len(timeStr) != len("yy/MM/dd,hh:mm:ss+zz") {
|
||||
return time.Time{}, fmt.Errorf("invalid time string: %s", timeStr)
|
||||
}
|
||||
// Convert time zone
|
||||
timeZoneStr := timeStr[len(timeStr)-2:]
|
||||
timeZone, err := strconv.Atoi(timeZoneStr)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("parse time zone: %w", err)
|
||||
}
|
||||
timeStr = fmt.Sprintf("%s%02d%02d", timeStr[:len(timeStr)-2], timeZone/4, timeZone%4)
|
||||
// Parse to golang time
|
||||
return time.Parse(timeLayout, timeStr)
|
||||
}
|
||||
|
||||
func (m *modem) PowerOn() error {
|
||||
m.onOffPin.PowerOn() // DEBUG do not want to wait 30 seconds
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modem) PowerOnCtx(ctx context.Context) error {
|
||||
m.onOffPin.PowerOnCtx(ctx) // 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) restart() error {
|
||||
m.PowerOff()
|
||||
time.Sleep(10 * time.Second)
|
||||
m.PowerOn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modem) Sms() sms.Sms {
|
||||
return m.sms
|
||||
}
|
||||
@ -192,24 +258,38 @@ func (m *modem) At() at.Port {
|
||||
return m.port
|
||||
}
|
||||
|
||||
func (m *modem) Ic() internet.Conn {
|
||||
return m.ic
|
||||
}
|
||||
|
||||
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())
|
||||
// Close submodules
|
||||
if m.sms != nil {
|
||||
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 m.ic != nil {
|
||||
if err := m.ic.Close(); err != nil {
|
||||
m.logger.Printf("\x1b[38;2;255;0;0mclose internet error: %s\x1b[38;2;255;255;255m\n", err.Error())
|
||||
}
|
||||
}
|
||||
if m.gps != nil {
|
||||
if err := m.gps.Close(); err != nil {
|
||||
m.logger.Printf("\x1b[38;2;255;0;0mclose gps 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())
|
||||
}
|
||||
// Close gpio and serial
|
||||
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())
|
||||
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())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -269,10 +349,10 @@ func (m *modem) saveGPS(path string) error {
|
||||
// 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())
|
||||
m.logger.Println("FAILED TO SEND REQ", cmd, "===>", err.Error())
|
||||
} else {
|
||||
_ = resp
|
||||
// m.logger.Println("CMD", cmd, ":", resp)
|
||||
// m.logger.Println("CMD", cmd, "===>", resp)
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,14 +367,46 @@ func (m *modem) setupPort() error {
|
||||
return fmt.Errorf("reset output buffer: %w", err)
|
||||
}
|
||||
|
||||
// m.restart()
|
||||
// These commands ensure that correct modes are set
|
||||
// m.port.RawSend("\r\n\x1A\r\n") // Sometimes enables echo mode
|
||||
//if m.At().GetName() == "/dev/ttyUSB0" {
|
||||
// buf := make([]byte, 256)
|
||||
// for i := 0; i < 10; i++ {
|
||||
// len, err := m.port.SerialPort().Read(buf)
|
||||
// if err != nil {
|
||||
// m.logger.Println("ERROR:", err.Error())
|
||||
// }
|
||||
// if len != 0 {
|
||||
// m.logger.Println(string(buf[:len]))
|
||||
// }
|
||||
// time.Sleep(time.Second)
|
||||
// m.logger.Println(".")
|
||||
// }
|
||||
//}
|
||||
m.printCmd("AT") // Sometimes enables echo mode
|
||||
m.printCmd("AT") // Sometimes enables echo mode
|
||||
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
|
||||
m.printCmd("AT+CTZU=1") // Turn on time update
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modem) checkCurPortDead() error {
|
||||
if err := m.port.RawSend("AT\r\n"); err != nil {
|
||||
return fmt.Errorf("raw send: %w", err)
|
||||
}
|
||||
if resp, err := m.port.RawRead(time.Second); err != nil || len(resp) == 0 {
|
||||
if err != nil {
|
||||
return fmt.Errorf("raw read: %w", err)
|
||||
}
|
||||
return fmt.Errorf("read 0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modem) checkPort(portName string) (outErr error) {
|
||||
defer func() {
|
||||
if outErr != nil { // Clear port if there is error
|
||||
@ -315,9 +427,12 @@ func (m *modem) checkPort(portName string) (outErr error) {
|
||||
}
|
||||
defer m.port.Disconnect() // Do not bother about errors...
|
||||
|
||||
// m.restart()
|
||||
|
||||
// To filter dead ports
|
||||
if _, err := m.port.Send("AT"); err != nil {
|
||||
return fmt.Errorf("ping error: %w", err)
|
||||
|
||||
if err := m.checkCurPortDead(); err != nil {
|
||||
return fmt.Errorf("echo: %w", err)
|
||||
}
|
||||
|
||||
if err := m.setupPort(); err != nil {
|
||||
@ -344,43 +459,23 @@ func (m *modem) checkPort(portName string) (outErr error) {
|
||||
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)
|
||||
|
||||
// Check model
|
||||
foundModel := ""
|
||||
for _, rightModel := range availableModels {
|
||||
if len(model) >= len(rightModel) && model[:len(rightModel)] == rightModel {
|
||||
foundModel = rightModel
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundModel == "" {
|
||||
return fmt.Errorf("invalid model: %s", model)
|
||||
}
|
||||
m.model = foundModel
|
||||
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 {
|
||||
@ -392,10 +487,39 @@ func (m *modem) ping() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTtyDevices() ([]string, error) {
|
||||
func (m *modem) searchPort(isSoft bool) ([]string, error) {
|
||||
// Get ports
|
||||
ports, err := getTtyPorts(isSoft)
|
||||
if err != nil {
|
||||
fmt.Errorf("get devices: %w", err)
|
||||
}
|
||||
// Check ports
|
||||
return m.getAtPorts(ports)
|
||||
}
|
||||
|
||||
func (m *modem) getAtPorts(ports []string) ([]string, error) {
|
||||
outPorts := make([]string, 0)
|
||||
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
|
||||
}
|
||||
|
||||
m.logger.Print("Found AT port: ", p)
|
||||
outPorts = append(outPorts, "/dev/"+p)
|
||||
}
|
||||
return outPorts, nil
|
||||
}
|
||||
|
||||
func getTtyPorts(isSoft bool) ([]string, error) {
|
||||
if isSoft {
|
||||
return ttyPorts, nil
|
||||
}
|
||||
// Get ports
|
||||
/**/
|
||||
out, err := exec.Command("ls", "--", "/dev/tty[!0-9]*").Output()
|
||||
out, err := exec.Command("ls", "/dev/tty[!0-9]*").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute ls command: %w", err)
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func DecodeError(code int) string {
|
||||
switch code {
|
||||
case 300:
|
||||
return "ME failure"
|
||||
case 301:
|
||||
return "SMS service of ME reserved"
|
||||
case 302:
|
||||
return "Operation not allowed"
|
||||
case 303:
|
||||
return "Operation not supported"
|
||||
case 304:
|
||||
return "Invalid PDU mode parameter"
|
||||
case 305:
|
||||
return "Invalid text mode parameter"
|
||||
case 310:
|
||||
return "SIM not inserted"
|
||||
case 311:
|
||||
return "SIM PIN required"
|
||||
case 312:
|
||||
return "PH-SIM PIN required"
|
||||
case 313:
|
||||
return "SIM failure"
|
||||
case 314:
|
||||
return "SIM busy"
|
||||
case 315:
|
||||
return "SIM wrong"
|
||||
case 316:
|
||||
return "SIM PUK required"
|
||||
case 317:
|
||||
return "SIM PIN2 required"
|
||||
case 318:
|
||||
return "SIM PUK2 required"
|
||||
case 320:
|
||||
return "Memory failure"
|
||||
case 321:
|
||||
return "Invalid memory index"
|
||||
case 322:
|
||||
return "Memory full"
|
||||
case 330:
|
||||
return "SMSC address unknown"
|
||||
case 331:
|
||||
return "No network service"
|
||||
case 332:
|
||||
return "Network timeout"
|
||||
case 340:
|
||||
return "NO +CNMA ACK EXPECTED"
|
||||
case 341:
|
||||
return "Buffer overflow"
|
||||
case 342:
|
||||
return "SMS size more than expected"
|
||||
case 500:
|
||||
return "Unknown error"
|
||||
}
|
||||
return "UNDEFINED ERROR CODE"
|
||||
}
|
||||
|
||||
func GetError(msg []byte) (int, error) {
|
||||
if len(msg) >= len("+CMS ERROR: ")+3 && string(msg[:len("+CMS ERROR: ")]) == "+CMS ERROR: " {
|
||||
return strconv.Atoi(string(msg[len("+CMS ERROR: ") : len("+CMS ERROR: ")+3]))
|
||||
}
|
||||
return 0, fmt.Errorf("failed to parse error")
|
||||
}
|
88
api/modem/sms/setup.go
Normal file
88
api/modem/sms/setup.go
Normal file
@ -0,0 +1,88 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (d *dialer) setupMsgSt() error {
|
||||
// Check for free space for messages
|
||||
// !!! I use one! memory for all three bindings
|
||||
// 1: read and delete
|
||||
// 2: sending
|
||||
// 3: write received
|
||||
|
||||
// First try SM
|
||||
if _, err := d.port.Send(`AT+CPMS="SM","SM","SM"`); err != nil {
|
||||
return fmt.Errorf(`AT+CPMS="SM","SM","SM" request: %w`, err)
|
||||
}
|
||||
st, err := d.getCurMsgStSize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("SM: %w", err)
|
||||
}
|
||||
if st[0].Used < st[0].Total {
|
||||
d.logger.Printf("Use SM message storage: %d/%d\n", st[0].Used, st[0].Total)
|
||||
return nil // There is space
|
||||
}
|
||||
d.logger.Printf("SM message storage is full: %d/%d\n", st[0].Used, st[0].Total)
|
||||
|
||||
// Second try ME
|
||||
if _, err := d.port.Send(`AT+CPMS="ME","ME","ME"`); err != nil {
|
||||
return fmt.Errorf(`AT+CPMS="ME","ME","ME" request: %w`, err)
|
||||
}
|
||||
st, err = d.getCurMsgStSize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ME: %w", err)
|
||||
}
|
||||
if st[0].Used < st[0].Total {
|
||||
d.logger.Printf("Use ME message storage: %d/%d\n", st[0].Used, st[0].Total)
|
||||
return nil // There is space
|
||||
}
|
||||
d.logger.Printf("ME message storage is full: %d/%d\n", st[0].Used, st[0].Total)
|
||||
// Otherwise error
|
||||
return fmt.Errorf("all storages are full")
|
||||
}
|
||||
|
||||
// Message storage
|
||||
type msgSt struct {
|
||||
Name string
|
||||
Used int
|
||||
Total int
|
||||
}
|
||||
|
||||
// Get size of used and total mem of current memory storage
|
||||
func (d *dialer) getCurMsgStSize() ([]msgSt, error) {
|
||||
// Request
|
||||
resp, err := d.port.Send("AT+CPMS?")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AT+CPMS? request: %w", err)
|
||||
}
|
||||
// Check start and end
|
||||
if !resp.Check() && !resp.CheckFront("+CPMS:") {
|
||||
return nil, fmt.Errorf("AT+CPMS")
|
||||
}
|
||||
// Remove front and cut to values
|
||||
resp = resp.RmFront("+CPMS:")
|
||||
values := strings.Split(strings.ReplaceAll(strings.Split(resp.String(), "\n")[0], "\r", ""), ",")
|
||||
if len(values) != 9 {
|
||||
return nil, fmt.Errorf("CPMS response: invalid values count: [%s]", values)
|
||||
}
|
||||
|
||||
// Parse values
|
||||
outMsgs := [3]msgSt{}
|
||||
for i := 0; i < 3; i++ {
|
||||
name := values[i]
|
||||
used, err := strconv.Atoi(values[i*3+1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse value #%d: %w", i+1, err)
|
||||
}
|
||||
total, err := strconv.Atoi(values[i*3+2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse value #%d, %w", i+2, err)
|
||||
}
|
||||
|
||||
outMsgs[i] = msgSt{name, used, total}
|
||||
}
|
||||
return outMsgs[:], nil
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/utils"
|
||||
)
|
||||
|
||||
type dialer struct {
|
||||
@ -30,55 +31,66 @@ func New(logger *log.Logger, port at.Port) Sms {
|
||||
}
|
||||
|
||||
func (d *dialer) Init() error {
|
||||
// Ensure serial port
|
||||
// Ensure serial port is connected
|
||||
if !d.port.IsConnected() {
|
||||
return fmt.Errorf("serial port is not connected")
|
||||
}
|
||||
// Check ping
|
||||
if err := utils.CheckPIN(d.port, d.logger); err != nil {
|
||||
return fmt.Errorf("check PIN: %w", err)
|
||||
}
|
||||
// Setup prefered message storage
|
||||
// if err := d.setupMsgSt(); err != nil { // Does not use store now
|
||||
// d.logger.Printf("ERROR setup msg storage: %s\n", err.Error())
|
||||
// }
|
||||
|
||||
// Check SIM an PIN
|
||||
if resp, err := d.port.Send("AT+CPIN?"); err != nil || !resp.Check() {
|
||||
// Set notifications into console
|
||||
if resp, err := d.port.Send("AT+CNMI=2,2"); err != nil || !resp.Check() {
|
||||
if err != nil {
|
||||
return fmt.Errorf("check pin: %w", err)
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("check pin: error response: %s", resp)
|
||||
return fmt.Errorf("CNMI= error response: %s", resp.String())
|
||||
}
|
||||
|
||||
// Ensure text format
|
||||
d.logger.Println(d.port.Send("AT+CMGF"))
|
||||
if resp, err := d.port.Send("AT+CMGF=1"); err != nil || !resp.Check() {
|
||||
// Check number
|
||||
if resp, err := d.port.Send("AT+CNUM"); err != nil || !resp.Check() {
|
||||
if err != nil {
|
||||
return fmt.Errorf("set to text format request: %w", err)
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("set SIM format: error response: %s", resp)
|
||||
return fmt.Errorf("CNUM error response: %s", resp.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dialer) Send(number, msg string) error {
|
||||
d.port.Send(fmt.Sprintf(`AT+CMGS="%s"`, number)) // Because it will throw error
|
||||
resp, err := d.port.RawSend(fmt.Sprintf("%s\n\r", msg)) // Add additional \r\n because there is not supposed to be
|
||||
sresp, err := d.port.Send(fmt.Sprintf(`AT+CMGSEX="%s"`, number)) // Because it will throw error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.logger.Println(sresp)
|
||||
|
||||
// Message body
|
||||
if err := d.port.RawSend(fmt.Sprintf("%s\x1A", msg)); err != nil {
|
||||
return fmt.Errorf("message request: %w", err)
|
||||
}
|
||||
d.logger.Println("SEND RESPONSE:", resp)
|
||||
resp, err = d.port.RawSend("\x1A")
|
||||
resp, err := d.port.RawRead(at.ReadTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("message request: %w", err)
|
||||
return fmt.Errorf("message request read: %w", err)
|
||||
}
|
||||
d.logger.Println("SEND RESPONSE:", resp)
|
||||
errCode, err := GetError([]byte(resp))
|
||||
if err != nil {
|
||||
return fmt.Errorf("send sms failed and failed to get error: %w", err)
|
||||
d.logger.Println("Send response:", resp)
|
||||
if !at.Resp(resp).Check() {
|
||||
return fmt.Errorf("error response: %s", resp)
|
||||
}
|
||||
return fmt.Errorf("failed to send with SMS error: %d - %s", errCode, DecodeError(errCode))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reads all new messages
|
||||
func (d *dialer) ReadNew() ([]string, error) {
|
||||
resp, err := d.port.Send("AT+CMGL=\"UNREAD\"")
|
||||
resp, err := d.port.Send("AT+CMGL=\"ALL\"")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AT+CMGL request: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
d.logger.Println("raw sms:", resp)
|
||||
msgs := strings.Split(strings.Replace(string(resp), "\r", "", -1), "\n")
|
||||
|
||||
outMsgs := make([]string, 0)
|
||||
|
37
api/modem/utils/service.go
Normal file
37
api/modem/utils/service.go
Normal file
@ -0,0 +1,37 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||
)
|
||||
|
||||
func CheckService(port at.Port, logger *log.Logger) (bool, error) {
|
||||
srv, err := getService(port)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get service: %w", err)
|
||||
}
|
||||
if srv == "NO SERVICE" {
|
||||
return false, nil
|
||||
}
|
||||
logger.Println("Current service:", srv)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Returns service
|
||||
func getService(port at.Port) (string, error) {
|
||||
resp, err := port.Send("AT+CPSI?")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !resp.Check() || !resp.CheckFront("+CPSI: ") {
|
||||
return "", fmt.Errorf("error response: %s", resp)
|
||||
}
|
||||
values := strings.Split(strings.ReplaceAll(strings.Split(resp.RmFront("+CPSI: ").String(), "\n")[0], "\r", ""), ",")
|
||||
if len(values) < 2 {
|
||||
return "", fmt.Errorf("invalid values(len): [% s]", values)
|
||||
}
|
||||
return values[0], nil
|
||||
}
|
109
api/modem/utils/signal.go
Normal file
109
api/modem/utils/signal.go
Normal file
@ -0,0 +1,109 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||
)
|
||||
|
||||
func CheckSignal(port at.Port, logger *log.Logger) (bool, error) {
|
||||
rssi, ber, err := getSignalQuality(port)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get signal quality: %w", err)
|
||||
}
|
||||
logger.Printf("check signal: rssi=%d ber=%d\n", rssi, ber)
|
||||
if err := checkRssi(rssi); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
if err := checkBer(ber); err != nil {
|
||||
logger.Printf("bad ber(not critical): %s", err.Error()) // Happened not to be critical
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func checkRssi(rssi int) error {
|
||||
// rssi - Received signal strength indicator
|
||||
// 0 -113 dBm or less
|
||||
// 1 -111 dBm
|
||||
// 2...30 -109... - 53 dBm
|
||||
// 31 -51 dBm or greater
|
||||
// 99 not known or not detectable
|
||||
// 100 -116 dBm or less
|
||||
// 101 -115 dBm
|
||||
// 102…191 -114... - 26dBm
|
||||
// 191 -25 dBm or greater
|
||||
// 199 not known or not detectable
|
||||
// 100…199 expand to TDSCDMA, indicate RSCPreceived
|
||||
|
||||
if rssi <= 2 { // Too poor
|
||||
return fmt.Errorf("too poor <= -109dBm")
|
||||
}
|
||||
if rssi > 2 && rssi <= 31 {
|
||||
return nil // Can live
|
||||
}
|
||||
if rssi == 99 {
|
||||
return fmt.Errorf("not known or not detectable")
|
||||
}
|
||||
if rssi >= 100 && rssi <= 102 {
|
||||
return fmt.Errorf("too poor <= -114dBm")
|
||||
}
|
||||
if rssi > 102 && rssi <= 191 {
|
||||
return nil // Can live
|
||||
}
|
||||
if rssi == 199 {
|
||||
return fmt.Errorf("not known or not detectable")
|
||||
}
|
||||
return fmt.Errorf("invalid code %d", rssi)
|
||||
}
|
||||
|
||||
func checkBer(ber int) error {
|
||||
// ber - Bit error rate
|
||||
// 0 <0.01%
|
||||
// 1 0.01% --- 0.1%
|
||||
// 2 0.1% --- 0.5%
|
||||
// 3 0.5% --- 1.0%
|
||||
// 4 1.0% --- 2.0%
|
||||
// 5 2.0% --- 4.0%
|
||||
// 6 4.0% --- 8.0%
|
||||
// 7 >=8.0%
|
||||
// 99 not known or not detectable
|
||||
if ber >= 0 && ber <= 3 {
|
||||
// ber -> [0%;1%)
|
||||
// Ok, can live
|
||||
return nil
|
||||
}
|
||||
if ber >= 4 && ber <= 7 {
|
||||
return fmt.Errorf("too high: %d code", ber)
|
||||
}
|
||||
if ber == 99 {
|
||||
return fmt.Errorf("not known or not detectable")
|
||||
}
|
||||
return fmt.Errorf("invalid code %d", ber)
|
||||
}
|
||||
|
||||
// Returns rssi and ber(look above)
|
||||
func getSignalQuality(port at.Port) (int, int, error) {
|
||||
resp, err := port.Send("AT+CSQ")
|
||||
if err != nil {
|
||||
return 99, 99, err
|
||||
}
|
||||
if !resp.Check() || !resp.CheckFront("+CSQ: ") {
|
||||
return 99, 99, fmt.Errorf("error response: %s", resp)
|
||||
}
|
||||
values := strings.Split(strings.ReplaceAll(strings.Split(resp.RmFront("+CSQ: ").String(), "\n")[0], "\r", ""), ",")
|
||||
if len(values) != 2 {
|
||||
return 99, 99, fmt.Errorf("invalid values(len): [% s]", values)
|
||||
}
|
||||
rssi, err := strconv.Atoi(values[0])
|
||||
if err != nil {
|
||||
return 99, 99, fmt.Errorf("parse rssi: %w", err)
|
||||
}
|
||||
ber, err := strconv.Atoi(values[1])
|
||||
if err != nil {
|
||||
return 99, 99, fmt.Errorf("parse ber: %w", err)
|
||||
}
|
||||
return rssi, ber, nil
|
||||
}
|
26
api/modem/utils/sim.go
Normal file
26
api/modem/utils/sim.go
Normal file
@ -0,0 +1,26 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
|
||||
)
|
||||
|
||||
func CheckPIN(port at.Port, logger *log.Logger) error {
|
||||
// Get code
|
||||
resp, err := port.Send("AT+CPIN?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("AT+CPIN? request: %w", err)
|
||||
}
|
||||
if !resp.Check() || !resp.CheckFront("+CPIN:") {
|
||||
return fmt.Errorf("AT+CPIN? error response: %s", resp)
|
||||
}
|
||||
code := strings.ReplaceAll(strings.ReplaceAll(strings.Split(resp.RmFront("+CPIN:").String(), "\n")[0], "\r", ""), " ", "")
|
||||
if code != "READY" {
|
||||
return fmt.Errorf("not READY code: %s", code)
|
||||
}
|
||||
logger.Println("PIN is ready")
|
||||
return nil
|
||||
}
|
202
main.go
202
main.go
@ -1,64 +1,198 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("CGSG forever!!!")
|
||||
if err := mainE(); err != nil {
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
// Just for logs
|
||||
go func(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
log.Println("GOT INTERUPT SIGNAL")
|
||||
}(ctx)
|
||||
|
||||
if err := mainE(ctx); err != nil {
|
||||
log.Println("MAIN finished with error:", err.Error())
|
||||
}
|
||||
log.Println("END")
|
||||
}
|
||||
|
||||
func mainE() error {
|
||||
logger := log.New(os.Stdout, "main : ", log.LstdFlags)
|
||||
m := modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
|
||||
var m modem.Modem
|
||||
var logger *log.Logger
|
||||
|
||||
func InetInit() error {
|
||||
// Connect to internet
|
||||
if err := m.Ic().Connect(); err != nil {
|
||||
return fmt.Errorf("connect to internet: %w", err)
|
||||
}
|
||||
|
||||
// Setup route table
|
||||
// Linux now always manage to add ppp0 interface to route table in time so it is better to add ф loop here
|
||||
setupRouteTableLoop:
|
||||
for {
|
||||
if err := m.Ic().SetDefaultRouteTable(); err != nil {
|
||||
logger.Println("set route table:", err.Error())
|
||||
time.Sleep(2 * time.Second)
|
||||
} else {
|
||||
break setupRouteTableLoop
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Cmd(cmd string) {
|
||||
resp, err := m.At().SendWithTimeout(cmd, 10*time.Second) //50*time.Millisecond)
|
||||
logger.Println(cmd, "===>", resp, err)
|
||||
}
|
||||
|
||||
func mainE(ctx context.Context) error {
|
||||
logger = log.New(os.Stdout, "main : ", log.LstdFlags)
|
||||
m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
|
||||
|
||||
logger.Println("||||||||||||||||| INIT |||||||||||||||")
|
||||
if err := m.Init(); err != nil {
|
||||
logger.Println("Init ended with error:", err.Error())
|
||||
logger.Println("Try to turn on")
|
||||
if err := m.PowerOn(); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Println("Reinit")
|
||||
if err := m.Init(); err != nil {
|
||||
return err
|
||||
|
||||
// If power is down modem won't find suitable devices add will try to send powerOn signal and then try again
|
||||
initLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done(): // For interupt
|
||||
logger.Println("Break init loop")
|
||||
return nil
|
||||
default:
|
||||
if err := m.Init(); err != nil {
|
||||
logger.Println("Init ended with error:", err.Error())
|
||||
// logger.Println("Turn on...")
|
||||
// if err := m.PowerOnCtx(ctx); err != nil {
|
||||
// logger.Println("Turn on error:", err.Error())
|
||||
// }
|
||||
time.Sleep(time.Second)
|
||||
continue initLoop
|
||||
}
|
||||
break initLoop
|
||||
}
|
||||
}
|
||||
|
||||
// Final check for sure
|
||||
if !m.IsConnected() {
|
||||
logger.Println("AAAAAAAAAAAAAAA Modem is not connected")
|
||||
logger.Println("Modem is not connected")
|
||||
return nil
|
||||
}
|
||||
// m.PowerOff()
|
||||
// time.Sleep(10 * time.Second)
|
||||
// m.PowerOn()
|
||||
// Close() deinits everything recursively
|
||||
defer func() {
|
||||
logger.Println("||||||||||||||||| CLOSE |||||||||||||||")
|
||||
m.Close()
|
||||
}()
|
||||
|
||||
logger.Println("||||||||||||||||| GET INFO |||||||||||||||||")
|
||||
logger.Println(m.Update())
|
||||
logger.Printf("DATA: %+v\n", m.GetData())
|
||||
|
||||
logger.Println("||||||||||||||||| SEND SMS |||||||||||||||||")
|
||||
logger.Println(m.At().Send("AT+CNUM"))
|
||||
// if err := m.Sms().Send("+79218937173", "CGSG forever"); err != nil {
|
||||
// Internet connect
|
||||
// if err := InetInit(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
if ms, err := m.Sms().ReadNew(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
logger.Println("NEW:", ms)
|
||||
|
||||
logger.Println("||||||||||||||||| SMS |||||||||||||||||")
|
||||
|
||||
// Select ME PMS
|
||||
// logger.Println("SEND SMS")
|
||||
// logger.Println(m.Sms().Send("+79218937173", "CGSG forever!!!"))
|
||||
// m.At().RawSend("\r\n\x1A\r\n")
|
||||
|
||||
// Cmd("AT+CREG?")
|
||||
// Cmd("AT+CNMI?")
|
||||
Cmd("AT+CNETSCAN")
|
||||
// Cmd("AT+CSCA?")
|
||||
// Cmd("AT+CPOL?")
|
||||
// Cmd("AT+COPS?")
|
||||
// // Cmd("AT+COPS=?")
|
||||
// Cmd("AT+CPSI?")
|
||||
// resp, err = m.At().Send("AT+CNMI=2,2")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Println("Break main loop")
|
||||
return nil
|
||||
default:
|
||||
// Cmd("AT+CPSI?")
|
||||
// Cmd("AT+CIPGSMLOC=1")
|
||||
// Cmd("AT+CSQ")
|
||||
// Cmd("AT+CTZU?")
|
||||
// Cmd("AT+CPIN?")
|
||||
// Cmd("AT+CCLK?")
|
||||
// logger.Println(m.Gps().GetStatus())
|
||||
// m.Update()
|
||||
// st, _ := m.Gps().GetStatus()
|
||||
// logger.Printf("FOUND SATELITES: %d\n", st.FoundSatelitesCount)
|
||||
// data := m.GetData()
|
||||
// logger.Printf("GPS DATA: %f%s %f%s\n", data.Latitude, data.LatitudeIndicator, data.Longitude, data.LongitudeIndicator)
|
||||
// logger.Println(m.GetTime())
|
||||
// logger.Println(m.Ic().Ping())
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
// Cmd("AT+CSQ")
|
||||
// Cmd("AT+COPS?")
|
||||
|
||||
// if err := m.CheckSignal(); err != nil {
|
||||
// logger.Println(err)
|
||||
// } else {
|
||||
// logger.Println("AAAAAAAAAAA THERE IS SIGNAL")
|
||||
// }
|
||||
// readLen, err := m.At().SerialPort().Read(buf)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if readLen > 0 {
|
||||
// logger.Println(string(buf[:readLen]))
|
||||
// }
|
||||
}
|
||||
logger.Println("||||||||||||||||| Checking gps status |||||||||||||||||")
|
||||
st, err := m.Gps().GetStatus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Printf("GPS Status:%+v\n", st)
|
||||
|
||||
// resp, err = m.At().Send("AT+CPMS?")
|
||||
// logger.Println("Prefered mem storage:", resp, err)
|
||||
|
||||
// resp, err = m.At().Send("AT+CREG?")
|
||||
// logger.Println("Network registration:", resp, err)
|
||||
// resp, err = m.At().Send("AT+CPMS?")
|
||||
// logger.Println("Prefered mem storage:", resp, err)
|
||||
// resp, err = m.At().Send("AT+CPMS=?")
|
||||
// logger.Println("Possible mem storage:", resp, err)
|
||||
// resp, err = m.At().Send("AT+CNMI?")
|
||||
// logger.Println("New message indications:", resp, err)
|
||||
// resp, err = m.At().Send("AT+CMGL=\"REC UNREAD\"")
|
||||
// logger.Println("New messages:", resp, err)
|
||||
// resp, err = m.At().Send("AT+CNMI=2,1")
|
||||
// logger.Println("AT+CNMI=2,1:", resp, err)
|
||||
// resp, err = m.At().Send("AT+CNMI?")
|
||||
// logger.Println("New message indications:", resp, err)
|
||||
// logger.Println("Reading port...")
|
||||
// for {
|
||||
// readLen, err := m.At().SerialPort().Read(buf)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if readLen > 0 {
|
||||
// logger.Println(string(buf[:readLen]))
|
||||
// }
|
||||
// }
|
||||
// for {
|
||||
// resp, err = m.At().Send("AT+CSQ")
|
||||
// logger.Println("AT+CSQ:", resp, err)
|
||||
// time.Sleep(500 * time.Millisecond)
|
||||
// }
|
||||
// logger.Println("||||||||||||||||| Checking gps status |||||||||||||||||")
|
||||
// st, err := m.Gps().GetStatus()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// logger.Printf("GPS Status:%+v\n", st)
|
||||
|
||||
// logger.Println("Turn off", m.PowerOff())
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user