Skip to content

Commit 8adabc0

Browse files
committed
programfromurl more status updates for better UI
Former-commit-id: 15246e42f690074cfd5150cf9df8c15efa6ae234 [formerly 9cb45a6be4361b424d29a1d618b11abe933b8255] Former-commit-id: 08fdcf34976797b566d89da1ea21675b847c7e4c
1 parent f823fa4 commit 8adabc0

File tree

6 files changed

+282
-248
lines changed

6 files changed

+282
-248
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ memstats | | Send back data on the memory usage and garbage collection performan
143143
broadcast string | broadcast my data | Send in this command and you will get a message reflected back to all connected endpoints. This is useful for communicating with all connected clients, i.e. in a CNC scenario is a pendant wants to ask the main workspace if there are any settings it should know about. For example send in "broadcast this is my custom cmd" and get this reflected back to all connected sockets {"Cmd":"Broadcast","Msg":"this is my custom cmd\n"}
144144
version | | Get the software version of SPJS that is running
145145
hostname | | Get the hostname of the current SPJS instance
146-
program port board:name $path/to/filename/without/extension | | Send a hex file to your Arduino board to program it.
146+
program port core:architecture:name $path/to/filename/without/extension | programfromurl com3 arduino:avr:uno c:\myfiles\grbl_v0_9i_atmega328p_16mhz_115200.hex | Send a hex file to your Arduino board to program it.
147+
programfromurl port core:architecture:name url | programfromurl com3 arduino:avr:uno https://raw.githubusercontent.com/grbl/grbl-builds/master/builds/grbl_v0_9i_atmega328p_16mhz_115200.hex | Download a hex file from a URL and then send it to your Arduino board to program it.
147148

148149
Garbage collection
149150
-------

download.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package main
33

44
import (
55
"errors"
6-
"fmt"
76
"io"
87
"io/ioutil"
8+
"log"
99
"net/http"
1010
"os"
1111
"path/filepath"
@@ -25,33 +25,33 @@ func downloadFromUrl(url string) (filename string, err error) {
2525
}
2626
tokens := strings.Split(url, "/")
2727
filePrefix := tokens[len(tokens)-1]
28-
fmt.Println("The filePrefix is", filePrefix)
28+
log.Println("The filePrefix is", filePrefix)
2929

3030
fileName, _ := filepath.Abs(tmpdir + "/" + filePrefix)
31-
fmt.Println("Downloading", url, "to", fileName)
31+
log.Println("Downloading", url, "to", fileName)
3232

3333
// TODO: check file existence first with io.IsExist
3434
output, err := os.Create(fileName)
3535
if err != nil {
36-
fmt.Println("Error while creating", fileName, "-", err)
36+
log.Println("Error while creating", fileName, "-", err)
3737
return fileName, err
3838
}
3939
defer output.Close()
4040

4141
response, err := http.Get(url)
4242
if err != nil {
43-
fmt.Println("Error while downloading", url, "-", err)
43+
log.Println("Error while downloading", url, "-", err)
4444
return fileName, err
4545
}
4646
defer response.Body.Close()
4747

4848
n, err := io.Copy(output, response.Body)
4949
if err != nil {
50-
fmt.Println("Error while downloading", url, "-", err)
50+
log.Println("Error while downloading", url, "-", err)
5151
return fileName, err
5252
}
5353

54-
fmt.Println(n, "bytes downloaded.")
54+
log.Println(n, "bytes downloaded.")
5555

5656
return fileName, nil
5757
}

main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ var (
2525
versionFloat = float32(1.82)
2626
addr = flag.String("addr", ":8989", "http service address")
2727
//assets = flag.String("assets", defaultAssetPath(), "path to assets")
28-
//verbose = flag.Bool("v", true, "show debug logging")
29-
verbose = flag.Bool("v", false, "show debug logging")
28+
verbose = flag.Bool("v", true, "show debug logging")
29+
//verbose = flag.Bool("v", false, "show debug logging")
3030
//homeTempl *template.Template
3131
isLaunchSelf = flag.Bool("ls", false, "launch self 5 seconds later")
3232

programmer.go

+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
//"fmt"
6+
"encoding/json"
7+
"github.com/facchinm/go-serial"
8+
"github.com/kardianos/osext"
9+
"log"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
13+
"strconv"
14+
"strings"
15+
"time"
16+
)
17+
18+
// Download the file from URL first, store in tmp folder, then pass to spProgram
19+
func spProgramFromUrl(portname string, boardname string, url string) {
20+
mapB, _ := json.Marshal(map[string]string{"ProgrammerStatus": "DownloadStart", "Url": url})
21+
h.broadcastSys <- mapB
22+
filename, err := downloadFromUrl(url)
23+
mapB, _ = json.Marshal(map[string]string{"ProgrammerStatus": "DownloadDone", "Filename": filename, "Url": url})
24+
h.broadcastSys <- mapB
25+
26+
if err != nil {
27+
spErr(err.Error())
28+
} else {
29+
spProgram(portname, boardname, filename)
30+
}
31+
32+
// delete file
33+
34+
}
35+
36+
func spProgram(portname string, boardname string, filePath string) {
37+
38+
isFound, flasher, mycmd := assembleCompilerCommand(boardname, portname, filePath)
39+
mapD := map[string]string{"ProgrammerStatus": "CommandReady", "IsFound": strconv.FormatBool(isFound), "Flasher": flasher, "Cmd": strings.Join(mycmd, " ")}
40+
mapB, _ := json.Marshal(mapD)
41+
h.broadcastSys <- mapB
42+
43+
if isFound {
44+
spHandlerProgram(flasher, mycmd)
45+
} else {
46+
spErr("We could not find the serial port " + portname + " or the board " + boardname + " that you were trying to program.")
47+
}
48+
}
49+
50+
func spHandlerProgram(flasher string, cmdString []string) {
51+
52+
var oscmd *exec.Cmd
53+
// if runtime.GOOS == "darwin" {
54+
// sh, _ := exec.LookPath("sh")
55+
// // prepend the flasher to run it via sh
56+
// cmdString = append([]string{flasher}, cmdString...)
57+
// oscmd = exec.Command(sh, cmdString...)
58+
// } else {
59+
oscmd = exec.Command(flasher, cmdString...)
60+
// }
61+
62+
// Stdout buffer
63+
//var cmdOutput []byte
64+
65+
//h.broadcastSys <- []byte("Start flashing with command " + cmdString)
66+
log.Printf("Flashing with command:" + strings.Join(cmdString, " "))
67+
mapD := map[string]string{"ProgrammerStatus": "Starting", "Cmd": strings.Join(cmdString, " ")}
68+
mapB, _ := json.Marshal(mapD)
69+
h.broadcastSys <- mapB
70+
71+
cmdOutput, err := oscmd.CombinedOutput()
72+
73+
if err != nil {
74+
log.Printf("Command finished with error: %v "+string(cmdOutput), err)
75+
h.broadcastSys <- []byte("Could not program the board")
76+
mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board", "Output": string(cmdOutput)}
77+
mapB, _ := json.Marshal(mapD)
78+
h.broadcastSys <- mapB
79+
} else {
80+
log.Printf("Finished without error. Good stuff. stdout: " + string(cmdOutput))
81+
h.broadcastSys <- []byte("Flash OK!")
82+
mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok", "Output": string(cmdOutput)}
83+
mapB, _ := json.Marshal(mapD)
84+
h.broadcastSys <- mapB
85+
// analyze stdin
86+
87+
}
88+
}
89+
90+
func formatCmdline(cmdline string, boardOptions map[string]string) (string, bool) {
91+
92+
list := strings.Split(cmdline, "{")
93+
if len(list) == 1 {
94+
return cmdline, false
95+
}
96+
cmdline = ""
97+
for _, item := range list {
98+
item_s := strings.Split(item, "}")
99+
item = boardOptions[item_s[0]]
100+
if len(item_s) == 2 {
101+
cmdline += item + item_s[1]
102+
} else {
103+
if item != "" {
104+
cmdline += item
105+
} else {
106+
cmdline += item_s[0]
107+
}
108+
}
109+
}
110+
log.Println(cmdline)
111+
return cmdline, true
112+
}
113+
114+
func containsStr(s []string, e string) bool {
115+
for _, a := range s {
116+
if a == e {
117+
return true
118+
}
119+
}
120+
return false
121+
}
122+
123+
func assembleCompilerCommand(boardname string, portname string, filePath string) (bool, string, []string) {
124+
125+
// get executable (self)path and use it as base for all other paths
126+
execPath, _ := osext.Executable()
127+
128+
boardFields := strings.Split(boardname, ":")
129+
if len(boardFields) != 3 {
130+
h.broadcastSys <- []byte("Board need to be specified in core:architecture:name format")
131+
return false, "", nil
132+
}
133+
tempPath := (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/boards.txt")
134+
file, err := os.Open(tempPath)
135+
if err != nil {
136+
h.broadcastSys <- []byte("Could not find board: " + boardname)
137+
log.Println("Error:", err)
138+
return false, "", nil
139+
}
140+
scanner := bufio.NewScanner(file)
141+
142+
boardOptions := make(map[string]string)
143+
uploadOptions := make(map[string]string)
144+
145+
for scanner.Scan() {
146+
// map everything matching with boardname
147+
if strings.Contains(scanner.Text(), boardFields[2]) {
148+
arr := strings.Split(scanner.Text(), "=")
149+
arr[0] = strings.Replace(arr[0], boardFields[2]+".", "", 1)
150+
boardOptions[arr[0]] = arr[1]
151+
}
152+
}
153+
154+
boardOptions["serial.port"] = portname
155+
boardOptions["serial.port.file"] = filepath.Base(portname)
156+
157+
// filepath need special care; the project_name var is the filename minus its extension (hex or bin)
158+
// if we are going to modify standard IDE files we also could pass ALL filename
159+
filePath = strings.Trim(filePath, "\n")
160+
boardOptions["build.path"] = filepath.Dir(filePath)
161+
boardOptions["build.project_name"] = strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filepath.Base(filePath)))
162+
163+
file.Close()
164+
165+
// get infos about the programmer
166+
tempPath = (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/platform.txt")
167+
file, err = os.Open(tempPath)
168+
if err != nil {
169+
h.broadcastSys <- []byte("Could not find board: " + boardname)
170+
log.Println("Error:", err)
171+
return false, "", nil
172+
}
173+
scanner = bufio.NewScanner(file)
174+
175+
tool := boardOptions["upload.tool"]
176+
177+
for scanner.Scan() {
178+
// map everything matching with upload
179+
if strings.Contains(scanner.Text(), tool) {
180+
arr := strings.Split(scanner.Text(), "=")
181+
uploadOptions[arr[0]] = arr[1]
182+
arr[0] = strings.Replace(arr[0], "tools."+tool+".", "", 1)
183+
boardOptions[arr[0]] = arr[1]
184+
// we have a "=" in command line
185+
if len(arr) > 2 {
186+
boardOptions[arr[0]] = arr[1] + "=" + arr[2]
187+
}
188+
}
189+
}
190+
file.Close()
191+
192+
// multiple verisons of the same programmer can be handled if "version" is specified
193+
version := uploadOptions["runtime.tools."+tool+".version"]
194+
path := (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/" + version)
195+
if err != nil {
196+
h.broadcastSys <- []byte("Could not find board: " + boardname)
197+
log.Println("Error:", err)
198+
return false, "", nil
199+
}
200+
201+
boardOptions["runtime.tools."+tool+".path"] = path
202+
203+
cmdline := boardOptions["upload.pattern"]
204+
// remove cmd.path as it is handled differently
205+
cmdline = strings.Replace(cmdline, "\"{cmd.path}\"", " ", 1)
206+
cmdline = strings.Replace(cmdline, "\"{path}/{cmd}\"", " ", 1)
207+
cmdline = strings.Replace(cmdline, "\"", "", -1)
208+
209+
// split the commandline in substrings and recursively replace mapped strings
210+
cmdlineSlice := strings.Split(cmdline, " ")
211+
var winded = true
212+
for index, _ := range cmdlineSlice {
213+
winded = true
214+
for winded != false {
215+
cmdlineSlice[index], winded = formatCmdline(cmdlineSlice[index], boardOptions)
216+
}
217+
}
218+
219+
// some boards (eg. Leonardo, Yun) need a special procedure to enter bootloader
220+
if boardOptions["upload.use_1200bps_touch"] == "true" {
221+
// triggers bootloader mode
222+
// the portname could change in this occasion, so fail gently
223+
log.Println("Restarting in bootloader mode")
224+
225+
mode := &serial.Mode{
226+
BaudRate: 1200,
227+
Vmin: 1,
228+
Vtimeout: 0,
229+
}
230+
port, err := serial.OpenPort(portname, mode)
231+
if err != nil {
232+
log.Println(err)
233+
return false, "", nil
234+
}
235+
//port.SetDTR(false)
236+
port.Close()
237+
time.Sleep(time.Second / 2)
238+
// time.Sleep(time.Second / 4)
239+
// wait for port to reappear
240+
if boardOptions["upload.wait_for_upload_port"] == "true" {
241+
ports, _ := serial.GetPortsList()
242+
for !(containsStr(ports, portname)) {
243+
ports, _ = serial.GetPortsList()
244+
}
245+
}
246+
}
247+
248+
tool = (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/bin/" + tool)
249+
// the file doesn't exist, we are on windows
250+
if _, err := os.Stat(tool); err != nil {
251+
tool = tool + ".exe"
252+
// convert all "/" to "\"
253+
tool = strings.Replace(tool, "/", "\\", -1)
254+
}
255+
256+
// remove blanks from cmdlineSlice
257+
var cmdlineSliceOut []string
258+
for _, element := range cmdlineSlice {
259+
if element != "" {
260+
cmdlineSliceOut = append(cmdlineSliceOut, element)
261+
}
262+
}
263+
264+
return (tool != ""), tool, cmdlineSliceOut
265+
}

0 commit comments

Comments
 (0)