package update import ( "encoding/binary" "encoding/hex" "errors" "fmt" "github.com/albenik/go-serial/v2" "github.com/go-playground/log/v7" "github.com/schollz/progressbar/v3" "io/ioutil" "os" "path" "regexp" "strings" "time" ) const CHUNKSIZE = 256 type modem struct { port serial.Port } func initmodem(serport string, baudrate int) (*modem, error) { p, err := serial.Open(serport, serial.WithBaudrate(baudrate), serial.WithDataBits(8), serial.WithParity(serial.NoParity), serial.WithStopBits(serial.OneStopBit), serial.WithReadTimeout(70), serial.WithWriteTimeout(70)) // 50 was not stable, bump to 70 if err != nil { fmt.Printf("ERROR: File %s could not be opened\n", serport) return nil, err } m := &modem{*p} return m, nil } func (m *modem) write(out string, nolog ...bool) ([]byte, error) { if len(nolog) == 0 { log.Debug("Sending " + out) } else { if len(out) < 200 { // in other cases it's the firmware! log.Debugf("Sending %d byte(s)", len(out)) } } _, err := m.port.Write([]byte(out)) if err != nil { log.Errorf("ERROR while sending serial command: %s\n", out) log.Error(err) return []byte(""), err } buff := make([]byte, 1024) if len(nolog) == 0 { time.Sleep(50 * time.Millisecond) // give the PTC some time to answer, no needed with binary } n, err := m.port.Read(buff) // fmt.Printf("Read %d bytes\n", n) if err != nil { log.Errorf("ERROR: cannot read answer from command: %s\n", out) return []byte(""), err } // TODO: Handle EOF return buff[:n], nil } func (m *modem) getSerNum() (string, error) { answer, err := m.write("\rsys sern\r") if err != nil { return "", err } re := regexp.MustCompile(`Serial number: \w{2,16}`) sn := strings.ReplaceAll(string(re.Find(answer)), "Serial number: ", "") return sn, nil } func (m *modem) setDateTime() error { ct := time.Now() answer, err := m.write("DATE " + ct.Format("020106\r")) if err != nil { return err } if strings.Contains(string(answer), "BAD ARGUMENT") { return errors.New("setting DATE returns a BAD ARGUMENT error!") } answer, err = m.write("TIME " + ct.Format("150405\r")) if strings.Contains(string(answer), "BAD ARGUMENT") { return errors.New("setting TIME returns a BAD ARGUMENT error!") } return nil } func (m *modem) writeUpdate(fwfile string) error { _, err := m.write("UPDATE\r") if err != nil { _, _ = m.write("\033", true) // send ESC to cancel the update return err } answer, err := m.write("\006", true) if err != nil { _, _ = m.write("\033", true) // send ESC to cancel the update return err } log.Debugf("flash stamp: % X", binary.LittleEndian.Uint32(answer[2:6])) flashId := binary.LittleEndian.Uint16(answer[0:2]) log.Debugf("flash id: % X", flashId) if flashId != 0xa41f && flashId != 0x5b1f && flashId != 0xda1f { _, _ = m.write("\033", true) // send ESC to cancel the update return errors.New("wrong flash id") } r, err := os.Open(fwfile) if err != nil { _, _ = m.write("\033", true) // send ESC to cancel the update return err } fstat, _ := r.Stat() fsize := fstat.Size() _ = r.Close() fwblob, err := ioutil.ReadFile(fwfile) if err != nil { _, _ = m.write("\033", true) // send ESC to cancel the update return err } // fmt.Println(fsize) chunks := uint16(fsize / 256) if fsize%256 != 0 { chunks++ } // send ACK _, err = m.write("\006", true) if err != nil { _, _ = m.write("\033", true) // send ESC to cancel the update return err } // send number of chunks a := make([]byte, 2) binary.BigEndian.PutUint16(a, chunks) res, err := m.write(string(a), true) if err != nil || string(res) != "\006" { //no ACK _, _ = m.write("\033", true) // send ESC to cancel the update return err } bar := progressbar.Default(int64(chunks), "flashing chunk") var sendbuff string = "" for i := 0; i < int(chunks); i++ { _ = bar.Add(1) //log.Debugf("Sending chunk %d", i) if i*CHUNKSIZE+CHUNKSIZE > len(fwblob) { nullbytes := make([]byte, CHUNKSIZE-len(fwblob[(i*CHUNKSIZE):])) sendbuff = string(append(fwblob[(i*CHUNKSIZE):], nullbytes...)) } else { sendbuff = string(fwblob[i*CHUNKSIZE : i*CHUNKSIZE+CHUNKSIZE]) } res, err = m.write(sendbuff, true) if err != nil || string(res) != "\006" { //no ACK _, _ = m.write("\033", true) // send ESC to cancel the update log.Errorf("Error in Handshake! Rx: %s", hex.EncodeToString(res)) return err } } log.Infof("Firmware file %s successfully written", fwfile) _, err = m.write("\r", true) if err != nil { log.Error("Error in Handshake: cannot send final cr") } return nil } func (m *modem) checkFirmwareFits(fwfile string) error { answer, err := m.write("ver ##\r") if err != nil { log.Errorf("Error while reading the modem version: %s", err) return err } re := regexp.MustCompile(`\w#1`) version := strings.ReplaceAll(string(re.Find(answer)), "#1", "") type modemtype struct { ext, modem string } var extensions = map[string]modemtype{ "A": {".pt2", "PTC-II"}, "B": {".pro", "PTC-IIpro"}, "C": {".pte", "PTC-IIe"}, "D": {".pex", "PTC-IIex"}, "E": {".ptu", "PTC-IIusb"}, "F": {".ptn", "PTC-IInet"}, "H": {".dr7", "DR-7800"}, "I": {".dr7", "DR-7400"}, "K": {".pr7", "DR-7000"}, "L": {".p3u", "PTC-IIIusb"}, "T": {".ptx", "PTC-IIItrx"}, } modem, exists := extensions[version] if exists { if modem.ext == path.Ext(fwfile) { log.Infof("Firmware %s matches detected modem type %s.", fwfile, modem.modem) return nil } else { log.Fatalf("Firmware %s does NOT match detected modem type %s.", fwfile, modem.modem) return errors.New("Firmware and modem type do not match") } } else { log.Fatal("Cannot detect modem type.") return errors.New("Cannot detect modem type") } } func Update(serport string, baudrate int, fwfile string) error { m, err := initmodem(serport, baudrate) defer m.port.Close() if err != nil { return errors.New("FEHLER in Update") } sn, err := m.getSerNum() if len(sn) < 6 { // can't be a valid serial number, something's wrong, bump out here log.Errorf("ERROR: got invalid serial number: %s", sn) return err } else { log.Infof("Serial number of your modem is: %s", sn) } if m.setDateTime() != nil { return err } if m.checkFirmwareFits(fwfile) != nil { return err } if m.writeUpdate(fwfile) != nil { return err } return nil }