From e7fbb030c70169c6121c42555f6ad6a8be5cffb9 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 28 May 2021 17:20:26 +0200 Subject: [PATCH 01/17] Add simple Flash function to run uploaders --- programmers/programmer.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 programmers/programmer.go diff --git a/programmers/programmer.go b/programmers/programmer.go new file mode 100644 index 00000000..29970a4b --- /dev/null +++ b/programmers/programmer.go @@ -0,0 +1,37 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package programmer + +import ( + "io" + + "github.com/arduino/arduino-cli/executils" +) + +// Flash runs the upload command and outputs to outStream and errStream +func Flash(command []string, outStream, errStream io.Writer) error { + cmd, err := executils.NewProcess(command...) + if err != nil { + return err + } + cmd.RedirectStdoutTo(outStream) + cmd.RedirectStderrTo(errStream) + return cmd.Run() +} From b7ff19d907b4fdb23be1d1e8509722001619ae12 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 28 May 2021 17:53:06 +0200 Subject: [PATCH 02/17] Implemented new module Flashers to flash firmware and certificates --- flasher/flasher.go | 86 ++++++++++++ flasher/nina.go | 339 +++++++++++++++++++++++++++++++++++++++++++++ flasher/sara.go | 206 +++++++++++++++++++++++++++ flasher/winc.go | 302 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 933 insertions(+) create mode 100644 flasher/flasher.go create mode 100644 flasher/nina.go create mode 100644 flasher/sara.go create mode 100644 flasher/winc.go diff --git a/flasher/flasher.go b/flasher/flasher.go new file mode 100644 index 00000000..955604fe --- /dev/null +++ b/flasher/flasher.go @@ -0,0 +1,86 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "fmt" + "time" + + "github.com/arduino/go-paths-helper" + "go.bug.st/serial" +) + +type CommandData struct { + Command byte + Address uint32 + Value uint32 + Payload []byte +} + +type FlasherError struct { + err string +} + +func (e FlasherError) Error() string { + return e.err +} + +type Flasher interface { + FlashFirmware(firmwareFile *paths.Path) error + FlashCertificates(certificatePaths *paths.PathList) error + Close() error + + hello() error + write(address uint32, buffer []byte) error + flashChunk(offset int, buffer []byte) error + getMaximumPayloadSize() (uint16, error) + serialFillBuffer(buffer []byte) error + sendCommand(data CommandData) error +} + +// http://www.ni.com/product-documentation/54548/en/ +// Standard baud rates supported by most serial ports +var baudRates = []int{ + 115200, + 57600, + 56000, + 38400, +} + +func openSerial(portAddress string) (serial.Port, error) { + var lastError error + + for _, baudRate := range baudRates { + port, err := serial.Open(portAddress, &serial.Mode{BaudRate: baudRate}) + if err != nil { + lastError = err + // Try another baudrate + continue + } + + if err := port.SetReadTimeout(30 * time.Second); err != nil { + return nil, fmt.Errorf("Could not set timeout on serial port: %s", err) + } + + return port, nil + } + + return nil, lastError +} diff --git a/flasher/nina.go b/flasher/nina.go new file mode 100644 index 00000000..6808b511 --- /dev/null +++ b/flasher/nina.go @@ -0,0 +1,339 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "bytes" + "crypto/md5" + "encoding/binary" + "fmt" + "time" + + "github.com/arduino/go-paths-helper" + "go.bug.st/serial" +) + +// NewNinaFlasher creates an new instance of NinaFlasher +func NewNinaFlasher(portAddress string) (*NinaFlasher, error) { + port, err := openSerial(portAddress) + if err != nil { + return nil, err + } + f := &NinaFlasher{port: port} + payloadSize, err := f.getMaximumPayloadSize() + if err != nil { + return nil, err + } + if payloadSize < 1024 { + return nil, fmt.Errorf("programmer reports %d as maximum payload size (1024 is needed)", payloadSize) + } + f.payloadSize = int(payloadSize) + return f, nil +} + +type NinaFlasher struct { + port serial.Port + payloadSize int +} + +// FlashFirmware in board connected to port using data from firmwareFile +func (f *NinaFlasher) FlashFirmware(firmwareFile *paths.Path) error { + if err := f.hello(); err != nil { + return err + } + + data, err := firmwareFile.ReadFile() + if err != nil { + return err + } + + firmwareOffset := 0x0000 + if err := f.flashChunk(firmwareOffset, data); err != nil { + return err + } + + return f.md5sum(data) +} + +func (f *NinaFlasher) FlashCertificates(certificatePaths *paths.PathList) error { + // TODO + return nil +} + +// Close the port used by this flasher +func (f *NinaFlasher) Close() error { + return f.port.Close() +} + +// Ping the programmer to see if it is alive. +// Also check if the version of the programmer protocol match the uploader +func (f *NinaFlasher) hello() error { + // "HELLO" command + err := f.sendCommand(CommandData{ + Command: 0x99, + Address: 0x11223344, + Value: 0x55667788, + Payload: nil, + }) + if err != nil { + return err + } + + // Wait a bit + time.Sleep(100 * time.Millisecond) + + // Receive response + res := make([]byte, 65535) + n, err := f.port.Read(res) + if err != nil { + return err + } + // flush eventual leftover from the rx buffer + if n >= 6 { + res = res[n-6 : n] + } + + if res[0] != 'v' { + return FlasherError{err: "Programmer is not responding"} + } + if string(res) != "v10000" { + // TODO: Do we really need this check? What is it trying to verify? + return FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + } + return nil +} + +// flashChunk flashes a chunk of data +func (f *NinaFlasher) flashChunk(offset int, buffer []byte) error { + chunkSize := int(f.payloadSize) + bufferLength := len(buffer) + + if err := f.erase(uint32(offset), uint32(bufferLength)); err != nil { + return err + } + + for i := 0; i < bufferLength; i += chunkSize { + // fmt.Printf("\rFlashing: " + strconv.Itoa((i*100)/bufferLength) + "%%") + start := i + end := i + chunkSize + if end > bufferLength { + end = bufferLength + } + if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + return err + } + } + + return nil +} + +// getMaximumPayloadSize asks the board the maximum payload size +func (f *NinaFlasher) getMaximumPayloadSize() (uint16, error) { + // "MAX_PAYLOAD_SIZE" command + err := f.sendCommand(CommandData{ + Command: 0x50, + Address: 0, + Value: 0, + Payload: nil, + }) + if err != nil { + return 0, err + } + + // Receive response + res := make([]byte, 2) + if err := f.serialFillBuffer(res); err != nil { + return 0, err + } + return (uint16(res[0]) << 8) + uint16(res[1]), nil +} + +// serialFillBuffer fills buffer with data read from the serial port +func (f *NinaFlasher) serialFillBuffer(buffer []byte) error { + read := 0 + for read < len(buffer) { + n, err := f.port.Read(buffer[read:]) + if err != nil { + return err + } + if n == 0 { + return FlasherError{err: "Serial port closed unexpectedly"} + } + read += n + } + return nil +} + +// sendCommand sends the data over serial port to connected board +func (f *NinaFlasher) sendCommand(data CommandData) error { + buff := new(bytes.Buffer) + if err := binary.Write(buff, binary.BigEndian, data.Command); err != nil { + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Address); err != nil { + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Value); err != nil { + return err + } + var length uint16 + if data.Payload == nil { + length = 0 + } else { + length = uint16(len(data.Payload)) + } + if err := binary.Write(buff, binary.BigEndian, length); err != nil { + return err + } + if data.Payload != nil { + buff.Write(data.Payload) + } + bufferData := buff.Bytes() + for { + sent, err := f.port.Write(bufferData) + if err != nil { + return err + } + if sent == len(bufferData) { + break + } + bufferData = bufferData[sent:] + } + return nil +} + +// read a block of flash memory +func (f *NinaFlasher) read(address uint32, length uint32) ([]byte, error) { + // "FLASH_READ" command + err := f.sendCommand(CommandData{ + Command: 0x01, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + return nil, err + } + + // Receive response + result := make([]byte, length) + if err := f.serialFillBuffer(result); err != nil { + return nil, err + } + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + return nil, err + } + if string(ack) != "OK" { + return nil, FlasherError{err: fmt.Sprintf("Missing ack on read: %s, result: %s", ack, result)} + } + return result, nil +} + +// write a block of flash memory +func (f *NinaFlasher) write(address uint32, buffer []byte) error { + // "FLASH_WRITE" command + err := f.sendCommand(CommandData{ + Command: 0x02, + Address: address, + Value: 0, + Payload: buffer, + }) + if err != nil { + return err + } + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + return err + } + if string(ack) != "OK" { + return FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + } + return nil +} + +// erase a block of flash memory +func (f *NinaFlasher) erase(address uint32, length uint32) error { + // "FLASH_ERASE" command + err := f.sendCommand(CommandData{ + Command: 0x03, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + return err + } + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + return err + } + if string(ack) != "OK" { + return FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + } + return nil +} + +func (f *NinaFlasher) md5sum(data []byte) error { + hasher := md5.New() + hasher.Write(data) + + // Get md5sum + err := f.sendCommand(CommandData{ + Command: 0x04, + Address: 0, + Value: uint32(len(data)), + Payload: nil, + }) + if err != nil { + return err + } + + // Wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + return err + } + if string(ack) != "OK" { + return FlasherError{err: fmt.Sprintf("Missing ack on md5sum: %s", ack)} + } + + // Wait md5 + md5sum := make([]byte, 16) + if err := f.serialFillBuffer(md5sum); err != nil { + return err + } + + md5sumfromdevice := hasher.Sum(nil) + + for i := 0; i < 16; i++ { + if md5sumfromdevice[i] != md5sum[i] { + return &FlasherError{err: "MD5sum failed"} + } + } + + // log.Println("Integrity ok") + + return nil +} diff --git a/flasher/sara.go b/flasher/sara.go new file mode 100644 index 00000000..54a6cc3a --- /dev/null +++ b/flasher/sara.go @@ -0,0 +1,206 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/arduino/go-paths-helper" + "go.bug.st/serial" +) + +func NewSaraFlasher(portAddress string) (*SaraFlasher, error) { + port, err := openSerial(portAddress) + if err != nil { + return nil, err + } + // Magic numbers ¯\_(ツ)_/¯ + return &SaraFlasher{port: port, payloadSize: 128}, nil +} + +type SaraFlasher struct { + port serial.Port + payloadSize int +} + +func (f *SaraFlasher) FlashFirmware(firmwareFile *paths.Path) error { + data, err := firmwareFile.ReadFile() + if err != nil { + return err + } + + _, err = f.expectMinBytes("AT+ULSTFILE", "+ULSTFILE:", 1000, 0) + if err != nil { + return err + } + + _, err = f.expectMinBytes("AT+UDWNFILE=\"UPDATE.BIN\","+strconv.Itoa(len(data))+",\"FOAT\"", ">", 20000, 0) + if err != nil { + return err + } + + firmwareOffset := 0x0000 + err = f.flashChunk(firmwareOffset, data) + if err != nil { + return err + } + + time.Sleep(1 * time.Second) + + _, err = f.expectMinBytes("", "OK", 1000, 0) + if err != nil { + return err + } + + _, err = f.expectMinBytes("AT+UFWINSTALL", "OK", 60000, 0) + if err != nil { + return err + } + + time.Sleep(10 * time.Second) + + // wait up to 20 minutes trying to ping the module. After 20 minutes signal the error + start := time.Now() + for time.Since(start) < time.Minute*20 { + err = f.hello() + if err == nil { + return nil + } + time.Sleep(1 * time.Second) + } + + return err +} + +func (f *SaraFlasher) FlashCertificates(certificatePaths *paths.PathList) error { + // TODO + return nil +} + +func (f *SaraFlasher) Close() error { + return f.port.Close() +} + +func (f *SaraFlasher) hello() error { + f.expectMinBytes("ATE0", "OK", 100, 0) + f.expectMinBytes("ATE0", "OK", 100, 0) + f.expectMinBytes("ATE0", "OK", 100, 0) + _, err := f.expectMinBytes("AT", "OK", 100, 0) + return err +} + +func (f *SaraFlasher) write(address uint32, buffer []byte) error { + return f.sendCommand(CommandData{ + Payload: buffer, + }) +} + +func (f *SaraFlasher) flashChunk(offset int, buffer []byte) error { + bufferLength := len(buffer) + + for i := 0; i < bufferLength; i += f.payloadSize { + fmt.Printf("\rFlashing: " + strconv.Itoa((i*100)/bufferLength) + "%%") + start := i + end := i + f.payloadSize + if end > bufferLength { + end = bufferLength + } + if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + return err + } + //time.Sleep(1 * time.Millisecond) + } + + return nil +} + +func (f *SaraFlasher) getMaximumPayloadSize() (uint16, error) { + return 0, fmt.Errorf("Not supported by SaraFlasher") +} + +func (f *SaraFlasher) serialFillBuffer(buffer []byte) error { + read := 0 + for read < len(buffer) { + n, err := f.port.Read(buffer[read:]) + if err != nil { + return err + } + if n == 0 { + return &FlasherError{err: "Serial port closed unexpectedly"} + } + read += n + } + return nil +} + +func (f *SaraFlasher) sendCommand(data CommandData) error { + if data.Payload != nil { + for { + if sent, err := f.port.Write(data.Payload); err != nil { + return err + } else if sent < len(data.Payload) { + data.Payload = data.Payload[sent:] + } else { + break + } + } + } + return nil +} + +func (f *SaraFlasher) expectMinBytes(buffer string, response string, timeout int, min_bytes int) (string, error) { + err := f.sendCommand(CommandData{ + Payload: []byte(buffer + "\r\n"), + }) + if err != nil { + return "", err + } + + // log.Println("Sending " + buffer) + + // Receive response + var res []byte + n := 0 + + start := time.Now() + for (time.Since(start) < time.Duration(timeout)*time.Millisecond && !strings.Contains(string(res), response)) || (len(res) < min_bytes) { + data := 0 + partial := make([]byte, 65535) + data, err = f.port.Read(partial) + res = append(res, partial[:data]...) + n += data + if err != nil { + return "", err + } + } + + // log.Println(string(res)) + + if !strings.Contains(string(res), response) { + return string(res), FlasherError{err: fmt.Sprintf("Expected %s, got %s", response, res)} + } + return string(res), nil +} +func (f *SaraFlasher) getFirmwareVersion() (string, error) { + return f.expectMinBytes("ATI9", "05.06,A.02.", 100, 25) +} diff --git a/flasher/winc.go b/flasher/winc.go new file mode 100644 index 00000000..e59dfcac --- /dev/null +++ b/flasher/winc.go @@ -0,0 +1,302 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "log" + "strconv" + "time" + + "github.com/arduino/go-paths-helper" + "go.bug.st/serial" +) + +func NewWincFlasher(portAddress string) (*WincFlasher, error) { + port, err := openSerial(portAddress) + if err != nil { + return nil, err + } + f := &WincFlasher{port: port} + payloadSize, err := f.getMaximumPayloadSize() + if err != nil { + return nil, err + } + if payloadSize < 1024 { + return nil, fmt.Errorf("programmer reports %d as maximum payload size (1024 is needed)", payloadSize) + } + f.payloadSize = int(payloadSize) + return f, nil +} + +type WincFlasher struct { + port serial.Port + payloadSize int +} + +func (f *WincFlasher) FlashFirmware(firmwareFile *paths.Path) error { + // log.Printf("Flashing firmware from '%v'", ctx.FirmwareFile) + data, err := firmwareFile.ReadFile() + if err != nil { + return err + } + firmwareOffset := 0x0000 + return f.flashChunk(firmwareOffset, data) +} + +func (f *WincFlasher) FlashCertificates(certificatePaths *paths.PathList) error { + // TODO + return nil +} + +func (f *WincFlasher) Close() error { + return f.port.Close() +} + +func (f *WincFlasher) hello() error { + // "HELLO" command + err := f.sendCommand(CommandData{ + Command: 0x99, + Address: 0x11223344, + Value: 0x55667788, + Payload: nil, + }) + if err != nil { + return err + } + + // Wait a bit + time.Sleep(100 * time.Millisecond) + + // Receive response + res := make([]byte, 65535) + n, err := f.port.Read(res) + if err != nil { + return err + } + // flush eventual leftover from the rx buffer + if n >= 6 { + res = res[n-6 : n] + } + + if res[0] != 'v' { + return FlasherError{err: "Programmer is not responding"} + } + if string(res) != "v10000" { + // TODO: Do we really need this check? What is it trying to verify? + return FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + } + return nil +} + +func (f *WincFlasher) write(address uint32, buffer []byte) error { + // "FLASH_WRITE" command + err := f.sendCommand(CommandData{ + Command: 0x02, + Address: address, + Value: 0, + Payload: buffer, + }) + if err != nil { + return err + } + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + return err + } + if string(ack) != "OK" { + return FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + } + return nil +} + +func (f *WincFlasher) flashChunk(offset int, buffer []byte) error { + bufferLength := len(buffer) + + if err := f.erase(uint32(offset), uint32(bufferLength)); err != nil { + return err + } + + for i := 0; i < bufferLength; i += f.payloadSize { + fmt.Printf("\rFlashing: " + strconv.Itoa((i*100)/bufferLength) + "%%") + start := i + end := i + f.payloadSize + if end > bufferLength { + end = bufferLength + } + if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + return err + } + } + + var flashData []byte + for i := 0; i < bufferLength; i += f.payloadSize { + readLength := f.payloadSize + if (i + f.payloadSize) > bufferLength { + readLength = bufferLength % f.payloadSize + } + + data, err := f.read(uint32(offset+i), uint32(readLength)) + if err != nil { + return err + } + + flashData = append(flashData, data...) + } + + if !bytes.Equal(buffer, flashData) { + return errors.New("flash data does not match written") + } + + return nil +} + +func (f *WincFlasher) getMaximumPayloadSize() (uint16, error) { + // "MAX_PAYLOAD_SIZE" command + err := f.sendCommand(CommandData{ + Command: 0x50, + Address: 0, + Value: 0, + Payload: nil, + }) + if err != nil { + return 0, err + } + + // Receive response + res := make([]byte, 2) + if err := f.serialFillBuffer(res); err != nil { + return 0, err + } + return (uint16(res[0]) << 8) + uint16(res[1]), nil +} + +func (f *WincFlasher) serialFillBuffer(buffer []byte) error { + read := 0 + for read < len(buffer) { + n, err := f.port.Read(buffer[read:]) + if err != nil { + return err + } + if n == 0 { + return &FlasherError{err: "Serial port closed unexpectedly"} + } + read += n + } + return nil +} + +func (f *WincFlasher) sendCommand(data CommandData) error { + buff := new(bytes.Buffer) + if err := binary.Write(buff, binary.BigEndian, data.Command); err != nil { + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Address); err != nil { + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Value); err != nil { + return err + } + var length uint16 + if data.Payload == nil { + length = 0 + } else { + length = uint16(len(data.Payload)) + } + if err := binary.Write(buff, binary.BigEndian, length); err != nil { + return err + } + if data.Payload != nil { + buff.Write(data.Payload) + } + + bufferData := buff.Bytes() + for { + sent, err := f.port.Write(bufferData) + if err != nil { + return err + } + if sent == len(bufferData) { + break + } + // fmt.Println("HEY! sent", sent, "out of", len(bufferData)) + bufferData = bufferData[sent:] + } + return nil +} + +// Read a block of flash memory +func (f *WincFlasher) read(address uint32, length uint32) ([]byte, error) { + // "FLASH_READ" command + err := f.sendCommand(CommandData{ + Command: 0x01, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + return nil, err + } + + // Receive response + result := make([]byte, length) + if err := f.serialFillBuffer(result); err != nil { + return nil, err + } + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + return nil, err + } + if string(ack) != "OK" { + return nil, FlasherError{err: fmt.Sprintf("Missing ack on read: %s", ack)} + } + return result, nil +} + +// Erase a block of flash memory +func (f *WincFlasher) erase(address uint32, length uint32) error { + // "FLASH_ERASE" command + err := f.sendCommand(CommandData{ + Command: 0x03, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + return err + } + + log.Printf("Erasing %d bytes from address 0x%X\n", length, address) + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + return err + } + if string(ack) != "OK" { + return FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + } + return nil +} From 307c2b29bb290283c2fca739fe3bc4c6c939b200 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 28 May 2021 17:53:20 +0200 Subject: [PATCH 03/17] Add flash-firmware command --- cli/flash_firmware/flash_firmware.go | 125 +++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 cli/flash_firmware/flash_firmware.go diff --git a/cli/flash_firmware/flash_firmware.go b/cli/flash_firmware/flash_firmware.go new file mode 100644 index 00000000..ab0af87a --- /dev/null +++ b/cli/flash_firmware/flash_firmware.go @@ -0,0 +1,125 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flash_firmware + +import ( + "bytes" + "os" + + "github.com/arduino/FirmwareUploader/flasher" + programmer "github.com/arduino/FirmwareUploader/programmers" + "github.com/arduino/arduino-cli/arduino/serialutils" + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/spf13/cobra" +) + +var ( + fqbn string + address string + module string +) + +// NewCommand created a new `version` command +func NewCommand() *cobra.Command { + command := &cobra.Command{ + Use: "flash-firmware", + Short: "Shows version number of FirmwareUploader.", + Long: "Shows the version number of FirmwareUploader which is installed on your system.", + Example: " " + os.Args[0] + " version", + Args: cobra.NoArgs, + Run: run, + } + + command.Flags().StringVarP(&fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:samd:mkr1000, arduino:mbed_nano:nanorp2040connect") + command.Flags().StringVarP(&address, "address", "a", "", "Upload port, e.g.: COM10, /dev/ttyACM0") + command.Flags().StringVarP(&module, "module", "m", "", "Firmware module ID, e.g.: WINC1500, NINA") + return command +} + +func run(cmd *cobra.Command, args []string) { + if fqbn == "" { + feedback.Errorf("Error during firmware flashing: missing board fqbn") + os.Exit(errorcodes.ErrGeneric) + } + + if address == "" { + feedback.Errorf("Error during firmware flashing: missing board address") + os.Exit(errorcodes.ErrGeneric) + } + + if module == "" { + // TODO: Get firmware ID for board if not provided + } + + // TODO: Get firmware binary from given ID + + // TODO: Get uploader executable path + + // TODO: Get uploader command line + commandLine := []string{""} + + // TODO: Build uploader command line using uploader path, eventual config path and Loader Sketch binary + + // TODO: Get 1200bps touch from upload properties + use1200bpsTouch := false + if use1200bpsTouch { + feedback.Print("Putting board into bootloader mode") + // TODO: Get waitForUploadPort from upload properties + waitForUploadPort := false + _, err := serialutils.Reset(address, waitForUploadPort, nil) + if err != nil { + // TODO + } + } + + // TODO: Flash loader Sketch + flashOut := new(bytes.Buffer) + flashErr := new(bytes.Buffer) + // TODO: Maybe this can be done differently? + var err error + // TODO: OutputFormat is not stored globally, we must store it globally since we need it + OutputFormat := "json" + if OutputFormat == "json" { + err = programmer.Flash(commandLine, flashOut, flashErr) + } else { + err = programmer.Flash(commandLine, os.Stdout, os.Stderr) + } + if err != nil { + // TODO + } + + // Get flasher depending on which module to use + var f flasher.Flasher + switch module { + case "NINA": + f, err = flasher.NewNinaFlasher(address) + case "SARA": + f, err = flasher.NewSaraFlasher(address) + case "WINC": + f, err = flasher.NewWincFlasher(address) + } + if err != nil { + // TODO + } + defer f.Close() + + // TODO: Flash firmware +} From c5f12bba840bc6acb9e2cb95ee077dc265ddce6c Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Mon, 7 Jun 2021 16:29:15 +0200 Subject: [PATCH 04/17] Enhance flash-firmware command examples --- cli/flash_firmware/flash_firmware.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cli/flash_firmware/flash_firmware.go b/cli/flash_firmware/flash_firmware.go index ab0af87a..222ac5cb 100644 --- a/cli/flash_firmware/flash_firmware.go +++ b/cli/flash_firmware/flash_firmware.go @@ -40,12 +40,15 @@ var ( // NewCommand created a new `version` command func NewCommand() *cobra.Command { command := &cobra.Command{ - Use: "flash-firmware", - Short: "Shows version number of FirmwareUploader.", - Long: "Shows the version number of FirmwareUploader which is installed on your system.", - Example: " " + os.Args[0] + " version", - Args: cobra.NoArgs, - Run: run, + Use: "flash-firmware", + Short: "Flashes firmwares to board.", + Long: "Flashes specified module firmware to board at specified address. Module name and version can be omitted to install latest version.", + Example: "" + + " " + os.Args[0] + " flash-firmware --fqbn arduino:samd:mkr1000 --address COM10 --module WINC1500@19.5.2\n" + + " " + os.Args[0] + " flash-firmware -b arduino:samd:mkr1000 -a COM10 -m WINC15000\n" + + " " + os.Args[0] + " flash-firmware -b arduino:samd:mkr1000 -a COM10\n", + Args: cobra.NoArgs, + Run: run, } command.Flags().StringVarP(&fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:samd:mkr1000, arduino:mbed_nano:nanorp2040connect") From 4c0c6822ccb854f07a3d7d4704b4328787157507 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Mon, 7 Jun 2021 16:30:17 +0200 Subject: [PATCH 05/17] Add some logging --- flasher/flasher.go | 4 ++- flasher/nina.go | 85 ++++++++++++++++++++++++++++++++++++---------- flasher/sara.go | 41 +++++++++++++++++----- flasher/winc.go | 59 ++++++++++++++++++++++++++------ 4 files changed, 152 insertions(+), 37 deletions(-) diff --git a/flasher/flasher.go b/flasher/flasher.go index 955604fe..f59b0fca 100644 --- a/flasher/flasher.go +++ b/flasher/flasher.go @@ -24,6 +24,7 @@ import ( "time" "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" "go.bug.st/serial" ) @@ -74,9 +75,10 @@ func openSerial(portAddress string) (serial.Port, error) { // Try another baudrate continue } + logrus.Infof("Opened port %s at %s", portAddress, baudRate) if err := port.SetReadTimeout(30 * time.Second); err != nil { - return nil, fmt.Errorf("Could not set timeout on serial port: %s", err) + return nil, fmt.Errorf("could not set timeout on serial port: %s", err) } return port, nil diff --git a/flasher/nina.go b/flasher/nina.go index 6808b511..5a522b29 100644 --- a/flasher/nina.go +++ b/flasher/nina.go @@ -24,9 +24,11 @@ import ( "crypto/md5" "encoding/binary" "fmt" + "strconv" "time" "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" "go.bug.st/serial" ) @@ -34,15 +36,19 @@ import ( func NewNinaFlasher(portAddress string) (*NinaFlasher, error) { port, err := openSerial(portAddress) if err != nil { + logrus.Error(err) return nil, err } f := &NinaFlasher{port: port} payloadSize, err := f.getMaximumPayloadSize() if err != nil { + logrus.Error(err) return nil, err } if payloadSize < 1024 { - return nil, fmt.Errorf("programmer reports %d as maximum payload size (1024 is needed)", payloadSize) + err = fmt.Errorf("programmer reports %d as maximum payload size (1024 is needed)", payloadSize) + logrus.Error(err) + return nil, err } f.payloadSize = int(payloadSize) return f, nil @@ -55,17 +61,21 @@ type NinaFlasher struct { // FlashFirmware in board connected to port using data from firmwareFile func (f *NinaFlasher) FlashFirmware(firmwareFile *paths.Path) error { + logrus.Infof("Flashing firmware %s", firmwareFile) if err := f.hello(); err != nil { + logrus.Error(err) return err } data, err := firmwareFile.ReadFile() if err != nil { + logrus.Error(err) return err } firmwareOffset := 0x0000 if err := f.flashChunk(firmwareOffset, data); err != nil { + logrus.Error(err) return err } @@ -79,7 +89,9 @@ func (f *NinaFlasher) FlashCertificates(certificatePaths *paths.PathList) error // Close the port used by this flasher func (f *NinaFlasher) Close() error { - return f.port.Close() + err := f.port.Close() + logrus.Error(err) + return err } // Ping the programmer to see if it is alive. @@ -93,6 +105,7 @@ func (f *NinaFlasher) hello() error { Payload: nil, }) if err != nil { + logrus.Error(err) return err } @@ -103,6 +116,7 @@ func (f *NinaFlasher) hello() error { res := make([]byte, 65535) n, err := f.port.Read(res) if err != nil { + logrus.Error(err) return err } // flush eventual leftover from the rx buffer @@ -111,11 +125,15 @@ func (f *NinaFlasher) hello() error { } if res[0] != 'v' { - return FlasherError{err: "Programmer is not responding"} + err = FlasherError{err: "Programmer is not responding"} + logrus.Error(err) + return err } if string(res) != "v10000" { // TODO: Do we really need this check? What is it trying to verify? - return FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + err = FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + logrus.Error(err) + return err } return nil } @@ -126,17 +144,19 @@ func (f *NinaFlasher) flashChunk(offset int, buffer []byte) error { bufferLength := len(buffer) if err := f.erase(uint32(offset), uint32(bufferLength)); err != nil { + logrus.Error(err) return err } for i := 0; i < bufferLength; i += chunkSize { - // fmt.Printf("\rFlashing: " + strconv.Itoa((i*100)/bufferLength) + "%%") + logrus.Debugf("Flashing chunk: %s%%", strconv.Itoa((i*100)/bufferLength)) start := i end := i + chunkSize if end > bufferLength { end = bufferLength } if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + logrus.Error(err) return err } } @@ -154,12 +174,14 @@ func (f *NinaFlasher) getMaximumPayloadSize() (uint16, error) { Payload: nil, }) if err != nil { + logrus.Error(err) return 0, err } // Receive response res := make([]byte, 2) if err := f.serialFillBuffer(res); err != nil { + logrus.Error(err) return 0, err } return (uint16(res[0]) << 8) + uint16(res[1]), nil @@ -171,10 +193,13 @@ func (f *NinaFlasher) serialFillBuffer(buffer []byte) error { for read < len(buffer) { n, err := f.port.Read(buffer[read:]) if err != nil { + logrus.Error(err) return err } if n == 0 { - return FlasherError{err: "Serial port closed unexpectedly"} + err = FlasherError{err: "Serial port closed unexpectedly"} + logrus.Error(err) + return err } read += n } @@ -183,14 +208,18 @@ func (f *NinaFlasher) serialFillBuffer(buffer []byte) error { // sendCommand sends the data over serial port to connected board func (f *NinaFlasher) sendCommand(data CommandData) error { + logrus.Debugf("sending command data %s", data) buff := new(bytes.Buffer) if err := binary.Write(buff, binary.BigEndian, data.Command); err != nil { + logrus.Error(err) return err } if err := binary.Write(buff, binary.BigEndian, data.Address); err != nil { + logrus.Error(err) return err } if err := binary.Write(buff, binary.BigEndian, data.Value); err != nil { + logrus.Error(err) return err } var length uint16 @@ -200,6 +229,7 @@ func (f *NinaFlasher) sendCommand(data CommandData) error { length = uint16(len(data.Payload)) } if err := binary.Write(buff, binary.BigEndian, length); err != nil { + logrus.Error(err) return err } if data.Payload != nil { @@ -209,11 +239,13 @@ func (f *NinaFlasher) sendCommand(data CommandData) error { for { sent, err := f.port.Write(bufferData) if err != nil { + logrus.Error(err) return err } if sent == len(bufferData) { break } + logrus.Debugf("Sent %d bytes out of %d", sent, len(bufferData)) bufferData = bufferData[sent:] } return nil @@ -229,20 +261,25 @@ func (f *NinaFlasher) read(address uint32, length uint32) ([]byte, error) { Payload: nil, }) if err != nil { + logrus.Error(err) return nil, err } // Receive response result := make([]byte, length) if err := f.serialFillBuffer(result); err != nil { + logrus.Error(err) return nil, err } ack := make([]byte, 2) if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) return nil, err } if string(ack) != "OK" { - return nil, FlasherError{err: fmt.Sprintf("Missing ack on read: %s, result: %s", ack, result)} + err = FlasherError{err: fmt.Sprintf("Missing ack on read: %s, result: %s", ack, result)} + logrus.Error(err) + return nil, err } return result, nil } @@ -257,16 +294,20 @@ func (f *NinaFlasher) write(address uint32, buffer []byte) error { Payload: buffer, }) if err != nil { + logrus.Error(err) return err } // wait acknowledge ack := make([]byte, 2) if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) return err } if string(ack) != "OK" { - return FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + err = FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + logrus.Error(err) + return err } return nil } @@ -281,16 +322,20 @@ func (f *NinaFlasher) erase(address uint32, length uint32) error { Payload: nil, }) if err != nil { + logrus.Error(err) return err } // wait acknowledge ack := make([]byte, 2) if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) return err } if string(ack) != "OK" { - return FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + err = FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + logrus.Error(err) + return err } return nil } @@ -307,33 +352,39 @@ func (f *NinaFlasher) md5sum(data []byte) error { Payload: nil, }) if err != nil { + logrus.Error(err) return err } // Wait acknowledge ack := make([]byte, 2) if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) return err } if string(ack) != "OK" { - return FlasherError{err: fmt.Sprintf("Missing ack on md5sum: %s", ack)} + err := FlasherError{err: fmt.Sprintf("Missing ack on md5sum: %s", ack)} + logrus.Error(err) + return err } // Wait md5 - md5sum := make([]byte, 16) - if err := f.serialFillBuffer(md5sum); err != nil { + md5sumfromdevice := make([]byte, 16) + if err := f.serialFillBuffer(md5sumfromdevice); err != nil { return err } - md5sumfromdevice := hasher.Sum(nil) + md5sum := hasher.Sum(nil) + logrus.Debugf("md5 read from device %s", md5sumfromdevice) + logrus.Debugf("md5 of data %s", md5sum) for i := 0; i < 16; i++ { - if md5sumfromdevice[i] != md5sum[i] { - return &FlasherError{err: "MD5sum failed"} + if md5sum[i] != md5sumfromdevice[i] { + err := FlasherError{err: "MD5sum failed"} + logrus.Error(err) + return err } } - // log.Println("Integrity ok") - return nil } diff --git a/flasher/sara.go b/flasher/sara.go index 54a6cc3a..7908d7d9 100644 --- a/flasher/sara.go +++ b/flasher/sara.go @@ -26,12 +26,14 @@ import ( "time" "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" "go.bug.st/serial" ) func NewSaraFlasher(portAddress string) (*SaraFlasher, error) { port, err := openSerial(portAddress) if err != nil { + logrus.Error(err) return nil, err } // Magic numbers ¯\_(ツ)_/¯ @@ -44,24 +46,29 @@ type SaraFlasher struct { } func (f *SaraFlasher) FlashFirmware(firmwareFile *paths.Path) error { + logrus.Infof("Flashing firmware %s", firmwareFile) data, err := firmwareFile.ReadFile() if err != nil { + logrus.Error(err) return err } _, err = f.expectMinBytes("AT+ULSTFILE", "+ULSTFILE:", 1000, 0) if err != nil { + logrus.Error(err) return err } _, err = f.expectMinBytes("AT+UDWNFILE=\"UPDATE.BIN\","+strconv.Itoa(len(data))+",\"FOAT\"", ">", 20000, 0) if err != nil { + logrus.Error(err) return err } firmwareOffset := 0x0000 err = f.flashChunk(firmwareOffset, data) if err != nil { + logrus.Error(err) return err } @@ -69,11 +76,13 @@ func (f *SaraFlasher) FlashFirmware(firmwareFile *paths.Path) error { _, err = f.expectMinBytes("", "OK", 1000, 0) if err != nil { + logrus.Error(err) return err } _, err = f.expectMinBytes("AT+UFWINSTALL", "OK", 60000, 0) if err != nil { + logrus.Error(err) return err } @@ -89,6 +98,9 @@ func (f *SaraFlasher) FlashFirmware(firmwareFile *paths.Path) error { time.Sleep(1 * time.Second) } + if err != nil { + logrus.Error(err) + } return err } @@ -98,7 +110,9 @@ func (f *SaraFlasher) FlashCertificates(certificatePaths *paths.PathList) error } func (f *SaraFlasher) Close() error { - return f.port.Close() + err := f.port.Close() + logrus.Error(err) + return err } func (f *SaraFlasher) hello() error { @@ -106,6 +120,9 @@ func (f *SaraFlasher) hello() error { f.expectMinBytes("ATE0", "OK", 100, 0) f.expectMinBytes("ATE0", "OK", 100, 0) _, err := f.expectMinBytes("AT", "OK", 100, 0) + if err != nil { + logrus.Error(err) + } return err } @@ -119,13 +136,14 @@ func (f *SaraFlasher) flashChunk(offset int, buffer []byte) error { bufferLength := len(buffer) for i := 0; i < bufferLength; i += f.payloadSize { - fmt.Printf("\rFlashing: " + strconv.Itoa((i*100)/bufferLength) + "%%") + logrus.Debugf("Flashing chunk: %s%%", strconv.Itoa((i*100)/bufferLength)) start := i end := i + f.payloadSize if end > bufferLength { end = bufferLength } if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + logrus.Error(err) return err } //time.Sleep(1 * time.Millisecond) @@ -135,7 +153,7 @@ func (f *SaraFlasher) flashChunk(offset int, buffer []byte) error { } func (f *SaraFlasher) getMaximumPayloadSize() (uint16, error) { - return 0, fmt.Errorf("Not supported by SaraFlasher") + return 0, fmt.Errorf("not supported by SaraFlasher") } func (f *SaraFlasher) serialFillBuffer(buffer []byte) error { @@ -143,10 +161,13 @@ func (f *SaraFlasher) serialFillBuffer(buffer []byte) error { for read < len(buffer) { n, err := f.port.Read(buffer[read:]) if err != nil { + logrus.Error(err) return err } if n == 0 { - return &FlasherError{err: "Serial port closed unexpectedly"} + err = &FlasherError{err: "Serial port closed unexpectedly"} + logrus.Error(err) + return err } read += n } @@ -154,9 +175,11 @@ func (f *SaraFlasher) serialFillBuffer(buffer []byte) error { } func (f *SaraFlasher) sendCommand(data CommandData) error { + logrus.Debugf("sending command data %s", data) if data.Payload != nil { for { if sent, err := f.port.Write(data.Payload); err != nil { + logrus.Error(err) return err } else if sent < len(data.Payload) { data.Payload = data.Payload[sent:] @@ -173,11 +196,10 @@ func (f *SaraFlasher) expectMinBytes(buffer string, response string, timeout int Payload: []byte(buffer + "\r\n"), }) if err != nil { + logrus.Error(err) return "", err } - // log.Println("Sending " + buffer) - // Receive response var res []byte n := 0 @@ -190,14 +212,15 @@ func (f *SaraFlasher) expectMinBytes(buffer string, response string, timeout int res = append(res, partial[:data]...) n += data if err != nil { + logrus.Error(err) return "", err } } - // log.Println(string(res)) - if !strings.Contains(string(res), response) { - return string(res), FlasherError{err: fmt.Sprintf("Expected %s, got %s", response, res)} + err = FlasherError{err: fmt.Sprintf("Expected %s, got %s", response, res)} + logrus.Error(err) + return string(res), err } return string(res), nil } diff --git a/flasher/winc.go b/flasher/winc.go index e59dfcac..528580f8 100644 --- a/flasher/winc.go +++ b/flasher/winc.go @@ -29,17 +29,20 @@ import ( "time" "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" "go.bug.st/serial" ) func NewWincFlasher(portAddress string) (*WincFlasher, error) { port, err := openSerial(portAddress) if err != nil { + logrus.Error(err) return nil, err } f := &WincFlasher{port: port} payloadSize, err := f.getMaximumPayloadSize() if err != nil { + logrus.Error(err) return nil, err } if payloadSize < 1024 { @@ -55,9 +58,10 @@ type WincFlasher struct { } func (f *WincFlasher) FlashFirmware(firmwareFile *paths.Path) error { - // log.Printf("Flashing firmware from '%v'", ctx.FirmwareFile) + logrus.Infof("Flashing firmware %s", firmwareFile) data, err := firmwareFile.ReadFile() if err != nil { + logrus.Error(err) return err } firmwareOffset := 0x0000 @@ -82,6 +86,7 @@ func (f *WincFlasher) hello() error { Payload: nil, }) if err != nil { + logrus.Error(err) return err } @@ -92,6 +97,7 @@ func (f *WincFlasher) hello() error { res := make([]byte, 65535) n, err := f.port.Read(res) if err != nil { + logrus.Error(err) return err } // flush eventual leftover from the rx buffer @@ -100,11 +106,15 @@ func (f *WincFlasher) hello() error { } if res[0] != 'v' { - return FlasherError{err: "Programmer is not responding"} + err = FlasherError{err: "Programmer is not responding"} + logrus.Error(err) + return err } if string(res) != "v10000" { // TODO: Do we really need this check? What is it trying to verify? - return FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + err = FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + logrus.Error(err) + return err } return nil } @@ -118,16 +128,20 @@ func (f *WincFlasher) write(address uint32, buffer []byte) error { Payload: buffer, }) if err != nil { + logrus.Error(err) return err } // wait acknowledge ack := make([]byte, 2) if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) return err } if string(ack) != "OK" { - return FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + err = FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + logrus.Error(err) + return err } return nil } @@ -136,17 +150,19 @@ func (f *WincFlasher) flashChunk(offset int, buffer []byte) error { bufferLength := len(buffer) if err := f.erase(uint32(offset), uint32(bufferLength)); err != nil { + logrus.Error(err) return err } for i := 0; i < bufferLength; i += f.payloadSize { - fmt.Printf("\rFlashing: " + strconv.Itoa((i*100)/bufferLength) + "%%") + logrus.Debugf("Flashing chunk: %s%%", strconv.Itoa((i*100)/bufferLength)) start := i end := i + f.payloadSize if end > bufferLength { end = bufferLength } if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + logrus.Error(err) return err } } @@ -160,6 +176,7 @@ func (f *WincFlasher) flashChunk(offset int, buffer []byte) error { data, err := f.read(uint32(offset+i), uint32(readLength)) if err != nil { + logrus.Error(err) return err } @@ -167,7 +184,9 @@ func (f *WincFlasher) flashChunk(offset int, buffer []byte) error { } if !bytes.Equal(buffer, flashData) { - return errors.New("flash data does not match written") + err := errors.New("flash data does not match written") + logrus.Error(err) + return err } return nil @@ -182,12 +201,14 @@ func (f *WincFlasher) getMaximumPayloadSize() (uint16, error) { Payload: nil, }) if err != nil { + logrus.Error(err) return 0, err } // Receive response res := make([]byte, 2) if err := f.serialFillBuffer(res); err != nil { + logrus.Error(err) return 0, err } return (uint16(res[0]) << 8) + uint16(res[1]), nil @@ -198,10 +219,13 @@ func (f *WincFlasher) serialFillBuffer(buffer []byte) error { for read < len(buffer) { n, err := f.port.Read(buffer[read:]) if err != nil { + logrus.Error(err) return err } if n == 0 { - return &FlasherError{err: "Serial port closed unexpectedly"} + err = FlasherError{err: "Serial port closed unexpectedly"} + logrus.Error(err) + return err } read += n } @@ -209,14 +233,18 @@ func (f *WincFlasher) serialFillBuffer(buffer []byte) error { } func (f *WincFlasher) sendCommand(data CommandData) error { + logrus.Debugf("sending command data %s", data) buff := new(bytes.Buffer) if err := binary.Write(buff, binary.BigEndian, data.Command); err != nil { + logrus.Error(err) return err } if err := binary.Write(buff, binary.BigEndian, data.Address); err != nil { + logrus.Error(err) return err } if err := binary.Write(buff, binary.BigEndian, data.Value); err != nil { + logrus.Error(err) return err } var length uint16 @@ -226,6 +254,7 @@ func (f *WincFlasher) sendCommand(data CommandData) error { length = uint16(len(data.Payload)) } if err := binary.Write(buff, binary.BigEndian, length); err != nil { + logrus.Error(err) return err } if data.Payload != nil { @@ -236,12 +265,13 @@ func (f *WincFlasher) sendCommand(data CommandData) error { for { sent, err := f.port.Write(bufferData) if err != nil { + logrus.Error(err) return err } if sent == len(bufferData) { break } - // fmt.Println("HEY! sent", sent, "out of", len(bufferData)) + logrus.Debugf("Sent %d bytes out of %d", sent, len(bufferData)) bufferData = bufferData[sent:] } return nil @@ -257,20 +287,25 @@ func (f *WincFlasher) read(address uint32, length uint32) ([]byte, error) { Payload: nil, }) if err != nil { + logrus.Error(err) return nil, err } // Receive response result := make([]byte, length) if err := f.serialFillBuffer(result); err != nil { + logrus.Error(err) return nil, err } ack := make([]byte, 2) if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) return nil, err } if string(ack) != "OK" { - return nil, FlasherError{err: fmt.Sprintf("Missing ack on read: %s", ack)} + err = FlasherError{err: fmt.Sprintf("Missing ack on read: %s", ack)} + logrus.Error(err) + return nil, err } return result, nil } @@ -285,6 +320,7 @@ func (f *WincFlasher) erase(address uint32, length uint32) error { Payload: nil, }) if err != nil { + logrus.Error(err) return err } @@ -293,10 +329,13 @@ func (f *WincFlasher) erase(address uint32, length uint32) error { // wait acknowledge ack := make([]byte, 2) if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) return err } if string(ack) != "OK" { - return FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + err = FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + logrus.Error(err) + return err } return nil } From 67412b97b2c32394d2ff511ddd57ba741e6ab19b Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Mon, 7 Jun 2021 18:07:05 +0200 Subject: [PATCH 06/17] Add logic to flash certificates --- flasher/certificate.go | 85 +++++++++++++++++++++++++++++++ flasher/flasher.go | 2 +- flasher/nina.go | 85 +++++++++++++++++++++++++++++-- flasher/sara.go | 5 +- flasher/winc.go | 113 +++++++++++++++++++++++++++++++++++++++-- 5 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 flasher/certificate.go diff --git a/flasher/certificate.go b/flasher/certificate.go new file mode 100644 index 00000000..cb95394c --- /dev/null +++ b/flasher/certificate.go @@ -0,0 +1,85 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "bytes" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/binary" + "time" + + "github.com/sirupsen/logrus" +) + +func calculateNameSha1(cert *x509.Certificate) (b []byte, err error) { + nameSha1 := sha1.New() + + var subjectDistinguishedNameSequence pkix.RDNSequence + + if _, err = asn1.Unmarshal(cert.RawSubject, &subjectDistinguishedNameSequence); err != nil { + logrus.Error(err) + return nil, err + } + + for _, dn := range subjectDistinguishedNameSequence { + nameSha1.Write([]byte(dn[0].Value.(string))) + } + + b = nameSha1.Sum(nil) + + return +} + +func convertTime(time time.Time) (b []byte, err error) { + asn1Bytes, err := asn1.Marshal(time) + if err != nil { + logrus.Error(err) + return nil, err + } + + b = bytes.Repeat([]byte{0x00}, 20) // value must be zero bytes + copy(b, asn1Bytes[2:]) // copy but drop the first two bytes + + return +} + +func modulusN(publicKey rsa.PublicKey) []byte { + return publicKey.N.Bytes() +} + +func publicExponent(publicKey rsa.PublicKey) (b []byte) { + b = make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(publicKey.E)) + // strip leading zeros + for b[0] == 0 { + b = b[1:] + } + return +} + +func uint16ToBytes(i int) (b []byte) { + b = make([]byte, 2) + binary.LittleEndian.PutUint16(b, uint16(i)) + return +} diff --git a/flasher/flasher.go b/flasher/flasher.go index f59b0fca..11759288 100644 --- a/flasher/flasher.go +++ b/flasher/flasher.go @@ -45,7 +45,7 @@ func (e FlasherError) Error() string { type Flasher interface { FlashFirmware(firmwareFile *paths.Path) error - FlashCertificates(certificatePaths *paths.PathList) error + FlashCertificates(certificatePaths *paths.PathList, URLs []string) error Close() error hello() error diff --git a/flasher/nina.go b/flasher/nina.go index 5a522b29..19bfb824 100644 --- a/flasher/nina.go +++ b/flasher/nina.go @@ -22,7 +22,10 @@ package flasher import ( "bytes" "crypto/md5" + "crypto/tls" + "crypto/x509" "encoding/binary" + "encoding/pem" "fmt" "strconv" "time" @@ -82,9 +85,85 @@ func (f *NinaFlasher) FlashFirmware(firmwareFile *paths.Path) error { return f.md5sum(data) } -func (f *NinaFlasher) FlashCertificates(certificatePaths *paths.PathList) error { - // TODO - return nil +func (f *NinaFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs []string) error { + var certificatesData []byte + for _, certPath := range *certificatePaths { + logrus.Infof("Converting and flashing certificate %s", certPath) + + data, err := f.certificateFromFile(certPath) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + } + + for _, URL := range URLs { + logrus.Infof("Converting and flashing certificate from %s", URL) + data, err := f.certificateFromURL(URL) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + } + + certificatesDataLimit := 0x20000 + if len(certificatesData) > certificatesDataLimit { + err := fmt.Errorf("certificates data %d exceeds limit of %d bytes", len(certificatesData), certificatesDataLimit) + logrus.Error(err) + return err + } + + // Pad certificatesData to flash page + for len(certificatesData)%int(f.payloadSize) != 0 { + certificatesData = append(certificatesData, 0) + } + + certificatesOffset := 0x10000 + return f.flashChunk(certificatesOffset, certificatesData) +} + +func (f *NinaFlasher) certificateFromFile(certificateFile *paths.Path) ([]byte, error) { + data, err := certificateFile.ReadFile() + if err != nil { + logrus.Error(err) + return nil, err + } + + cert, err := x509.ParseCertificate(data) + if err != nil { + logrus.Error(err) + return nil, err + } + + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}), nil +} + +func (f *NinaFlasher) certificateFromURL(URL string) ([]byte, error) { + config := &tls.Config{ + InsecureSkipVerify: true, + } + + conn, err := tls.Dial("tcp", URL, config) + if err != nil { + logrus.Error(err) + return nil, err + } + defer conn.Close() + + if err := conn.Handshake(); err != nil { + logrus.Error(err) + return nil, err + } + + peerCertificates := conn.ConnectionState().PeerCertificates + if len(peerCertificates) == 0 { + err = fmt.Errorf("no peer certificates found at %s", URL) + logrus.Error(err) + return nil, err + } + + rootCertificate := peerCertificates[len(peerCertificates)-1] + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCertificate.Raw}), nil } // Close the port used by this flasher diff --git a/flasher/sara.go b/flasher/sara.go index 7908d7d9..99399faa 100644 --- a/flasher/sara.go +++ b/flasher/sara.go @@ -104,9 +104,8 @@ func (f *SaraFlasher) FlashFirmware(firmwareFile *paths.Path) error { return err } -func (f *SaraFlasher) FlashCertificates(certificatePaths *paths.PathList) error { - // TODO - return nil +func (f *SaraFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs []string) error { + return fmt.Errorf("not supported by SaraFlasher") } func (f *SaraFlasher) Close() error { diff --git a/flasher/winc.go b/flasher/winc.go index 528580f8..4ef972a4 100644 --- a/flasher/winc.go +++ b/flasher/winc.go @@ -21,6 +21,9 @@ package flasher import ( "bytes" + "crypto/rsa" + "crypto/tls" + "crypto/x509" "encoding/binary" "errors" "fmt" @@ -68,9 +71,113 @@ func (f *WincFlasher) FlashFirmware(firmwareFile *paths.Path) error { return f.flashChunk(firmwareOffset, data) } -func (f *WincFlasher) FlashCertificates(certificatePaths *paths.PathList) error { - // TODO - return nil +func (f *WincFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs []string) error { + var certificatesData []byte + certificatesNumber := 0 + for _, certPath := range *certificatePaths { + logrus.Infof("Converting and flashing certificate %s", certPath) + + data, err := f.certificateFromFile(certPath) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + certificatesNumber++ + } + + for _, URL := range URLs { + logrus.Infof("Converting and flashing certificate from %s", URL) + data, err := f.certificateFromURL(URL) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + certificatesNumber++ + } + + certificatesOffset := 0x4000 + return f.flashChunk(certificatesOffset, certificatesData) +} + +func (f *WincFlasher) certificateFromFile(certificateFile *paths.Path) ([]byte, error) { + data, err := certificateFile.ReadFile() + if err != nil { + logrus.Error(err) + return nil, err + } + + cert, err := x509.ParseCertificate(data) + if err != nil { + logrus.Error(err) + return nil, err + } + + return f.getCertificateData(cert) +} + +func (f *WincFlasher) certificateFromURL(URL string) ([]byte, error) { + config := &tls.Config{ + InsecureSkipVerify: true, + } + + conn, err := tls.Dial("tcp", URL, config) + if err != nil { + logrus.Error(err) + return nil, err + } + defer conn.Close() + + if err := conn.Handshake(); err != nil { + logrus.Error(err) + return nil, err + } + + peerCertificates := conn.ConnectionState().PeerCertificates + if len(peerCertificates) == 0 { + err = fmt.Errorf("no peer certificates found at %s", URL) + logrus.Error(err) + return nil, err + } + rootCertificate := peerCertificates[len(peerCertificates)-1] + return f.getCertificateData(rootCertificate) +} + +func (f *WincFlasher) getCertificateData(cert *x509.Certificate) ([]byte, error) { + b := []byte{} + nameSHA1Bytes, err := calculateNameSha1(cert) + if err != nil { + return nil, err + } + + notBeforeBytes, err := convertTime(cert.NotBefore) + if err != nil { + return nil, err + } + + notAfterBytes, err := convertTime(cert.NotAfter) + if err != nil { + return nil, err + } + + rsaPublicKey := *cert.PublicKey.(*rsa.PublicKey) + + rsaModulusNBytes := modulusN(rsaPublicKey) + rsaPublicExponentBytes := publicExponent(rsaPublicKey) + + rsaModulusNLenBytes := uint16ToBytes(len(rsaModulusNBytes)) + rsaPublicExponentLenBytes := uint16ToBytes(len(rsaPublicExponentBytes)) + + b = append(b, nameSHA1Bytes...) + b = append(b, rsaModulusNLenBytes...) + b = append(b, rsaPublicExponentLenBytes...) + b = append(b, notBeforeBytes...) + b = append(b, notAfterBytes...) + b = append(b, rsaModulusNBytes...) + b = append(b, rsaPublicExponentBytes...) + for (len(b) & 3) != 0 { + b = append(b, 0xff) // padding + } + return b, nil } func (f *WincFlasher) Close() error { From 28278cff99e3f9344ca7c8fd5cbc06944972e90f Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 16:58:27 +0200 Subject: [PATCH 07/17] Fix some utilities functions --- flasher/certificate.go | 20 ++++++------ indexes/firmwareindex/firmwareindex_test.go | 34 +++++++++------------ 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/flasher/certificate.go b/flasher/certificate.go index cb95394c..f6821ef4 100644 --- a/flasher/certificate.go +++ b/flasher/certificate.go @@ -51,35 +51,35 @@ func calculateNameSha1(cert *x509.Certificate) (b []byte, err error) { return } -func convertTime(time time.Time) (b []byte, err error) { +func convertTime(time time.Time) ([]byte, error) { asn1Bytes, err := asn1.Marshal(time) if err != nil { logrus.Error(err) return nil, err } - b = bytes.Repeat([]byte{0x00}, 20) // value must be zero bytes - copy(b, asn1Bytes[2:]) // copy but drop the first two bytes + b := bytes.Repeat([]byte{0x00}, 20) // value must be zero bytes + copy(b, asn1Bytes[2:]) // copy but drop the first two bytes - return + return b, err } func modulusN(publicKey rsa.PublicKey) []byte { return publicKey.N.Bytes() } -func publicExponent(publicKey rsa.PublicKey) (b []byte) { - b = make([]byte, 4) +func publicExponent(publicKey rsa.PublicKey) []byte { + b := make([]byte, 4) binary.BigEndian.PutUint32(b, uint32(publicKey.E)) // strip leading zeros for b[0] == 0 { b = b[1:] } - return + return b } -func uint16ToBytes(i int) (b []byte) { - b = make([]byte, 2) +func uint16ToBytes(i int) []byte { + b := make([]byte, 2) binary.LittleEndian.PutUint16(b, uint16(i)) - return + return b } diff --git a/indexes/firmwareindex/firmwareindex_test.go b/indexes/firmwareindex/firmwareindex_test.go index 41b49073..ed6bf052 100644 --- a/indexes/firmwareindex/firmwareindex_test.go +++ b/indexes/firmwareindex/firmwareindex_test.go @@ -6,7 +6,6 @@ modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -38,41 +37,38 @@ func TestIndexParsing(t *testing.T) { require.NotEmpty(t, index) } -func TestGetLatestFirmwareURL(t *testing.T) { +func TestGetLatestFirmware(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - result, err := index.GetLatestFirmwareURL("arduino:samd:mkr1000") - require.NoError(t, err) - require.NotEmpty(t, result) - require.Equal(t, "https://downloads.arduino.cc/arduino-fwuploader/firmwares/WINC1500/19.6.1/m2m_aio_3a0.bin", result) + firmware := index.GetLatestFirmware("arduino:samd:mkr1000") + require.Equal(t, firmware.Version, "19.6.1") - result, err = index.GetLatestFirmwareURL("arduino:samd:mkr1001") - require.Error(t, err) - require.Empty(t, result) + firmware = index.GetLatestFirmware("arduino:samd:mkr1001") + require.Nil(t, firmware) } -func TestGetFirmwareURL(t *testing.T) { +func TestGetFirmware(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - result, err := index.GetFirmwareURL("arduino:samd:mkr1000", "19.6.1") - require.NoError(t, err) - require.NotEmpty(t, result) + firmware := index.GetFirmware("arduino:samd:mkr1000", "19.6.1") + require.Equal(t, firmware.Version, "19.6.1") - result, err = index.GetFirmwareURL("arduino:samd:mkr1000", "0.0.0") - require.Error(t, err) - require.Empty(t, result) + firmware = index.GetFirmware("arduino:samd:mkr1000", "19.5.2") + require.Equal(t, firmware.Version, "19.5.2") - result, err = index.GetFirmwareURL("arduino:samd:mkr1001", "19.6.1") - require.Error(t, err) - require.Empty(t, result) + firmware = index.GetFirmware("arduino:samd:mkr1000", "0.0.0") + require.Nil(t, firmware) + + firmware = index.GetFirmware("arduino:samd:mkr1001", "19.6.1") + require.Nil(t, firmware) } func TestGetLoaderSketchURL(t *testing.T) { From 1900a09bfd3f8f93478b8ef3c2796b7b72228d38 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Wed, 9 Jun 2021 17:53:34 +0200 Subject: [PATCH 08/17] Add String function to CommandData --- flasher/flasher.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flasher/flasher.go b/flasher/flasher.go index 11759288..42f4d2af 100644 --- a/flasher/flasher.go +++ b/flasher/flasher.go @@ -35,6 +35,10 @@ type CommandData struct { Payload []byte } +func (d CommandData) String() string { + return fmt.Sprintf("%+v, %+v, %+v, %+v", d.Command, d.Address, d.Value, d.Payload) +} + type FlasherError struct { err string } @@ -75,7 +79,7 @@ func openSerial(portAddress string) (serial.Port, error) { // Try another baudrate continue } - logrus.Infof("Opened port %s at %s", portAddress, baudRate) + logrus.Infof("Opened port %s at %d", portAddress, baudRate) if err := port.SetReadTimeout(30 * time.Second); err != nil { return nil, fmt.Errorf("could not set timeout on serial port: %s", err) From a9a89813d359ad90caa862f3c7e8407ae69341d4 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Wed, 9 Jun 2021 17:54:12 +0200 Subject: [PATCH 09/17] Made IndexBoard struct public and enhanced some firmwareindex methods --- indexes/firmwareindex/firmwareindex.go | 54 +++---------------- indexes/firmwareindex/firmwareindex_test.go | 57 +++++++-------------- 2 files changed, 27 insertions(+), 84 deletions(-) diff --git a/indexes/firmwareindex/firmwareindex.go b/indexes/firmwareindex/firmwareindex.go index c071957b..1f5f6a83 100644 --- a/indexes/firmwareindex/firmwareindex.go +++ b/indexes/firmwareindex/firmwareindex.go @@ -23,6 +23,7 @@ import ( "encoding/json" "fmt" "runtime" + "sort" "github.com/arduino/arduino-cli/arduino/security" "github.com/arduino/go-paths-helper" @@ -89,7 +90,6 @@ func LoadIndex(jsonIndexFile *paths.Path) (*Index, error) { if err != nil { return nil, err } - trusted, _, err := security.VerifySignature(jsonIndexFile, jsonSignatureFile, key) if err != nil { logrus. @@ -136,44 +136,15 @@ func LoadIndexNoSign(jsonIndexFile *paths.Path) (*Index, error) { return &index, nil } -// GetLatestFirmwareURL takes the fqbn as parameter and returns the URL of the latest available firmware. -// Not currently implemented for SARA, as the version for it's firmware is a bit strange -func (i *Index) GetLatestFirmwareURL(fqbn string) (string, error) { - board := i.GetBoard(fqbn) - if board == nil { - return "", fmt.Errorf("invalid FQBN: %s", fqbn) - } - - if board.Latest == nil { - return "", fmt.Errorf("cannot find latest version") - } - - return board.Latest.URL, nil -} - -// GetFirmwareURL will take the fqbn of the required board and the version of the firmware as parameters. -// It will return the URL of the required firmware -func (i *Index) GetFirmwareURL(fqbn, v string) (string, error) { - board := i.GetBoard(fqbn) - if board == nil { - return "", fmt.Errorf("invalid FQBN: %s", fqbn) - } - version := semver.ParseRelaxed(v) - for _, firmware := range board.Firmwares { - if firmware.Version.Equal(version) { - return firmware.URL, nil +// GetLatestFirmware returns the specified IndexFirmware version for this board. +// Returns nil if version is not found. +func (b *IndexBoard) GetFirmware(version string) *IndexFirmware { + for _, firmware := range b.Firmwares { + if firmware.Version == version { + return firmware } } - return "", fmt.Errorf("version not found: %s", version) -} - -// GetLoaderSketchURL will take the board's fqbn and return the url of the loader sketch -func (i *Index) GetLoaderSketchURL(fqbn string) (string, error) { - board := i.GetBoard(fqbn) - if board == nil { - return "", fmt.Errorf("invalid FQBN: %s", fqbn) - } - return board.LoaderSketch.URL, nil + return nil } // GetBoard returns the IndexBoard for the given FQBN @@ -196,12 +167,3 @@ func (b *IndexBoard) GetUploaderCommand() string { return b.UploaderCommand.Linux } -// GetModule will take the board's fqbn and return the name of the module -func (i *Index) GetModule(fqbn string) (string, error) { - for _, board := range i.Boards { - if board.Fqbn == fqbn { - return board.Module, nil - } - } - return "", fmt.Errorf("invalid FQBN: %s", fqbn) -} diff --git a/indexes/firmwareindex/firmwareindex_test.go b/indexes/firmwareindex/firmwareindex_test.go index ed6bf052..4e542c1e 100644 --- a/indexes/firmwareindex/firmwareindex_test.go +++ b/indexes/firmwareindex/firmwareindex_test.go @@ -37,68 +37,49 @@ func TestIndexParsing(t *testing.T) { require.NotEmpty(t, index) } -func TestGetLatestFirmware(t *testing.T) { +func TestGetBoard(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - firmware := index.GetLatestFirmware("arduino:samd:mkr1000") - require.Equal(t, firmware.Version, "19.6.1") + board := index.GetBoard("arduino:samd:mkr1000") + require.NotNil(t, board) + require.Equal(t, board.Fqbn, "arduino:samd:mkr1000") - firmware = index.GetLatestFirmware("arduino:samd:mkr1001") - require.Nil(t, firmware) + board = index.GetBoard("arduino:samd:nano_33_iot") + require.NotNil(t, board) + require.Equal(t, board.Fqbn, "arduino:samd:nano_33_iot") + + board = index.GetBoard("arduino:avr:nessuno") + require.Nil(t, board) } -func TestGetFirmware(t *testing.T) { +func TestGetLatestFirmware(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - firmware := index.GetFirmware("arduino:samd:mkr1000", "19.6.1") + firmware := index.GetBoard("arduino:samd:mkr1000").GetLatestFirmware() require.Equal(t, firmware.Version, "19.6.1") - - firmware = index.GetFirmware("arduino:samd:mkr1000", "19.5.2") - require.Equal(t, firmware.Version, "19.5.2") - - firmware = index.GetFirmware("arduino:samd:mkr1000", "0.0.0") - require.Nil(t, firmware) - - firmware = index.GetFirmware("arduino:samd:mkr1001", "19.6.1") - require.Nil(t, firmware) } -func TestGetLoaderSketchURL(t *testing.T) { +func TestGetFirmware(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - result, err := index.GetLoaderSketchURL("arduino:samd:mkr1000") - require.NoError(t, err) - require.NotEmpty(t, result) - - result, err = index.GetLoaderSketchURL("arduino:samd:mkr1001") - require.Error(t, err) - require.Empty(t, result) -} - -func TestGetModule(t *testing.T) { - indexFile := paths.New("testdata/module_firmware_index.json") - t.Logf("testing with index: %s", indexFile) - index, e := LoadIndexNoSign(indexFile) - require.NoError(t, e) - require.NotEmpty(t, index) + firmware := index.GetBoard("arduino:samd:mkr1000").GetFirmware("19.6.1") + require.Equal(t, firmware.Version, "19.6.1") - result, err := index.GetModule("arduino:samd:mkr1000") - require.NoError(t, err) - require.Equal(t, result, "WINC1500") + firmware = index.GetBoard("arduino:samd:mkr1000").GetFirmware("19.5.2") + require.Equal(t, firmware.Version, "19.5.2") - result, err = index.GetModule("arduino:samd:mkr1001") - require.Error(t, err) - require.Empty(t, result) + firmware = index.GetBoard("arduino:samd:mkr1000").GetFirmware("0.0.0") + require.Nil(t, firmware) } From 63d550ba1429bbbe87a4642318870f39116e605b Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 14:35:10 +0200 Subject: [PATCH 10/17] Rebases fixes --- indexes/firmwareindex/firmwareindex.go | 2 -- indexes/firmwareindex/testdata/module_firmware_index.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/indexes/firmwareindex/firmwareindex.go b/indexes/firmwareindex/firmwareindex.go index 1f5f6a83..4b18d4ed 100644 --- a/indexes/firmwareindex/firmwareindex.go +++ b/indexes/firmwareindex/firmwareindex.go @@ -21,7 +21,6 @@ package firmwareindex import ( "encoding/json" - "fmt" "runtime" "sort" @@ -166,4 +165,3 @@ func (b *IndexBoard) GetUploaderCommand() string { // The linux uploader command is considere to be the generic one return b.UploaderCommand.Linux } - diff --git a/indexes/firmwareindex/testdata/module_firmware_index.json b/indexes/firmwareindex/testdata/module_firmware_index.json index 0cced4e7..f47356be 100644 --- a/indexes/firmwareindex/testdata/module_firmware_index.json +++ b/indexes/firmwareindex/testdata/module_firmware_index.json @@ -529,4 +529,4 @@ "linux": "\"{tool_dir}/rp2040load\" -v -D \"{loader.sketch}.elf\"" } } -] \ No newline at end of file +] From e8574a43fc3cc5e193bb8acb78ee62e513f5cd8b Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 14:35:47 +0200 Subject: [PATCH 11/17] Sleep a bit after opening port to flash NINA firmware --- flasher/nina.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flasher/nina.go b/flasher/nina.go index 19bfb824..c326d3ca 100644 --- a/flasher/nina.go +++ b/flasher/nina.go @@ -42,6 +42,7 @@ func NewNinaFlasher(portAddress string) (*NinaFlasher, error) { logrus.Error(err) return nil, err } + time.Sleep(2 * time.Second) f := &NinaFlasher{port: port} payloadSize, err := f.getMaximumPayloadSize() if err != nil { @@ -290,14 +291,17 @@ func (f *NinaFlasher) sendCommand(data CommandData) error { logrus.Debugf("sending command data %s", data) buff := new(bytes.Buffer) if err := binary.Write(buff, binary.BigEndian, data.Command); err != nil { + err = fmt.Errorf("writing command: %s", err) logrus.Error(err) return err } if err := binary.Write(buff, binary.BigEndian, data.Address); err != nil { + err = fmt.Errorf("writing address: %s", err) logrus.Error(err) return err } if err := binary.Write(buff, binary.BigEndian, data.Value); err != nil { + err = fmt.Errorf("writing value: %s", err) logrus.Error(err) return err } @@ -308,6 +312,7 @@ func (f *NinaFlasher) sendCommand(data CommandData) error { length = uint16(len(data.Payload)) } if err := binary.Write(buff, binary.BigEndian, length); err != nil { + err = fmt.Errorf("writing payload length: %s", err) logrus.Error(err) return err } @@ -318,6 +323,7 @@ func (f *NinaFlasher) sendCommand(data CommandData) error { for { sent, err := f.port.Write(bufferData) if err != nil { + err = fmt.Errorf("writing data: %s", err) logrus.Error(err) return err } From 38590fd78bca86c8260b7eb94ab6edf88a8796ec Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 14:36:13 +0200 Subject: [PATCH 12/17] Implemented flash-firmware command --- cli/flash_firmware/flash_firmware.go | 129 +++++++++++++++++++++------ go.mod | 1 + 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/cli/flash_firmware/flash_firmware.go b/cli/flash_firmware/flash_firmware.go index 222ac5cb..8a653cfa 100644 --- a/cli/flash_firmware/flash_firmware.go +++ b/cli/flash_firmware/flash_firmware.go @@ -22,12 +22,19 @@ package flash_firmware import ( "bytes" "os" + "path/filepath" + "strings" "github.com/arduino/FirmwareUploader/flasher" + "github.com/arduino/FirmwareUploader/indexes" + "github.com/arduino/FirmwareUploader/indexes/download" + "github.com/arduino/FirmwareUploader/indexes/firmwareindex" programmer "github.com/arduino/FirmwareUploader/programmers" "github.com/arduino/arduino-cli/arduino/serialutils" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/go-properties-orderedmap" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -58,60 +65,126 @@ func NewCommand() *cobra.Command { } func run(cmd *cobra.Command, args []string) { + packageIndex, err := indexes.GetPackageIndex() + if err != nil { + feedback.Errorf("Can't load package index: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + + firmwareIndex, err := indexes.GetFirmwareIndex() + if err != nil { + feedback.Errorf("Can't load firmware index: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + if fqbn == "" { feedback.Errorf("Error during firmware flashing: missing board fqbn") - os.Exit(errorcodes.ErrGeneric) + os.Exit(errorcodes.ErrBadArgument) } if address == "" { feedback.Errorf("Error during firmware flashing: missing board address") - os.Exit(errorcodes.ErrGeneric) + os.Exit(errorcodes.ErrBadArgument) } + board := firmwareIndex.GetBoard(fqbn) + if board == nil { + feedback.Errorf("Can't find board with %s fqbn", fqbn) + os.Exit(errorcodes.ErrBadArgument) + } + + // Get module name if not specified + moduleName := "" + moduleVersion := "" if module == "" { - // TODO: Get firmware ID for board if not provided + moduleName = board.Module + } else { + moduleSplit := strings.Split(module, "@") + if len(moduleSplit) == 2 { + moduleName = moduleSplit[0] + moduleVersion = moduleSplit[1] + } else { + moduleName = module + } } + // Normalize module name + moduleName = strings.ToUpper(moduleName) - // TODO: Get firmware binary from given ID + var firmware *firmwareindex.IndexFirmware + if moduleVersion == "" { + firmware = board.GetLatestFirmware() + } else { + firmware = board.GetFirmware(moduleVersion) + } + if firmware == nil { + feedback.Errorf("Error getting firmware for board: %s", fqbn) + os.Exit(errorcodes.ErrGeneric) + } - // TODO: Get uploader executable path + firwareFile, err := download.DownloadFirmware(firmware) + if err != nil { + feedback.Errorf("Error downloading firmware from %s: %s", firmware.URL, err) + os.Exit(errorcodes.ErrGeneric) + } - // TODO: Get uploader command line - commandLine := []string{""} + toolRelease := indexes.GetToolRelease(packageIndex, board.Uploader) + if toolRelease == nil { + feedback.Errorf("Error getting upload tool %s for board %s", board.Uploader, board.Fqbn) + os.Exit(errorcodes.ErrGeneric) + } + uploadToolDir, err := download.DownloadTool(toolRelease) + if err != nil { + feedback.Errorf("Error downloading tool %s: %s", board.Uploader, err) + os.Exit(errorcodes.ErrGeneric) + } + + loaderSketchPath, err := download.DownloadLoaderSketch(board.LoaderSketch) + if err != nil { + feedback.Errorf("Error downloading loader sketch from %s: %s", board.LoaderSketch.URL, err) + os.Exit(errorcodes.ErrGeneric) + } - // TODO: Build uploader command line using uploader path, eventual config path and Loader Sketch binary + loaderSketch := strings.ReplaceAll(loaderSketchPath.String(), loaderSketchPath.Ext(), "") - // TODO: Get 1200bps touch from upload properties - use1200bpsTouch := false - if use1200bpsTouch { - feedback.Print("Putting board into bootloader mode") - // TODO: Get waitForUploadPort from upload properties - waitForUploadPort := false - _, err := serialutils.Reset(address, waitForUploadPort, nil) + uploaderCommand := board.GetUploaderCommand() + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{tool_dir}", filepath.FromSlash(uploadToolDir.String())) + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{serial.port.file}", address) + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{loader.sketch}", loaderSketch) + + // TODO: Get uploader command line + commandLine, err := properties.SplitQuotedString(uploaderCommand, "\"", false) + if err != nil { + feedback.Errorf(`Error splitting command line "%s": %s`, uploaderCommand, err) + os.Exit(errorcodes.ErrGeneric) + } + + // Check if board needs a 1200bps touch for upload + if board.UploadTouch { + logrus.Info("Putting board into bootloader mode") + _, err := serialutils.Reset(address, board.UploadWait, nil) if err != nil { - // TODO + feedback.Errorf("Error during firmware flashing: missing board address") + os.Exit(errorcodes.ErrGeneric) } } - // TODO: Flash loader Sketch + // Flash loader Sketch flashOut := new(bytes.Buffer) flashErr := new(bytes.Buffer) - // TODO: Maybe this can be done differently? - var err error - // TODO: OutputFormat is not stored globally, we must store it globally since we need it - OutputFormat := "json" - if OutputFormat == "json" { + // var err error + if feedback.GetFormat() == feedback.JSON { err = programmer.Flash(commandLine, flashOut, flashErr) } else { err = programmer.Flash(commandLine, os.Stdout, os.Stderr) } if err != nil { - // TODO + feedback.Errorf("Error during firmware flashing: %s", err) + os.Exit(errorcodes.ErrGeneric) } // Get flasher depending on which module to use var f flasher.Flasher - switch module { + switch moduleName { case "NINA": f, err = flasher.NewNinaFlasher(address) case "SARA": @@ -120,9 +193,13 @@ func run(cmd *cobra.Command, args []string) { f, err = flasher.NewWincFlasher(address) } if err != nil { - // TODO + feedback.Errorf("Error during firmware flashing: %s", err) + os.Exit(errorcodes.ErrGeneric) } defer f.Close() - // TODO: Flash firmware + if err := f.FlashFirmware(firwareFile); err != nil { + feedback.Errorf("Error during firmware flashing: %s", err) + os.Exit(errorcodes.ErrGeneric) + } } diff --git a/go.mod b/go.mod index ce5af737..4909756d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ replace go.bug.st/serial => github.com/cmaglie/go-serial v0.0.0-20200923162623-b require ( github.com/arduino/arduino-cli v0.0.0-20210603144340-aef5a54882fa github.com/arduino/go-paths-helper v1.6.0 + github.com/arduino/go-properties-orderedmap v1.3.0 // indirect github.com/cmaglie/go.rice v1.0.3 github.com/mattn/go-colorable v0.1.8 github.com/pkg/errors v0.9.1 From bfd6fa281cde4c682776d9354b516147c2213737 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 16:54:23 +0200 Subject: [PATCH 13/17] Fix issue with port closing --- flasher/flasher.go | 4 +++- flasher/nina.go | 15 +++++++++++---- flasher/sara.go | 4 +--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/flasher/flasher.go b/flasher/flasher.go index 42f4d2af..7dae5c11 100644 --- a/flasher/flasher.go +++ b/flasher/flasher.go @@ -82,7 +82,9 @@ func openSerial(portAddress string) (serial.Port, error) { logrus.Infof("Opened port %s at %d", portAddress, baudRate) if err := port.SetReadTimeout(30 * time.Second); err != nil { - return nil, fmt.Errorf("could not set timeout on serial port: %s", err) + err = fmt.Errorf("could not set timeout on serial port: %s", err) + logrus.Error(err) + return nil, err } return port, nil diff --git a/flasher/nina.go b/flasher/nina.go index c326d3ca..a9eb42cc 100644 --- a/flasher/nina.go +++ b/flasher/nina.go @@ -71,19 +71,28 @@ func (f *NinaFlasher) FlashFirmware(firmwareFile *paths.Path) error { return err } + logrus.Debugf("Reading file %s", firmwareFile) data, err := firmwareFile.ReadFile() if err != nil { logrus.Error(err) return err } + logrus.Debugf("Flashing chunks") firmwareOffset := 0x0000 if err := f.flashChunk(firmwareOffset, data); err != nil { logrus.Error(err) return err } - return f.md5sum(data) + logrus.Debugf("Checking md5") + err = f.md5sum(data) + if err != nil { + logrus.Error(err) + return err + } + logrus.Debugf("Flashed all the things") + return nil } func (f *NinaFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs []string) error { @@ -169,9 +178,7 @@ func (f *NinaFlasher) certificateFromURL(URL string) ([]byte, error) { // Close the port used by this flasher func (f *NinaFlasher) Close() error { - err := f.port.Close() - logrus.Error(err) - return err + return f.port.Close() } // Ping the programmer to see if it is alive. diff --git a/flasher/sara.go b/flasher/sara.go index 99399faa..876143b4 100644 --- a/flasher/sara.go +++ b/flasher/sara.go @@ -109,9 +109,7 @@ func (f *SaraFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs [ } func (f *SaraFlasher) Close() error { - err := f.port.Close() - logrus.Error(err) - return err + return f.port.Close() } func (f *SaraFlasher) hello() error { From a21b6a31795184c8305359bf4ff0492e4c846b80 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 16:54:37 +0200 Subject: [PATCH 14/17] Fixed firmware flashing --- cli/flash_firmware/flash_firmware.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cli/flash_firmware/flash_firmware.go b/cli/flash_firmware/flash_firmware.go index 8a653cfa..f4e45c56 100644 --- a/cli/flash_firmware/flash_firmware.go +++ b/cli/flash_firmware/flash_firmware.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/arduino/FirmwareUploader/flasher" "github.com/arduino/FirmwareUploader/indexes" @@ -121,7 +122,7 @@ func run(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - firwareFile, err := download.DownloadFirmware(firmware) + firmwareFile, err := download.DownloadFirmware(firmware) if err != nil { feedback.Errorf("Error downloading firmware from %s: %s", firmware.URL, err) os.Exit(errorcodes.ErrGeneric) @@ -151,7 +152,6 @@ func run(cmd *cobra.Command, args []string) { uploaderCommand = strings.ReplaceAll(uploaderCommand, "{serial.port.file}", address) uploaderCommand = strings.ReplaceAll(uploaderCommand, "{loader.sketch}", loaderSketch) - // TODO: Get uploader command line commandLine, err := properties.SplitQuotedString(uploaderCommand, "\"", false) if err != nil { feedback.Errorf(`Error splitting command line "%s": %s`, uploaderCommand, err) @@ -182,6 +182,10 @@ func run(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } + // Wait a bit after flashing the loader sketch for the board to become + // available again. + time.Sleep(1 * time.Second) + // Get flasher depending on which module to use var f flasher.Flasher switch moduleName { @@ -198,7 +202,7 @@ func run(cmd *cobra.Command, args []string) { } defer f.Close() - if err := f.FlashFirmware(firwareFile); err != nil { + if err := f.FlashFirmware(firmwareFile); err != nil { feedback.Errorf("Error during firmware flashing: %s", err) os.Exit(errorcodes.ErrGeneric) } From ff2623db5e3fd0df542b981c8f9962441d893a33 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 17:12:22 +0200 Subject: [PATCH 15/17] Rename Latest IndexBoard field to LatestFirmware --- cli/firmware/list.go | 2 +- indexes/firmwareindex/firmwareindex.go | 32 +++++++++++++------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cli/firmware/list.go b/cli/firmware/list.go index e835f809..2399f2a1 100644 --- a/cli/firmware/list.go +++ b/cli/firmware/list.go @@ -71,7 +71,7 @@ func list(fqbn string) { BoardFQBN: board.Fqbn, Module: board.Module, FirmwareVersion: firmware.Version, - Latest: board.Latest == firmware, + Latest: board.LatestFirmware == firmware, }) } } diff --git a/indexes/firmwareindex/firmwareindex.go b/indexes/firmwareindex/firmwareindex.go index 4b18d4ed..0a68fb94 100644 --- a/indexes/firmwareindex/firmwareindex.go +++ b/indexes/firmwareindex/firmwareindex.go @@ -22,7 +22,6 @@ package firmwareindex import ( "encoding/json" "runtime" - "sort" "github.com/arduino/arduino-cli/arduino/security" "github.com/arduino/go-paths-helper" @@ -48,7 +47,7 @@ type IndexBoard struct { UploadTouch bool `json:"upload.use_1200bps_touch"` UploadWait bool `json:"upload.wait_for_upload_port"` UploaderCommand *IndexUploaderCommand `json:"uploader.command,required"` - Latest *IndexFirmware `json:"-"` + LatestFirmware *IndexFirmware `json:"-"` } type IndexUploaderCommand struct { @@ -122,12 +121,12 @@ func LoadIndexNoSign(jsonIndexFile *paths.Path) (*Index, error) { // Determine latest firmware for each board for _, board := range index.Boards { if board.Module == "SARA" { - // TODO implement?? by defualt you have to specify the version + // TODO implement?? by default you have to specify the version continue } for _, firmware := range board.Firmwares { - if board.Latest == nil || firmware.Version.GreaterThan(board.Latest.Version) { - board.Latest = firmware + if board.LatestFirmware == nil || firmware.Version.GreaterThan(board.LatestFirmware.Version) { + board.LatestFirmware = firmware } } } @@ -135,17 +134,6 @@ func LoadIndexNoSign(jsonIndexFile *paths.Path) (*Index, error) { return &index, nil } -// GetLatestFirmware returns the specified IndexFirmware version for this board. -// Returns nil if version is not found. -func (b *IndexBoard) GetFirmware(version string) *IndexFirmware { - for _, firmware := range b.Firmwares { - if firmware.Version == version { - return firmware - } - } - return nil -} - // GetBoard returns the IndexBoard for the given FQBN func (i *Index) GetBoard(fqbn string) *IndexBoard { for _, b := range i.Boards { @@ -156,6 +144,18 @@ func (i *Index) GetBoard(fqbn string) *IndexBoard { return nil } +// GetLatestFirmware returns the specified IndexFirmware version for this board. +// Returns nil if version is not found. +func (b *IndexBoard) GetFirmware(version string) *IndexFirmware { + v := semver.ParseRelaxed(version) + for _, firmware := range b.Firmwares { + if firmware.Version.Equal(v) { + return firmware + } + } + return nil +} + func (b *IndexBoard) GetUploaderCommand() string { if runtime.GOOS == "windows" && b.UploaderCommand.Windows != "" { return b.UploaderCommand.Linux From 7fae6dc43e4048eeee3bc8e20deaa6832cbbfaa4 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 17:12:55 +0200 Subject: [PATCH 16/17] Moved flash-firmware to flash firmware subcommand --- cli/firmware/firmware.go | 1 + .../flash_firmware.go => firmware/flash.go} | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) rename cli/{flash_firmware/flash_firmware.go => firmware/flash.go} (94%) diff --git a/cli/firmware/firmware.go b/cli/firmware/firmware.go index 2b81a8a1..9bcf87ce 100644 --- a/cli/firmware/firmware.go +++ b/cli/firmware/firmware.go @@ -33,6 +33,7 @@ func NewCommand() *cobra.Command { Example: " " + os.Args[0] + " firmware ...", } + firmwareCmd.AddCommand(NewFlashCommand()) firmwareCmd.AddCommand(newListCommand()) return firmwareCmd } diff --git a/cli/flash_firmware/flash_firmware.go b/cli/firmware/flash.go similarity index 94% rename from cli/flash_firmware/flash_firmware.go rename to cli/firmware/flash.go index f4e45c56..3d3f98fe 100644 --- a/cli/flash_firmware/flash_firmware.go +++ b/cli/firmware/flash.go @@ -17,7 +17,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -package flash_firmware +package firmware import ( "bytes" @@ -46,15 +46,15 @@ var ( ) // NewCommand created a new `version` command -func NewCommand() *cobra.Command { +func NewFlashCommand() *cobra.Command { command := &cobra.Command{ - Use: "flash-firmware", + Use: "flash", Short: "Flashes firmwares to board.", Long: "Flashes specified module firmware to board at specified address. Module name and version can be omitted to install latest version.", Example: "" + - " " + os.Args[0] + " flash-firmware --fqbn arduino:samd:mkr1000 --address COM10 --module WINC1500@19.5.2\n" + - " " + os.Args[0] + " flash-firmware -b arduino:samd:mkr1000 -a COM10 -m WINC15000\n" + - " " + os.Args[0] + " flash-firmware -b arduino:samd:mkr1000 -a COM10\n", + " " + os.Args[0] + " flash --fqbn arduino:samd:mkr1000 --address COM10 --module WINC1500@19.5.2\n" + + " " + os.Args[0] + " flash -b arduino:samd:mkr1000 -a COM10 -m WINC15000\n" + + " " + os.Args[0] + " flash -b arduino:samd:mkr1000 -a COM10\n", Args: cobra.NoArgs, Run: run, } @@ -113,7 +113,7 @@ func run(cmd *cobra.Command, args []string) { var firmware *firmwareindex.IndexFirmware if moduleVersion == "" { - firmware = board.GetLatestFirmware() + firmware = board.LatestFirmware } else { firmware = board.GetFirmware(moduleVersion) } From d14f07c6891d77ea6302c97fc32cbdc759e15899 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Jun 2021 17:39:36 +0200 Subject: [PATCH 17/17] Fix tests --- indexes/firmwareindex/firmwareindex_test.go | 8 ++++---- indexes/firmwareindex/testdata/module_firmware_index.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indexes/firmwareindex/firmwareindex_test.go b/indexes/firmwareindex/firmwareindex_test.go index 4e542c1e..e7326ece 100644 --- a/indexes/firmwareindex/firmwareindex_test.go +++ b/indexes/firmwareindex/firmwareindex_test.go @@ -63,8 +63,8 @@ func TestGetLatestFirmware(t *testing.T) { require.NoError(t, e) require.NotEmpty(t, index) - firmware := index.GetBoard("arduino:samd:mkr1000").GetLatestFirmware() - require.Equal(t, firmware.Version, "19.6.1") + firmware := index.GetBoard("arduino:samd:mkr1000").LatestFirmware + require.Equal(t, firmware.Version.String(), "19.6.1") } func TestGetFirmware(t *testing.T) { @@ -75,10 +75,10 @@ func TestGetFirmware(t *testing.T) { require.NotEmpty(t, index) firmware := index.GetBoard("arduino:samd:mkr1000").GetFirmware("19.6.1") - require.Equal(t, firmware.Version, "19.6.1") + require.Equal(t, firmware.Version.String(), "19.6.1") firmware = index.GetBoard("arduino:samd:mkr1000").GetFirmware("19.5.2") - require.Equal(t, firmware.Version, "19.5.2") + require.Equal(t, firmware.Version.String(), "19.5.2") firmware = index.GetBoard("arduino:samd:mkr1000").GetFirmware("0.0.0") require.Nil(t, firmware) diff --git a/indexes/firmwareindex/testdata/module_firmware_index.json b/indexes/firmwareindex/testdata/module_firmware_index.json index f47356be..0cced4e7 100644 --- a/indexes/firmwareindex/testdata/module_firmware_index.json +++ b/indexes/firmwareindex/testdata/module_firmware_index.json @@ -529,4 +529,4 @@ "linux": "\"{tool_dir}/rp2040load\" -v -D \"{loader.sketch}.elf\"" } } -] +] \ No newline at end of file