Skip to content

Commit 6f5fe36

Browse files
matteosuppofacchinm
authored andcommitted
Add network program
1 parent 6a77998 commit 6f5fe36

File tree

2 files changed

+187
-10
lines changed

2 files changed

+187
-10
lines changed

programmer/programmer.go

+161-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ package programmer
22

33
import (
44
"bufio"
5+
"bytes"
6+
"fmt"
7+
"io"
8+
"log"
9+
"mime/multipart"
10+
"net/http"
11+
"os"
512
"os/exec"
613
"path/filepath"
714
"regexp"
@@ -13,6 +20,7 @@ import (
1320
"github.com/facchinm/go-serial"
1421
shellwords "github.com/mattn/go-shellwords"
1522
"github.com/pkg/errors"
23+
"github.com/sfreiberg/simplessh"
1624
)
1725

1826
type logger interface {
@@ -84,19 +92,27 @@ func Resolve(port, board, file, commandline string, extra Extra, t locater) (str
8492
return commandline, nil
8593
}
8694

87-
// Do performs a command on a port with a board attached to it
88-
func Do(port, commandline string, extra Extra, l logger) error {
89-
if extra.Network {
90-
doNetwork()
91-
} else {
92-
return doSerial(port, commandline, extra, l)
95+
// Network performs a network upload
96+
func Network(port, board, file, commandline string, auth Auth, l logger) error {
97+
// Defaults
98+
if auth.Username == "" {
99+
auth.Username = "root"
100+
}
101+
if auth.Password == "" {
102+
auth.Password = "arduino"
93103
}
94-
return nil
95-
}
96104

97-
func doNetwork() {}
105+
// try with a form
106+
err := form(port, board, file, auth, l)
107+
if err != nil {
108+
// try with ssh
109+
err = ssh(port, file, commandline, auth, l)
110+
}
111+
return err
112+
}
98113

99-
func doSerial(port, commandline string, extra Extra, l logger) error {
114+
// Serial performs a serial upload
115+
func Serial(port, commandline string, extra Extra, l logger) error {
100116
// some boards needs to be resetted
101117
if extra.Use1200bpsTouch {
102118
var err error
@@ -260,3 +276,138 @@ func program(binary string, args []string, l logger) error {
260276

261277
return errors.Wrapf(err, "Executing command")
262278
}
279+
280+
func form(port, board, file string, auth Auth, l logger) error {
281+
// Prepare a form that you will submit to that URL.
282+
_url := "http://" + port + "/data/upload_sketch_silent"
283+
var b bytes.Buffer
284+
w := multipart.NewWriter(&b)
285+
286+
// Add your image file
287+
file = strings.Trim(file, "\n")
288+
f, err := os.Open(file)
289+
if err != nil {
290+
return errors.Wrapf(err, "Open file %s", file)
291+
}
292+
fw, err := w.CreateFormFile("sketch_hex", file)
293+
if err != nil {
294+
return errors.Wrapf(err, "Create form file")
295+
}
296+
if _, err = io.Copy(fw, f); err != nil {
297+
return errors.Wrapf(err, "Copy form file")
298+
}
299+
// Add the other fields
300+
board = strings.Replace(board, ":", "_", -1)
301+
if fw, err = w.CreateFormField("board"); err != nil {
302+
return errors.Wrapf(err, "Create board field")
303+
}
304+
if _, err = fw.Write([]byte(board)); err != nil {
305+
return errors.Wrapf(err, "")
306+
}
307+
// Don't forget to close the multipart writer.
308+
// If you don't close it, your request will be missing the terminating boundary.
309+
w.Close()
310+
311+
// Now that you have a form, you can submit it to your handler.
312+
req, err := http.NewRequest("POST", _url, &b)
313+
if err != nil {
314+
return errors.Wrapf(err, "Create POST req")
315+
}
316+
317+
// Don't forget to set the content type, this will contain the boundary.
318+
req.Header.Set("Content-Type", w.FormDataContentType())
319+
if auth.Username != "" {
320+
req.SetBasicAuth(auth.Username, auth.Password)
321+
}
322+
323+
info(l, "Network upload on ", port)
324+
325+
// Submit the request
326+
client := &http.Client{}
327+
res, err := client.Do(req)
328+
if err != nil {
329+
log.Println("Error during post request")
330+
return errors.Wrapf(err, "")
331+
}
332+
333+
// Check the response
334+
if res.StatusCode != http.StatusOK {
335+
return errors.Wrapf(err, "Bad status: %s", res.Status)
336+
}
337+
return nil
338+
}
339+
340+
func ssh(port, file, commandline string, auth Auth, l logger) error {
341+
// Connect via ssh
342+
client, err := simplessh.ConnectWithPassword(port+":22", auth.Username, auth.Password)
343+
debug(l, "Connect via ssh ", client, err)
344+
if err != nil {
345+
return errors.Wrapf(err, "Connect via ssh")
346+
}
347+
defer client.Close()
348+
349+
// Copy the sketch
350+
err = scp(client, file, "/tmp/sketch"+filepath.Ext(file))
351+
debug(l, "Copy the sketch ", err)
352+
if err != nil {
353+
return errors.Wrapf(err, "Copy sketch")
354+
}
355+
356+
// very special case for Yun (remove once AVR boards.txt is fixed)
357+
if commandline == "" {
358+
commandline = "merge-sketch-with-bootloader.lua /tmp/sketch.hex && /usr/bin/run-avrdude /tmp/sketch.hex"
359+
}
360+
361+
// Execute commandline
362+
output, err := client.Exec(commandline)
363+
debug(l, "Execute commandline ", commandline, output, err)
364+
if err != nil {
365+
return errors.Wrapf(err, "Execute commandline")
366+
}
367+
return nil
368+
}
369+
370+
// scp uploads sourceFile to remote machine like native scp console app.
371+
func scp(client *simplessh.Client, sourceFile, targetFile string) error {
372+
// open ssh session
373+
session, err := client.SSHClient.NewSession()
374+
if err != nil {
375+
return errors.Wrapf(err, "open ssh session")
376+
}
377+
defer session.Close()
378+
379+
// open file
380+
src, err := os.Open(sourceFile)
381+
if err != nil {
382+
return errors.Wrapf(err, "open file %s", sourceFile)
383+
}
384+
385+
// stat file
386+
srcStat, err := src.Stat()
387+
if err != nil {
388+
return errors.Wrapf(err, "stat file %s", sourceFile)
389+
}
390+
391+
// Copy over ssh
392+
go func() {
393+
w, _ := session.StdinPipe()
394+
395+
fmt.Fprintln(w, "C0644", srcStat.Size(), filepath.Base(targetFile))
396+
397+
if srcStat.Size() > 0 {
398+
io.Copy(w, src)
399+
fmt.Fprint(w, "\x00")
400+
w.Close()
401+
} else {
402+
fmt.Fprint(w, "\x00")
403+
w.Close()
404+
}
405+
406+
}()
407+
408+
if err := session.Run("scp -t " + targetFile); err != nil {
409+
return errors.Wrapf(err, "Execute %s", "scp -t "+targetFile)
410+
}
411+
412+
return nil
413+
}

programmer/programmer_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func (mockTools) GetLocation(el string) (string, error) {
1616
return "$loc" + el, nil
1717
}
1818

19+
// TestSerialData requires a leonardo connected to the /dev/ttyACM0 port
1920
var TestSerialData = []struct {
2021
Name string
2122
Port string
@@ -41,6 +42,31 @@ func TestSerial(t *testing.T) {
4142
t.Fail()
4243
}
4344

45+
var TestNetworkData = []struct {
46+
Name string
47+
Port string
48+
Commandline string
49+
Extra programmer.Extra
50+
}{
51+
{
52+
"yun", "",
53+
``, programmer.Extra{Network: true}},
54+
}
55+
56+
func TestNetwork(t *testing.T) {
57+
logger := logrus.New()
58+
logger.Level = logrus.DebugLevel
59+
60+
home, _ := homedir.Dir()
61+
62+
for _, test := range TestNetworkData {
63+
commandline := strings.Replace(test.Commandline, "~", home, -1)
64+
err := programmer.Do(test.Port, commandline, test.Extra, logger)
65+
log.Println(err)
66+
}
67+
t.Fail()
68+
}
69+
4470
var TestResolveData = []struct {
4571
Port string
4672
Board string

0 commit comments

Comments
 (0)