diff --git a/api/modem/internet/ic.go b/api/modem/internet/ic.go index e8ab955..1a9b198 100644 --- a/api/modem/internet/ic.go +++ b/api/modem/internet/ic.go @@ -1,15 +1,27 @@ package internet import ( + "context" "fmt" "io" "log" "os/exec" + "strconv" "strings" + "time" "gitea.unprism.ru/KRBL/sim-modem/api/modem/at" ) +const ( + pingPacketsCount = 3 + pingTimeout = 5 + inetConnectedTimeout = 2 * time.Second + pingAddr = "8.8.8.8" + ifName = "ppp0" // Interface name + inetMetric = 2000 +) + type conn struct { logger *log.Logger port at.Port @@ -18,6 +30,10 @@ type conn struct { isConnectExecuted bool isInited bool + isRouteSet bool + + connectTime time.Time + gw string // Gateway } type Conn interface { @@ -26,6 +42,9 @@ type Conn interface { Connect() error Disconnect() error + SetDefaultRouteTable() error + UnsetDefaultRouteTable() error + IsConnected() bool // Check interface existance Ping() error @@ -34,10 +53,12 @@ type Conn interface { func New(logger *log.Logger, port at.Port) Conn { return &conn{ - logger: logger, - port: port, + logger: logger, + port: port, + isConnectExecuted: false, isInited: false, + isRouteSet: false, } } @@ -60,12 +81,6 @@ func (c *conn) Connect() error { return fmt.Errorf("already connected") } // Check signal - // if ok, err := utils.CheckService(c.port, c.logger); err != nil || !ok { - // if err != nil { - // return fmt.Errorf("check service: %w", err) - // } - // return fmt.Errorf("no service") - // } resp, err := exec.Command("pon", pppConfigName).Output() if err != nil { @@ -74,11 +89,15 @@ func (c *conn) Connect() error { if len(resp) > 0 { c.logger.Println("pon response:", string(resp)) } + c.isConnectExecuted = true - - // Set default route - + 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 } @@ -97,30 +116,156 @@ func (c *conn) Disconnect() error { 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) Ping() error { - // Test - try to connect to Google DNS - // ping -I ppp0 8.8.8.8 - resp, err := exec.Command("ping", "8.8.8.8").Output() +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) } - c.logger.Println("Ping default interface resp:", string(resp)) - resp, err = exec.Command("ping", "-I", "ppp0", "8.8.8.8").Output() - if err != nil { - c.logger.Println("Ping ppp0 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 + } } - c.logger.Println("Ping ppp0 interface resp:", string(resp)) + if stLineI == 0 || stLineI >= len(lines) { + return fmt.Errorf("failed to find statistics line: %d", stLineI) + } + stStr := lines[stLineI] - if strings.Contains(string(resp), "Destination Host Unreachable") || strings.Contains(string(resp), "Destination Net Unreachable") { - return fmt.Errorf("ping response: %s", string(resp)) + // 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) GetHostIp() (string, error) { + if !c.isConnectExecuted { + return "", fmt.Errorf("internet not connected") + } + // Wait some time for system to setup route table + time.Sleep(time.Until(c.connectTime.Add(inetConnectedTimeout))) + + // Execute cmd + resp, err := exec.Command("route").Output() + if err != nil { + return "", fmt.Errorf("exec route cmd: %w", err) + } + // 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") +} + +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 @@ -140,7 +285,7 @@ func (c *conn) IsConnected() bool { continue } interfaceName := strings.Split(l, ":")[0] - if interfaceName == "ppp0" { + if interfaceName == ifName { return true } } @@ -149,8 +294,17 @@ func (c *conn) IsConnected() bool { func (c *conn) Close() error { c.isInited = false - if err := c.Disconnect(); err != nil { - return fmt.Errorf("diconnect: %w", err) + // 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 } diff --git a/api/modem/modem.go b/api/modem/modem.go index aea9af9..aee2b27 100644 --- a/api/modem/modem.go +++ b/api/modem/modem.go @@ -130,12 +130,12 @@ func (m *modem) Init() error { // submodulesLogger := io.Discard // FOR less logs m.logger.Println("=============================== Init submodules") - // m.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port) - // if err := m.ic.Init(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.ic = internet.New(log.New(submodulesLogger, "modem-internet : ", log.LstdFlags), m.port) + if err := m.ic.Init(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 { @@ -203,6 +203,7 @@ func (m *modem) GetTime() (time.Time, error) { 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) } @@ -361,6 +362,7 @@ func (m *modem) setupPort() error { 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 } diff --git a/main.go b/main.go index 2593008..b324755 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "log" "os" "os/signal" @@ -28,15 +29,45 @@ func main() { log.Println("END") } +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, 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 = log.New(os.Stdout, "main : ", log.LstdFlags) + m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags)) logger.Println("||||||||||||||||| INIT |||||||||||||||") + + // 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(): + case <-ctx.Done(): // For interupt logger.Println("Break init loop") return nil default: @@ -51,26 +82,23 @@ initLoop: break initLoop } } + + // Final check for sure if !m.IsConnected() { logger.Println("Modem is not connected") return nil } + // Close() deinits everything recursively defer func() { logger.Println("||||||||||||||||| CLOSE |||||||||||||||") m.Close() }() - // Connect to internet - // if err := m.Ic().Connect(); err != nil { - // return fmt.Errorf("connect to internet: %w", err) - // } + if err := InetInit(); err != nil { + return err + } logger.Println("||||||||||||||||| SMS |||||||||||||||||") - Cmd := func(cmd string) { - resp, err := m.At().SendWithTimeout(cmd, 50*time.Millisecond) - logger.Println(cmd, "===>", resp, err) - } - _ = Cmd // Select ME PMS // logger.Println("SEND SMS") @@ -92,15 +120,18 @@ initLoop: logger.Println("Break main loop") return nil default: - // Cmd("AT+CPSI?") + Cmd("AT+CPSI?") // 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("GPS STATUS: %+v", st) // logger.Printf("GPS DATA: %+v", m.GetData()) - logger.Println(m.GetTime()) + // logger.Println(m.GetTime()) + logger.Println(m.Ic().Ping()) time.Sleep(2 * time.Second) } // Cmd("AT+CSQ")