├── .gitignore
├── .gitmodules
├── .project
├── .travis.yml
├── Dockerfile
├── LICENSE.md
├── README.md
├── altbuild.sh
├── bufferflow.go
├── bufferflow_default.go
├── bufferflow_dummypause.go
├── bufferflow_grbl.go
├── bufferflow_marlin.go
├── bufferflow_nodemcu.go
├── bufferflow_timed.go
├── bufferflow_tinyg.go
├── bufferflow_tinyg_v2.go
├── bufferflow_tinygg2.go
├── bufferflow_tinygpktmode.go
├── bufferflow_tinygtidmode.go
├── cayenn.go
├── compile_go1_12_crosscompile_fromwindows.sh
├── compile_go1_5_crosscompile.sh
├── compile_spjs.sh
├── compile_webidebridge.sh
├── conn.go
├── download.go
├── drivers
└── windows
│ └── TinyGv2.inf
├── dummy.go
├── execprocess.go
├── feedrateoverride.go
├── gpio.go
├── gpio_linux_arm.go
├── home.html
├── hub.go
├── initd_script.go
├── main.go
├── programmer.go
├── queue.go
├── queue_tid.go
├── release.sh
├── release_windows.sh
├── sample-cert.pem
├── sample-key.pem
├── serial.go
├── seriallist.go
├── seriallist_darwin.go
├── seriallist_linux.go
├── seriallist_windows.go
├── serialport.go
├── usb.go
├── usb_linux.go
└── usb_other.go
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | bufferflow_tinyg_old.md
3 |
4 | serial-port-json-server
5 |
6 | snapshot/*
7 |
8 | *.exe
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "arduino"]
2 | path = arduino
3 | url = https://github.com/facchinm/arduino-flash-tools
4 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | sps
4 |
5 |
6 |
7 |
8 |
9 | com.googlecode.goclipse.goBuilder
10 |
11 |
12 |
13 |
14 |
15 | goclipse.goNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.5
5 | - tip
6 |
7 | deploy:
8 | provider: releases
9 | file:
10 | - "serial-port-json-server"
11 | - "serial-port-json-server.exe"
12 | skip_cleanup: true
13 | on:
14 | tags: true
15 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.12-alpine
2 | ARG release_version=1.96
3 | ARG release_filename=serial-port-json-server-$release_version\_linux_amd64
4 |
5 | RUN apk update && apk add wget gzip
6 |
7 | WORKDIR /runtime
8 | # https://github.com/chilipeppr/serial-port-json-server/releases
9 | RUN wget https://github.com/chilipeppr/serial-port-json-server/releases/download/v$release_version/$release_filename.tar.gz
10 | RUN gzip -d $release_filename.tar.gz
11 | RUN tar -xvf $release_filename.tar
12 | WORKDIR /runtime/$release_filename
13 | RUN chmod 777 serial-port-json-server
14 |
15 | EXPOSE 8988
16 | EXPOSE 8989
17 |
18 | CMD ["./serial-port-json-server", "start"]
19 |
20 | # example usage:
21 | # docker build -t chilipeppr-json-server .
22 | # docker run --net=host --device=/dev/ttyS0:/dev/ttyS0 -it chilipeppr-json-server
23 |
--------------------------------------------------------------------------------
/altbuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "About to cross-compile Serial Port JSON Server"
4 | #echo '$0 = ' $0
5 | #echo '$1 = ' $1
6 | #echo '$2 = ' $2
7 |
8 | if [ "$1" = "" ]; then
9 | echo "You need to pass in the version number as the first parameter."
10 | exit
11 | fi
12 |
13 | # turn on echo
14 | set -x
15 | #set -v
16 |
17 | # Windows x32 and x64, Linux
18 | goxc -bc=windows,linux -d="." -pv=$1 -tasks-=pkg-build default -GOARM=6
19 |
20 | # Rename arm to arm6
21 | #set +x
22 | FILE=$1'/serial-port-json-server_'$1'_linux_arm.tar.gz'
23 | FILE2=$1'/serial-port-json-server_'$1'_linux_armv6.tar.gz'
24 | #set -x
25 | mv $FILE $FILE2
26 |
27 | # Special build for armv7 for BBB and Raspi2
28 | goxc -bc=linux,arm -d="." -pv=$1 -tasks-=pkg-build default -GOARM=7
29 | FILE3=$1'/serial-port-json-server_'$1'_linux_armv7.tar.gz'
30 | mv $FILE $FILE3
31 |
32 | # Special build for armv8
33 | goxc -bc=linux,arm -d="." -pv=$1 -tasks-=pkg-build default -GOARM=8
34 | FILE4=$1'/serial-port-json-server_'$1'_linux_armv8.tar.gz'
35 | mv $FILE $FILE4
36 |
37 |
--------------------------------------------------------------------------------
/bufferflow.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //"log"
4 | //"time"
5 |
6 | var availableBufferAlgorithms = []string{"default", "timed", "nodemcu", "tinyg", "tinyg_old", "tinyg_linemode", "tinyg_tidmode", "tinygg2", "grbl", "marlin"}
7 |
8 | //var availableBufferAlgorithms = []string{"default", "tinyg", "tinygg2", "dummypause", "grbl"}
9 |
10 | type BufferMsg struct {
11 | Cmd string
12 | Port string
13 | TriggeringResponse string
14 | //Desc string
15 | //Desc string
16 | }
17 |
18 | type Bufferflow interface {
19 | BlockUntilReady(cmd string, id string) (bool, bool, string) // implement this method
20 | //JustQueue(cmd string, id string) bool // implement this method
21 | OnIncomingData(data string) // implement this method
22 | ClearOutSemaphore() // implement this method
23 | BreakApartCommands(cmd string) []string // implement this method
24 | Pause() // implement this method
25 | Unpause() // implement this method
26 | GetManualPaused() bool
27 | SetManualPaused(isPaused bool)
28 | SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool // implement this method
29 | SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool // implement this method
30 | SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool // implement this method
31 | SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool // implement this method
32 | SeeIfSpecificCommandsReturnNoResponse(cmd string) bool // implement this method
33 | ReleaseLock() // implement this method
34 | IsBufferGloballySendingBackIncomingData() bool // implement this method
35 | Close() // implement this method
36 | RewriteSerialData(cmd string, id string) string // implement this method
37 | }
38 |
39 | /*data packets returned to client*/
40 | type DataCmdComplete struct {
41 | Cmd string
42 | Id string
43 | P string
44 | BufSize int `json:"-"`
45 | D string `json:"-"`
46 | }
47 |
48 | type DataPerLine struct {
49 | P string
50 | D string
51 | }
52 |
--------------------------------------------------------------------------------
/bufferflow_default.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | //"regexp"
6 | //"strconv"
7 | //"time"
8 | )
9 |
10 | type BufferflowDefault struct {
11 | Name string
12 | Port string
13 | }
14 |
15 | var ()
16 |
17 | func (b *BufferflowDefault) Init() {
18 | log.Println("Initting default buffer flow (which means no buffering)")
19 | }
20 |
21 | func (b *BufferflowDefault) RewriteSerialData(cmd string, id string) string {
22 | return ""
23 | }
24 |
25 | func (b *BufferflowDefault) BlockUntilReady(cmd string, id string) (bool, bool, string) {
26 | //log.Printf("BlockUntilReady() start\n")
27 | return true, false, ""
28 | }
29 |
30 | func (b *BufferflowDefault) OnIncomingData(data string) {
31 | //log.Printf("OnIncomingData() start. data:%v\n", data)
32 | }
33 |
34 | // Clean out b.sem so it can truly block
35 | func (b *BufferflowDefault) ClearOutSemaphore() {
36 | }
37 |
38 | func (b *BufferflowDefault) BreakApartCommands(cmd string) []string {
39 | return []string{cmd}
40 | }
41 |
42 | func (b *BufferflowDefault) Pause() {
43 | return
44 | }
45 |
46 | func (b *BufferflowDefault) Unpause() {
47 | return
48 | }
49 |
50 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
51 | return false
52 | }
53 |
54 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
55 | return false
56 | }
57 |
58 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
59 | return false
60 | }
61 |
62 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
63 | return false
64 | }
65 |
66 | func (b *BufferflowDefault) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
67 | return false
68 | }
69 |
70 | func (b *BufferflowDefault) ReleaseLock() {
71 | }
72 |
73 | func (b *BufferflowDefault) IsBufferGloballySendingBackIncomingData() bool {
74 | return false
75 | }
76 |
77 | func (b *BufferflowDefault) Close() {
78 | }
79 |
80 | func (b *BufferflowDefault) GetManualPaused() bool {
81 | return false
82 | }
83 |
84 | func (b *BufferflowDefault) SetManualPaused(isPaused bool) {
85 | }
86 |
--------------------------------------------------------------------------------
/bufferflow_dummypause.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "time"
6 | )
7 |
8 | type BufferflowDummypause struct {
9 | Name string
10 | Port string
11 | NumLines int
12 | Paused bool
13 | }
14 |
15 | func (b *BufferflowDummypause) Init() {
16 | }
17 |
18 | func (b *BufferflowDummypause) RewriteSerialData(cmd string, id string) string {
19 | return ""
20 | }
21 |
22 | func (b *BufferflowDummypause) BlockUntilReady(cmd string, id string) (bool, bool, string) {
23 | log.Printf("BlockUntilReady() start. numLines:%v\n", b.NumLines)
24 | log.Printf("buffer:%v\n", b)
25 | //for b.Paused {
26 | log.Println("We are paused for 3 seconds. Yeilding send.")
27 | time.Sleep(3000 * time.Millisecond)
28 | //}
29 | log.Printf("BlockUntilReady() end\n")
30 | return true, false, ""
31 | }
32 |
33 | func (b *BufferflowDummypause) OnIncomingData(data string) {
34 | log.Printf("OnIncomingData() start. data:%v\n", data)
35 | b.NumLines++
36 | //time.Sleep(3000 * time.Millisecond)
37 | log.Printf("OnIncomingData() end. numLines:%v\n", b.NumLines)
38 | }
39 |
40 | // Clean out b.sem so it can truly block
41 | func (b *BufferflowDummypause) ClearOutSemaphore() {
42 | }
43 |
44 | func (b *BufferflowDummypause) BreakApartCommands(cmd string) []string {
45 | return []string{cmd}
46 | }
47 |
48 | func (b *BufferflowDummypause) Pause() {
49 | return
50 | }
51 |
52 | func (b *BufferflowDummypause) Unpause() {
53 | return
54 | }
55 |
56 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
57 | return false
58 | }
59 |
60 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
61 | return false
62 | }
63 |
64 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
65 | return false
66 | }
67 |
68 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
69 | return false
70 | }
71 |
72 | func (b *BufferflowDummypause) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
73 | /*
74 | // remove comments
75 | cmd = b.reComment.ReplaceAllString(cmd, "")
76 | cmd = b.reComment2.ReplaceAllString(cmd, "")
77 | if match := b.reNoResponse.MatchString(cmd); match {
78 | log.Printf("Found cmd that does not get a response from TinyG. cmd:%v\n", cmd)
79 | return true
80 | }
81 | */
82 | return false
83 | }
84 |
85 | func (b *BufferflowDummypause) ReleaseLock() {
86 | }
87 |
88 | func (b *BufferflowDummypause) IsBufferGloballySendingBackIncomingData() bool {
89 | return false
90 | }
91 |
92 | func (b *BufferflowDummypause) Close() {
93 | }
94 |
95 | func (b *BufferflowDummypause) GetManualPaused() bool {
96 | return false
97 | }
98 |
99 | func (b *BufferflowDummypause) SetManualPaused(isPaused bool) {
100 | }
101 |
--------------------------------------------------------------------------------
/bufferflow_marlin.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "regexp"
7 | "strconv"
8 | "strings"
9 | "sync"
10 | "time"
11 | )
12 |
13 | type BufferflowMarlin struct {
14 | Name string
15 | Port string
16 | Paused bool
17 | BufferMax int
18 | q *Queue
19 |
20 | // use thread locking for b.Paused
21 | lock *sync.Mutex
22 |
23 | sem chan int
24 | LatestData string
25 | LastStatus string
26 | version string
27 | quit chan int
28 | parent_serport *serport
29 |
30 | reNewLine *regexp.Regexp
31 | ok *regexp.Regexp
32 | err *regexp.Regexp
33 | initline *regexp.Regexp
34 | qry *regexp.Regexp
35 | rpt *regexp.Regexp
36 | }
37 |
38 | func (b *BufferflowMarlin) Init() {
39 | b.lock = &sync.Mutex{}
40 | b.SetPaused(false, 1)
41 |
42 | log.Println("Initting MARLIN buffer flow")
43 | b.BufferMax = 127 //max buffer size 127 bytes available
44 |
45 | b.q = NewQueue()
46 |
47 | //create channels
48 | b.sem = make(chan int)
49 |
50 | //define regex
51 | b.reNewLine, _ = regexp.Compile("\\r{0,1}\\n{1,2}") //\\r{0,1}
52 | b.ok, _ = regexp.Compile("^ok")
53 | b.err, _ = regexp.Compile("^error")
54 | b.initline, _ = regexp.Compile("^echo:Marlin")
55 | b.qry, _ = regexp.Compile("\\?")
56 | b.rpt, _ = regexp.Compile("^X:")
57 |
58 | //initialize query loop
59 | b.rptQueryLoop(b.parent_serport) // Disable the query loop
60 | }
61 |
62 | func (b *BufferflowMarlin) RewriteSerialData(cmd string, id string) string {
63 | return ""
64 | }
65 |
66 | func (b *BufferflowMarlin) BlockUntilReady(cmd string, id string) (bool, bool, string) {
67 | log.Printf("BlockUntilReady() start\n")
68 |
69 | b.q.Push(cmd, id)
70 |
71 | log.Printf("New line length: %v, buffer size increased to:%v\n", len(cmd), b.q.LenOfCmds())
72 | log.Println(b.q)
73 |
74 | if b.q.LenOfCmds() >= b.BufferMax {
75 | b.SetPaused(true, 0)
76 | log.Printf("Buffer Full - Will send this command when space is available")
77 | }
78 |
79 | if b.GetPaused() {
80 | log.Println("It appears we are being asked to pause, so we will wait on b.sem")
81 | // We are being asked to pause our sending of commands
82 |
83 | // clear all b.sem signals so when we block below, we truly block
84 | b.ClearOutSemaphore()
85 |
86 | log.Println("Blocking on b.sem until told from OnIncomingData to go")
87 | unblockType, ok := <-b.sem // will block until told from OnIncomingData to go
88 |
89 | log.Printf("Done blocking cuz got b.sem semaphore release. ok:%v, unblockType:%v\n", ok, unblockType)
90 |
91 | // we get an unblockType of 1 for normal unblocks
92 | // we get an unblockType of 2 when we're being asked to wipe the buffer, i.e. from a % cmd
93 | if unblockType == 2 {
94 | log.Println("This was an unblock of type 2, which means we're being asked to wipe internal buffer. so return false.")
95 | // returning false asks the calling method to wipe the serial send once
96 | // this function returns
97 | return false, false, ""
98 | }
99 |
100 | log.Printf("BlockUntilReady(cmd:%v, id:%v) end\n", cmd, id)
101 | }
102 | return true, true, ""
103 | }
104 |
105 | func (b *BufferflowMarlin) OnIncomingData(data string) {
106 | log.Printf("OnIncomingData() start. data:%q\n", data)
107 |
108 | b.LatestData += data
109 |
110 | //it was found ok was only received with status responses until the MARLIN buffer is full.
111 | //b.LatestData = regexp.MustCompile(">\\r\\nok").ReplaceAllString(b.LatestData, ">") //remove oks from status responses
112 |
113 | arrLines := b.reNewLine.Split(b.LatestData, -1)
114 | log.Printf("arrLines:%v\n", arrLines)
115 |
116 | if len(arrLines) > 1 {
117 | // that means we found a newline and have 2 or greater array values
118 | // so we need to analyze our arrLines[] lines but keep last line
119 | // for next trip into OnIncomingData
120 | log.Printf("We have data lines to analyze. numLines:%v\n", len(arrLines))
121 |
122 | } else {
123 | // we don't have a newline yet, so just exit and move on
124 | // we don't have to reset b.LatestData because we ended up
125 | // without any newlines so maybe we will next time into this method
126 | log.Printf("Did not find newline yet, so nothing to analyze\n")
127 | return
128 | }
129 |
130 | // if we made it here we have lines to analyze
131 | // so analyze all of them except the last line
132 | for index, element := range arrLines[:len(arrLines)-1] {
133 | log.Printf("Working on element:%v, index:%v", element, index)
134 |
135 | //check for 'ok' or 'error' response indicating a gcode line has been processed
136 | if b.ok.MatchString(element) || b.err.MatchString(element) {
137 | if b.q.Len() > 0 {
138 | doneCmd, id := b.q.Poll()
139 |
140 | if b.ok.MatchString(element) {
141 | // Send cmd:"Complete" back
142 | m := DataCmdComplete{"Complete", id, b.Port, b.q.LenOfCmds(), doneCmd}
143 | bm, err := json.Marshal(m)
144 | if err == nil {
145 | h.broadcastSys <- bm
146 | }
147 | } else if b.err.MatchString(element) {
148 | // Send cmd:"Error" back
149 | log.Printf("Error Response Received:%v, id:%v", doneCmd, id)
150 | m := DataCmdComplete{"Error", id, b.Port, b.q.LenOfCmds(), doneCmd}
151 | bm, err := json.Marshal(m)
152 | if err == nil {
153 | h.broadcastSys <- bm
154 | }
155 | }
156 |
157 | log.Printf("Buffer decreased to itemCnt:%v, lenOfBuf:%v\n", b.q.Len(), b.q.LenOfCmds())
158 | } else {
159 | log.Printf("We should NEVER get here cuz we should have a command in the queue to dequeue when we get the r:{} response. If you see this debug stmt this is BAD!!!!")
160 | }
161 |
162 | if b.q.LenOfCmds() < b.BufferMax {
163 |
164 | log.Printf("Marlin just completed a line of gcode\n")
165 |
166 | // if we are paused, tell us to unpause cuz we have clean buffer room now
167 | if b.GetPaused() {
168 | b.SetPaused(false, 1)
169 | }
170 | }
171 |
172 | //check for the marlin init line indicating the arduino is ready to accept commands
173 | //could also pull version from this string, if we find a need for that later
174 | } else if b.initline.MatchString(element) {
175 | //marlin init line received, clear anything from current buffer and unpause
176 | b.LocalBufferWipe(b.parent_serport)
177 |
178 | //unpause buffer but wipe the command in the queue as marlin has restarted.
179 | if b.GetPaused() {
180 | b.SetPaused(false, 2)
181 | }
182 |
183 | b.version = element //save element in version
184 |
185 | //Check for report output, compare to last report output, if different return to client to update status; otherwise ignore status.
186 | } else if b.rpt.MatchString(element) {
187 | //if element == b.LastStatus {
188 | // log.Println("Marlin status has not changed, not reporting to client")
189 | // continue //skip this element as the cnc position has not changed, and move on to the next element.
190 | //}
191 |
192 | b.LastStatus = element //if we make it here something has changed with the status string and laststatus needs updating
193 | }
194 |
195 | // handle communication back to client
196 | m := DataPerLine{b.Port, element + "\n"}
197 | bm, err := json.Marshal(m)
198 | if err == nil {
199 | h.broadcastSys <- bm
200 | }
201 |
202 | } // for loop
203 |
204 | // now wipe the LatestData to only have the last line that we did not analyze
205 | // because we didn't know/think that was a full command yet
206 | b.LatestData = arrLines[len(arrLines)-1]
207 |
208 | //time.Sleep(3000 * time.Millisecond)
209 | log.Printf("OnIncomingData() end.\n")
210 | }
211 |
212 | // Clean out b.sem so it can truly block
213 | func (b *BufferflowMarlin) ClearOutSemaphore() {
214 | ctr := 0
215 |
216 | keepLooping := true
217 | for keepLooping {
218 | select {
219 | case d, ok := <-b.sem:
220 | log.Printf("Consuming b.sem queue to clear it before we block. ok:%v, d:%v\n", ok, string(d))
221 | ctr++
222 | if ok == false {
223 | keepLooping = false
224 | }
225 | default:
226 | keepLooping = false
227 | log.Println("Hit default in select clause")
228 | }
229 | }
230 | log.Printf("Done consuming b.sem queue so we're good to block on it now. ctr:%v\n", ctr)
231 | // ok, all b.sem signals are now consumed into la-la land
232 | }
233 |
234 | func (b *BufferflowMarlin) BreakApartCommands(cmd string) []string {
235 |
236 | // add newline after !~%
237 | log.Printf("Command Before Break-Apart: %q\n", cmd)
238 |
239 | cmds := strings.Split(cmd, "\n")
240 | finalCmds := []string{}
241 | for _, item := range cmds {
242 | //remove comments and whitespace from item
243 | item = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(item, "")
244 | item = regexp.MustCompile(";.*").ReplaceAllString(item, "")
245 | item = strings.Replace(item, " ", "", -1)
246 |
247 | if item == "*init*" { //return init string to update marlin widget when already connected to marlin
248 | m := DataPerLine{b.Port, b.version + "\n"}
249 | bm, err := json.Marshal(m)
250 | if err == nil {
251 | h.broadcastSys <- bm
252 | }
253 | } else if item == "*status*" { //return status when client first connects to existing open port
254 | m := DataPerLine{b.Port, b.LastStatus + "\n"}
255 | bm, err := json.Marshal(m)
256 | if err == nil {
257 | h.broadcastSys <- bm
258 | }
259 | } else if item == "?" {
260 | log.Printf("Query added without newline: %q\n", item)
261 | finalCmds = append(finalCmds, item) //append query request without newline character
262 | } else if item == "%" {
263 | log.Printf("Wiping Marlin BufferFlow")
264 | b.LocalBufferWipe(b.parent_serport)
265 | //dont add this command to the list of finalCmds
266 | } else if item != "" {
267 | log.Printf("Re-adding newline to item:%v\n", item)
268 | s := item + "\n"
269 | finalCmds = append(finalCmds, s)
270 | log.Printf("New cmd item:%v\n", s)
271 | }
272 |
273 | }
274 | log.Printf("Final array of cmds after BreakApartCommands(). finalCmds:%v\n", finalCmds)
275 |
276 | return finalCmds
277 | //return []string{cmd} //do not process string
278 | }
279 |
280 | func (b *BufferflowMarlin) Pause() {
281 | b.SetPaused(true, 0)
282 | //b.BypassMode = false // turn off bypassmode in case it's on
283 | log.Println("Paused buffer on next BlockUntilReady() call")
284 | }
285 |
286 | func (b *BufferflowMarlin) Unpause() {
287 | //unpause buffer by setting paused to false and passing a 1 to b.sem
288 | b.SetPaused(false, 1)
289 | log.Println("Unpaused buffer inside BlockUntilReady() call")
290 | }
291 |
292 | func (b *BufferflowMarlin) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
293 | // remove comments
294 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
295 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
296 | if match, _ := regexp.MatchString("[!~\\?]|(\u0018)", cmd); match {
297 | log.Printf("Found cmd that should skip buffer. cmd:%v\n", cmd)
298 | return true
299 | }
300 | return false
301 | }
302 |
303 | func (b *BufferflowMarlin) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
304 | // remove comments
305 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
306 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
307 | if match, _ := regexp.MatchString("[!]", cmd); match {
308 | log.Printf("Found cmd that should pause buffer. cmd:%v\n", cmd)
309 | return true
310 | }
311 | return false
312 | }
313 |
314 | func (b *BufferflowMarlin) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
315 |
316 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
317 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
318 | if match, _ := regexp.MatchString("[~]", cmd); match {
319 | log.Printf("Found cmd that should unpause buffer. cmd:%v\n", cmd)
320 | return true
321 | }
322 | return false
323 | }
324 |
325 | func (b *BufferflowMarlin) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
326 |
327 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
328 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
329 | if match, _ := regexp.MatchString("(\u0018)", cmd); match {
330 | log.Printf("Found cmd that should wipe out and reset buffer. cmd:%v\n", cmd)
331 |
332 | //b.q.Delete() //delete tracking queue, all buffered commands will be wiped.
333 |
334 | //log.Println("Buffer variables cleared for new input.")
335 | return true
336 | }
337 | return false
338 | }
339 |
340 | func (b *BufferflowMarlin) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
341 | /*
342 | // remove comments
343 | cmd = b.reComment.ReplaceAllString(cmd, "")
344 | cmd = b.reComment2.ReplaceAllString(cmd, "")
345 | if match := b.reNoResponse.MatchString(cmd); match {
346 | log.Printf("Found cmd that does not get a response from TinyG. cmd:%v\n", cmd)
347 | return true
348 | }
349 | */
350 | return false
351 | }
352 |
353 | func (b *BufferflowMarlin) ReleaseLock() {
354 | log.Println("Lock being released in Marlin buffer")
355 |
356 | b.q.Delete()
357 |
358 | log.Println("ReleaseLock(), so we will send signal of 2 to b.sem to unpause the BlockUntilReady() thread")
359 |
360 | //release lock, send signal 2 to b.sem
361 | b.SetPaused(false, 2)
362 | }
363 |
364 | func (b *BufferflowMarlin) IsBufferGloballySendingBackIncomingData() bool {
365 | //telling json server that we are handling client responses
366 | return true
367 | }
368 |
369 | //Use this function to open a connection, write directly to serial port and close connection.
370 | //This is used for sending query requests outside of the normal buffered operations that will pause to wait for room in the marlin buffer
371 | //'?' is asynchronous to the normal buffer load and does not need to be paused when buffer full
372 | func (b *BufferflowMarlin) rptQueryLoop(p *serport) {
373 | b.parent_serport = p //make note of this port for use in clearing the buffer later, on error.
374 | ticker := time.NewTicker(2000 * time.Millisecond)
375 | b.quit = make(chan int)
376 | go func() {
377 | for {
378 | select {
379 | case <-ticker.C:
380 |
381 | n2, err := p.portIo.Write([]byte("M114\n"))
382 |
383 | log.Print("Just wrote ", n2, " bytes to serial: M114")
384 |
385 | if err != nil {
386 | errstr := "Error writing to " + p.portConf.Name + " " + err.Error() + " Closing port."
387 | log.Print(errstr)
388 | h.broadcastSys <- []byte(errstr)
389 | ticker.Stop() //stop query loop if we can't write to the port
390 | break
391 | }
392 | case <-b.quit:
393 | ticker.Stop()
394 | return
395 | }
396 | }
397 | }()
398 | }
399 |
400 | func (b *BufferflowMarlin) Close() {
401 | //stop the status query loop when the serial port is closed off.
402 | log.Println("Stopping the status query loop")
403 | b.quit <- 1
404 | }
405 |
406 | // Gets the paused state of this buffer
407 | // go-routine safe.
408 | func (b *BufferflowMarlin) GetPaused() bool {
409 | b.lock.Lock()
410 | defer b.lock.Unlock()
411 | return b.Paused
412 | }
413 |
414 | // Sets the paused state of this buffer
415 | // go-routine safe.
416 | func (b *BufferflowMarlin) SetPaused(isPaused bool, semRelease int) {
417 | b.lock.Lock()
418 | defer b.lock.Unlock()
419 | b.Paused = isPaused
420 |
421 | //if we are unpausing the buffer, we need to send a signal to release the channel
422 | if isPaused == false {
423 | go func() {
424 | // sending a 2 asks BlockUntilReady() to cancel the send
425 | b.sem <- semRelease
426 | defer func() {
427 | log.Printf("Unpause Semaphore just got consumed by the BlockUntilReady()\n")
428 | }()
429 | }()
430 | }
431 | }
432 |
433 | //local version of buffer wipe loop needed to handle pseudo clear buffer (%) without passing that value on to
434 | func (b *BufferflowMarlin) LocalBufferWipe(p *serport) {
435 | log.Printf("Pseudo command received to wipe marlin buffer but *not* send on to marlin controller.")
436 |
437 | // consume all stuff queued
438 | func() {
439 | ctr := 0
440 |
441 | keepLooping := true
442 | for keepLooping {
443 | select {
444 | case d, ok := <-p.sendBuffered:
445 | log.Printf("Consuming sendBuffered queue. ok:%v, d:%v, id:%v\n", ok, string(d.data), string(d.id))
446 | ctr++
447 |
448 | p.itemsInBuffer--
449 | if ok == false {
450 | keepLooping = false
451 | }
452 | default:
453 | keepLooping = false
454 | log.Println("Hit default in select clause")
455 | }
456 | }
457 | log.Printf("Done consuming sendBuffered cmds. ctr:%v\n", ctr)
458 | }()
459 |
460 | b.ReleaseLock()
461 |
462 | // let user know we wiped queue
463 | log.Printf("itemsInBuffer:%v\n", p.itemsInBuffer)
464 | h.broadcastSys <- []byte("{\"Cmd\":\"WipedQueue\",\"QCnt\":" + strconv.Itoa(p.itemsInBuffer) + ",\"Port\":\"" + p.portConf.Name + "\"}")
465 | }
466 |
467 | func (b *BufferflowMarlin) GetManualPaused() bool {
468 | return false
469 | }
470 |
471 | func (b *BufferflowMarlin) SetManualPaused(isPaused bool) {
472 | }
473 |
--------------------------------------------------------------------------------
/bufferflow_nodemcu.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "regexp"
7 | "strings"
8 | "sync"
9 | "time"
10 | )
11 |
12 | type BufferflowNodeMcu struct {
13 | Name string
14 | Port string
15 | //Output chan []byte
16 | Input chan string
17 | ticker *time.Ticker
18 | IsOpen bool
19 | bufferedOutput string
20 | reNewLine *regexp.Regexp
21 | reCmdDone *regexp.Regexp
22 | // additional lock for BlockUntilReady vs OnIncomingData method
23 | inOutLock *sync.Mutex
24 | q *Queue
25 | sem chan int // semaphore to wait on until given release
26 | Paused bool
27 | ManualPaused bool
28 | lock *sync.Mutex
29 | manualLock *sync.Mutex
30 | BufferMax int
31 | }
32 |
33 | func (b *BufferflowNodeMcu) Init() {
34 | log.Println("Initting timed buffer flow (output once every 16ms)")
35 | b.bufferedOutput = ""
36 | b.IsOpen = true
37 | b.reNewLine, _ = regexp.Compile("\\r{0,1}\\n")
38 | b.inOutLock = &sync.Mutex{}
39 |
40 | b.q = NewQueue()
41 | // when we get a > response we know a line was processed
42 | b.reCmdDone, _ = regexp.Compile("^(>|stdin:|=)")
43 | b.sem = make(chan int, 1000)
44 | b.Paused = false
45 | b.ManualPaused = false
46 | b.lock = &sync.Mutex{}
47 | b.manualLock = &sync.Mutex{}
48 | b.Input = make(chan string)
49 | b.BufferMax = 2
50 |
51 | go func() {
52 | for data := range b.Input {
53 |
54 | //log.Printf("Got to b.Input chan loop. data:%v\n", data)
55 |
56 | // Lock the packet ctr at start and then end
57 | b.inOutLock.Lock()
58 |
59 | b.bufferedOutput = b.bufferedOutput + data
60 | arrLines := b.reNewLine.Split(b.bufferedOutput, -1)
61 | if len(arrLines) > 1 {
62 | // that means we found a newline and have 2 or greater array values
63 | // so we need to analyze our arrLines[] lines but keep last line
64 | // for next trip into OnIncomingData
65 | //log.Printf("We have data lines to analyze. numLines:%v\n", len(arrLines))
66 |
67 | } else {
68 | // we don't have a newline yet, so just exit and move on
69 | // we don't have to reset b.LatestData because we ended up
70 | // without any newlines so maybe we will next time into this method
71 | //log.Printf("Did not find newline yet, so nothing to analyze\n")
72 | b.inOutLock.Unlock()
73 | continue
74 | }
75 |
76 | log.Printf("Analyzing incoming data. Start.")
77 |
78 | // if we made it here we have lines to analyze
79 | // so analyze all of them except the last line
80 | for _, element := range arrLines[:len(arrLines)-1] {
81 | //log.Printf("Working on element:%v, index:%v", element, index)
82 | //log.Printf("Working on element:%v, index:%v", element)
83 | log.Printf("\t\tData:%v", element)
84 |
85 | // check if there was a reset cuz we need to wipe our buffer if there was
86 | if len(element) > 4 {
87 | bTxt := []byte(element)[len(element)-4:]
88 | bTest := []byte{14, 219, 200, 244}
89 | //log.Printf("\t\ttesting two arrays\n\tbTxt :%v\n\tbTest:%v\n", bTxt, bTest)
90 | //reWasItReset := regexp.MustCompile("fffd")
91 | //if reWasItReset.MatchString(element) {
92 | if ByteArrayEquals(bTxt, bTest) {
93 | // it was reset, wipe buffer
94 | b.q.Delete()
95 | log.Printf("\t\tLooks like it was reset based on 1st 4 bytes. We should wipe buffer.")
96 | b.SetPaused(false, 2)
97 | }
98 | }
99 |
100 | // see if it just got restarted
101 | reIsRestart := regexp.MustCompile("(NodeMCU custom build by frightanic.com|NodeMCU .+ build .+ powered by Lua)")
102 | if reIsRestart.MatchString(element) {
103 | // it was reset, wipe buffer
104 | b.q.Delete()
105 | log.Printf("\t\tLooks like it was reset based on NodeMCU build line. We should wipe buffer.")
106 | b.SetPaused(false, 2)
107 | }
108 |
109 | // Peek to see if the message back matches the command we just sent in
110 | lastCmd, _ := b.q.Peek()
111 | lastCmd = regexp.MustCompile("\n").ReplaceAllString(lastCmd, "")
112 |
113 | cmdProcessed := false
114 | log.Printf("\t\tSeeing if peek compare to lastCmd makes sense. lastCmd:\"%v\", element:\"%v\"", lastCmd, element)
115 | if lastCmd == element {
116 | // we just got back the last command so that is a good indicator we got processed
117 | log.Printf("\t\tWe got back the same command that was just sent in. That is a sign we are processed.")
118 | cmdProcessed = true
119 | }
120 |
121 | //check for >|stdin:|= response indicating a line has been processed
122 | if cmdProcessed || b.reCmdDone.MatchString(element) {
123 |
124 | // ok, a line has been processed, the if statement below better
125 | // be guaranteed to be true, cuz if its not we did something wrong
126 | if b.q.Len() > 0 {
127 | //b.BufferSize -= b.BufferSizeArray[0]
128 | doneCmd, id := b.q.Poll()
129 |
130 | // Send cmd:"Complete" back
131 | m := DataCmdComplete{"Complete", id, b.Port, b.q.Len(), doneCmd}
132 | bm, err := json.Marshal(m)
133 | if err == nil {
134 | h.broadcastSys <- bm
135 | }
136 |
137 | log.Printf("\tBuffer decreased to b.q.Len:%v\n", b.q.Len())
138 | } else {
139 | log.Printf("\tWe should RARELY get here cuz we should have a command in the queue to dequeue when we get the >|stdin:|= response. If you see this debug stmt this is one of those few instances where NodeMCU sent us a >|stdin:|= not in response to a command we sent.")
140 | }
141 |
142 | if b.q.Len() < b.BufferMax {
143 |
144 | // if we are paused, tell us to unpause cuz we have clean buffer room now
145 | if b.GetPaused() {
146 |
147 | // we are paused, but we can't just go unpause ourself, because we may
148 | // be manually paused. this means we have to do a double-check here
149 | if b.GetManualPaused() == false {
150 |
151 | // we are not in a manual pause state, that means we can go ahead
152 | // and unpause ourselves
153 | b.SetPaused(false, 1) //set paused to false first, then release the hold on the buffer
154 | } else {
155 | log.Println("\tWe just got incoming >|stdin:|= so we could unpause, but since manual paused we will ignore until next time a >|stdin:|= comes in to unpause")
156 | }
157 | }
158 |
159 | }
160 |
161 | }
162 |
163 | // handle communication back to client
164 | // for base serial data (this is not the cmd:"Write" or cmd:"Complete")
165 | m := DataPerLine{b.Port, element + "\n"}
166 | bm, err := json.Marshal(m)
167 | if err == nil {
168 | h.broadcastSys <- bm
169 | }
170 |
171 | } // for loop
172 |
173 | b.bufferedOutput = arrLines[len(arrLines)-1]
174 |
175 | b.inOutLock.Unlock()
176 | log.Printf("Done with analyzing incoming data.")
177 |
178 | }
179 | }()
180 |
181 | /*
182 | go func() {
183 | b.ticker = time.NewTicker(16 * time.Millisecond)
184 | for _ = range b.ticker.C {
185 | if b.bufferedOutput != "" {
186 | m := SpPortMessage{b.Port, b.bufferedOutput}
187 | buf, _ := json.Marshal(m)
188 | b.Output <- []byte(buf)
189 | //log.Println(buf)
190 | b.bufferedOutput = ""
191 | }
192 | }
193 | }()
194 | */
195 |
196 | }
197 |
198 | func IntArrayEquals(a []int, b []int) bool {
199 | if len(a) != len(b) {
200 | return false
201 | }
202 | for i, v := range a {
203 | if v != b[i] {
204 | return false
205 | }
206 | }
207 | return true
208 | }
209 |
210 | func ByteArrayEquals(a []byte, b []byte) bool {
211 | if len(a) != len(b) {
212 | return false
213 | }
214 | for i, v := range a {
215 | if v != b[i] {
216 | return false
217 | }
218 | }
219 | return true
220 | }
221 |
222 | func (b *BufferflowNodeMcu) BlockUntilReady(cmd string, id string) (bool, bool, string) {
223 |
224 | // Lock for this ENTIRE method
225 | b.inOutLock.Lock()
226 |
227 | log.Printf("BlockUntilReady() Start\n")
228 | log.Printf("\tid:%v, txt:%v\n", id, strings.Replace(cmd, "\n", "\\n", -1))
229 |
230 | // keep track of whether we need to unlock at end of method or not
231 | // i.e. we unlock if we have to pause, thus we won't have to doubly unlock at end of method
232 | isNeedToUnlock := true
233 |
234 | b.q.Push(cmd, id)
235 |
236 | if b.q.Len() >= b.BufferMax {
237 | b.SetPaused(true, 0) // b.Paused = true
238 | log.Printf("\tIt looks like the local queue at Len: %v is over the allowed size of BufferMax: %v, so we are going to pause. Then when some incoming responses come in a check will occur to see if there's room to send this command. Pausing...", b.q.Len(), b.BufferMax)
239 | }
240 |
241 | if b.GetPaused() {
242 | //log.Println("It appears we are being asked to pause, so we will wait on b.sem")
243 | // We are being asked to pause our sending of commands
244 |
245 | // clear all b.sem signals so when we block below, we truly block
246 | b.ClearOutSemaphore()
247 |
248 | // since we need other code to run while we're blocking, we better release the packet ctr lock
249 | b.inOutLock.Unlock()
250 | // since we already unlocked this thread, note it so we don't doubly unlock
251 | isNeedToUnlock = false
252 |
253 | log.Println("\tBlocking on b.sem until told from OnIncomingData to go")
254 | unblockType, ok := <-b.sem // will block until told from OnIncomingData to go
255 |
256 | log.Printf("\tDone blocking cuz got b.sem semaphore release. ok:%v, unblockType:%v\n", ok, unblockType)
257 |
258 | log.Printf("\tDone blocking cuz got b.sem semaphore release. ok:%v, unblockType:%v\n", ok, unblockType)
259 |
260 | // we get an unblockType of 1 for normal unblocks
261 | // we get an unblockType of 2 when we're being asked to wipe the buffer, i.e. from a % cmd
262 | if unblockType == 2 {
263 | log.Println("\tThis was an unblock of type 2, which means we're being asked to wipe internal buffer. so return false.")
264 | // returning false asks the calling method to wipe the serial send once
265 | // this function returns
266 | return false, false, ""
267 | }
268 | }
269 |
270 | log.Printf("BlockUntilReady() end\n")
271 |
272 | time.Sleep(10 * time.Millisecond)
273 |
274 | if isNeedToUnlock {
275 | b.inOutLock.Unlock()
276 | }
277 |
278 | //return true, willHandleCompleteResponse, newCmd
279 |
280 | return true, true, ""
281 | }
282 |
283 | func (b *BufferflowNodeMcu) OnIncomingData(data string) {
284 | b.Input <- data
285 | }
286 |
287 | // Clean out b.sem so it can truly block
288 | func (b *BufferflowNodeMcu) ClearOutSemaphore() {
289 | keepLooping := true
290 | for keepLooping {
291 | select {
292 | case _, ok := <-b.sem: // case d, ok :=
293 | //log.Printf("Consuming b.sem queue to clear it before we block. ok:%v, d:%v\n", ok, string(d))
294 | //ctr++
295 | if ok == false {
296 | keepLooping = false
297 | }
298 | default:
299 | keepLooping = false
300 | //log.Println("Hit default in select clause")
301 | }
302 | }
303 | }
304 |
305 | func (b *BufferflowNodeMcu) BreakApartCommands(cmd string) []string {
306 | return []string{cmd}
307 | }
308 |
309 | func (b *BufferflowNodeMcu) Pause() {
310 | return
311 | }
312 |
313 | func (b *BufferflowNodeMcu) Unpause() {
314 | return
315 | }
316 |
317 | func (b *BufferflowNodeMcu) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
318 | reRestart := regexp.MustCompile("node.restart\\(\\)")
319 | if reRestart.MatchString(cmd) {
320 | return true
321 | } else {
322 | return false
323 | }
324 | }
325 |
326 | func (b *BufferflowNodeMcu) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
327 | return false
328 | }
329 |
330 | func (b *BufferflowNodeMcu) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
331 | return false
332 | }
333 |
334 | func (b *BufferflowNodeMcu) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
335 | reRestart := regexp.MustCompile("^\\s*node.restart\\(\\)")
336 | if reRestart.MatchString(cmd) {
337 | log.Printf("\t\tWe found a node.restart() and thus we will wipe buffer")
338 | b.ReleaseLock()
339 | return true
340 | } else {
341 | return false
342 | }
343 | }
344 |
345 | func (b *BufferflowNodeMcu) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
346 |
347 | reWhiteSpace := regexp.MustCompile("^\\s*$")
348 | if reWhiteSpace.MatchString(cmd) {
349 | log.Println("Found a whitespace only command")
350 | return true
351 | } else {
352 | return false
353 | }
354 |
355 | //return false
356 | }
357 |
358 | func (b *BufferflowNodeMcu) ReleaseLock() {
359 | log.Println("Wiping NodeMCU buffer")
360 |
361 | b.q.Delete()
362 | b.SetPaused(false, 2)
363 | }
364 |
365 | func (b *BufferflowNodeMcu) IsBufferGloballySendingBackIncomingData() bool {
366 | return true
367 | }
368 |
369 | func (b *BufferflowNodeMcu) Close() {
370 | if b.IsOpen == false {
371 | // we are being asked a 2nd time to close when we already have
372 | // that will cause a panic
373 | log.Println("We got called a 2nd time to close, but already closed")
374 | return
375 | }
376 | b.IsOpen = false
377 |
378 | //b.ticker.Stop()
379 | close(b.Input)
380 | }
381 |
382 | func (b *BufferflowNodeMcu) RewriteSerialData(cmd string, id string) string {
383 | return ""
384 | }
385 |
386 | // Gets the paused state of this buffer
387 | // go-routine safe.
388 | func (b *BufferflowNodeMcu) GetPaused() bool {
389 | b.lock.Lock()
390 | defer b.lock.Unlock()
391 | return b.Paused
392 | }
393 |
394 | // Sets the paused state of this buffer
395 | // go-routine safe.
396 | func (b *BufferflowNodeMcu) SetPaused(isPaused bool, semRelease int) {
397 | b.lock.Lock()
398 | defer b.lock.Unlock()
399 | b.Paused = isPaused
400 |
401 | // only release semaphore if we are being told to unpause
402 | if b.Paused == false {
403 | // the BlockUntilReady thread should be sitting waiting
404 | // so when we send this should trigger it
405 | b.sem <- semRelease
406 | log.Printf("\tJust sent release to b.sem with val:%v, so we will not block the sending to serial port anymore.", semRelease)
407 |
408 | }
409 | }
410 |
411 | func (b *BufferflowNodeMcu) GetManualPaused() bool {
412 | b.manualLock.Lock()
413 | defer b.manualLock.Unlock()
414 | return b.ManualPaused
415 | }
416 |
417 | func (b *BufferflowNodeMcu) SetManualPaused(isPaused bool) {
418 | b.manualLock.Lock()
419 | defer b.manualLock.Unlock()
420 | b.ManualPaused = isPaused
421 | }
422 |
--------------------------------------------------------------------------------
/bufferflow_timed.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "time"
7 | )
8 |
9 | type BufferflowTimed struct {
10 | Name string
11 | Port string
12 | Output chan []byte
13 | Input chan string
14 | ticker *time.Ticker
15 | IsOpen bool
16 | bufferedOutput string
17 | }
18 |
19 | /*
20 | var (
21 | bufferedOutput string
22 | )
23 | */
24 |
25 | func (b *BufferflowTimed) Init() {
26 | log.Println("Initting timed buffer flow (output once every 16ms)")
27 | b.bufferedOutput = ""
28 | b.IsOpen = true
29 |
30 | go func() {
31 | for data := range b.Input {
32 | b.bufferedOutput = b.bufferedOutput + data
33 |
34 | }
35 | }()
36 |
37 | go func() {
38 | b.ticker = time.NewTicker(16 * time.Millisecond)
39 | for _ = range b.ticker.C {
40 | if b.bufferedOutput != "" {
41 | m := SpPortMessage{b.Port, b.bufferedOutput}
42 | buf, _ := json.Marshal(m)
43 | b.Output <- []byte(buf)
44 | //log.Println(buf)
45 | b.bufferedOutput = ""
46 | }
47 | }
48 | }()
49 |
50 | }
51 |
52 | func (b *BufferflowTimed) BlockUntilReady(cmd string, id string) (bool, bool, string) {
53 | //log.Printf("BlockUntilReady() start\n")
54 | return true, false, ""
55 | }
56 |
57 | func (b *BufferflowTimed) OnIncomingData(data string) {
58 | b.Input <- data
59 | }
60 |
61 | // Clean out b.sem so it can truly block
62 | func (b *BufferflowTimed) ClearOutSemaphore() {
63 | }
64 |
65 | func (b *BufferflowTimed) BreakApartCommands(cmd string) []string {
66 | return []string{cmd}
67 | }
68 |
69 | func (b *BufferflowTimed) Pause() {
70 | return
71 | }
72 |
73 | func (b *BufferflowTimed) Unpause() {
74 | return
75 | }
76 |
77 | func (b *BufferflowTimed) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
78 | return false
79 | }
80 |
81 | func (b *BufferflowTimed) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
82 | return false
83 | }
84 |
85 | func (b *BufferflowTimed) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
86 | return false
87 | }
88 |
89 | func (b *BufferflowTimed) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
90 | return false
91 | }
92 |
93 | func (b *BufferflowTimed) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
94 | return false
95 | }
96 |
97 | func (b *BufferflowTimed) ReleaseLock() {
98 | }
99 |
100 | func (b *BufferflowTimed) IsBufferGloballySendingBackIncomingData() bool {
101 | return true
102 | }
103 |
104 | func (b *BufferflowTimed) Close() {
105 | if b.IsOpen == false {
106 | // we are being asked a 2nd time to close when we already have
107 | // that will cause a panic
108 | log.Println("We got called a 2nd time to close, but already closed")
109 | return
110 | }
111 | b.IsOpen = false
112 |
113 | b.ticker.Stop()
114 | close(b.Input)
115 | }
116 |
117 | func (b *BufferflowTimed) GetManualPaused() bool {
118 | return false
119 | }
120 |
121 | func (b *BufferflowTimed) RewriteSerialData(cmd string, id string) string {
122 | return ""
123 | }
124 |
125 | func (b *BufferflowTimed) SetManualPaused(isPaused bool) {
126 | }
127 |
--------------------------------------------------------------------------------
/cayenn.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "log"
7 | "net"
8 | "regexp"
9 | "strconv"
10 | "strings"
11 | )
12 |
13 | type Addr struct {
14 | IP string
15 | Port int
16 | Network string
17 | TcpOrUdp string
18 | }
19 |
20 | type DataAnnounce struct {
21 | Addr Addr
22 | Announce string
23 | Widget string
24 | JsonTag string
25 | DeviceId string
26 | }
27 |
28 | type ClientAnnounceMsg struct {
29 | Announce string
30 | Widget string
31 | MyDeviceId string
32 | JsonTag string
33 | }
34 |
35 | // This is the UDP packet sent back from the server (us)
36 | // to the client saying "hey we got your announce, this is
37 | // who we are and our IP in case you want to create a TCP
38 | // socket conn back to us for reliable conn"
39 | type ServerAnnounceResponseMsg struct {
40 | Announce string
41 | Widget string
42 | YourDeviceId string
43 | ServerIp string
44 | JsonTag string
45 | }
46 |
47 | func udpServerRun() {
48 |
49 | /* Lets prepare a address at any address at port 8988*/
50 | ServerAddr, err := net.ResolveUDPAddr("udp", ":8988")
51 | if err != nil {
52 | log.Println("Error: ", err)
53 | return
54 | }
55 |
56 | /* Now listen at selected port */
57 | ServerConn, err := net.ListenUDP("udp", ServerAddr)
58 | if err != nil {
59 | log.Println("Error creating Cayenn UDP server. Consider using -disablecayenn command line switch to turn off the Cayenn TCP/UDP server. Error: ", err)
60 | return
61 | }
62 | defer ServerConn.Close()
63 |
64 | log.Println("Cayenn UDP server running on port 8988 to listen for incoming device announcements and unguaranteed data.")
65 | buf := make([]byte, 1024*10)
66 |
67 | for {
68 | n, addr, err := ServerConn.ReadFromUDP(buf)
69 |
70 | if err != nil {
71 | log.Println("Error: ", err)
72 | } else {
73 | log.Println("Received ", string(buf[0:n]), " from ", addr)
74 |
75 | m := DataAnnounce{}
76 | m.Addr.IP = addr.IP.String()
77 | m.Addr.Network = addr.Network()
78 | m.Addr.Port = addr.Port
79 | m.Addr.TcpOrUdp = "udp"
80 |
81 | // if the udp message was from us, i.e. we sent out a broadcast so
82 | // we got a copy back, just ignore
83 | MyIp := ServerConn.LocalAddr().String()
84 | // drop port, cuz we don't care about it. we have known ports
85 | re := regexp.MustCompile(":\\d+$")
86 | MyIp = re.ReplaceAllString(MyIp, "")
87 | externIP, _ := externalIP()
88 | // print("Checking if from me ", addr.IP.String(), "<>", externIP)
89 | if addr.IP.String() == externIP {
90 | log.Println("Got msg back from ourself, so dropping.")
91 | continue
92 | }
93 |
94 | var am ClientAnnounceMsg
95 | err := json.Unmarshal([]byte(buf[0:n]), &am)
96 | if err != nil {
97 | log.Println("Err unmarshalling UDP inbound message from device. err:", err)
98 | continue
99 | }
100 | m.Announce = am.Announce
101 | m.Widget = am.Widget
102 | m.JsonTag = am.JsonTag
103 | m.DeviceId = am.MyDeviceId
104 |
105 | // send message to websocket clients, i.e. ChiliPeppr
106 | bm, err := json.Marshal(m)
107 | if err == nil {
108 | h.broadcastSys <- bm
109 | }
110 |
111 | // send back our own AnnounceRecv
112 | // but only if the incoming message was an "Announce":"i-am-a-client"
113 | //re2 := regexp.MustCompile('"Announce":"i-am-a-client"')
114 | //if re2.MatchString()
115 | if am.Announce == "i-am-a-client" {
116 |
117 | var arm ServerAnnounceResponseMsg
118 | arm.Announce = "i-am-your-server"
119 | arm.YourDeviceId = am.MyDeviceId
120 | arm.ServerIp = ServerConn.LocalAddr().String()
121 | arm.Widget = am.Widget
122 | //arm.JsonTag = am.JsonTag
123 |
124 | // we send back reverse acknowledgement on both UDP and TCP
125 | // but long term we should likely just send a TCP response back
126 | // because Cayenn devices will likely long term need to store what
127 | // server they are interacting with, however, the debate is i have
128 | // Cayenn devices mostly just sending back broadcast messages to entire
129 | // network and it's working well
130 | // sendUdp(arm, m.Addr.IP, ":8988")
131 | go sendTcp(arm, m.Addr.IP, ":8988")
132 |
133 | // cayennSendTcpMsg(m.Addr.IP, ":8988", bmsg)
134 | // go makeTcpConnBackToDevice(m.Addr.IP)
135 | } else {
136 | log.Println("The incoming msg was not an i-am-client announce so not sending back response")
137 | }
138 | }
139 | }
140 | }
141 |
142 | // Called from hub.go as entry point
143 | func cayennSendUdp(s string) {
144 | // we get here if a client sent into spjs the command
145 | // cayenn-sendudp 192.168.1.12 any-msg-to-end-of-line
146 | args := strings.SplitN(s, " ", 3)
147 |
148 | // make sure we got 3 args
149 | if len(args) < 3 {
150 | spErr("Error parsing cayenn-sendudp. Returning. msg:" + s)
151 | return
152 | }
153 |
154 | ip := args[1]
155 | if len(ip) < 7 {
156 | spErr("Error parsing IP address for cayenn-sendudp. Returning. msg:" + s)
157 | return
158 | }
159 | msg := args[2]
160 | log.Println("cayenn-sendudp ip:", ip, "msg:", msg)
161 | cayennSendUdpMsg(ip, ":8988", msg)
162 | }
163 |
164 | func cayennSendUdpMsg(ipaddr string, port string, msg string) {
165 |
166 | // This method sends a message to a specific IP address / port over UDP
167 | var service = ipaddr + port
168 |
169 | conn, err := net.Dial("udp", service)
170 |
171 | if err != nil {
172 | log.Println("Could not resolve udp address or connect to it on ", service)
173 | log.Println(err)
174 | return
175 | }
176 | defer conn.Close()
177 |
178 | log.Println("Connected to udp server at ", service)
179 |
180 | n, err := conn.Write([]byte(msg))
181 | if err != nil {
182 | log.Println("error writing data to server", service)
183 | log.Println(err)
184 | return
185 | }
186 |
187 | if n > 0 {
188 | log.Println("Wrote ", n, " bytes to server at ", service)
189 | } else {
190 | log.Println("Wrote 0 bytes to server. Huh?")
191 | }
192 | }
193 |
194 | // This method is similar to cayennSendUdp but it takes in a struct and json
195 | // serializes it
196 | func sendUdp(sarm ServerAnnounceResponseMsg, ipaddr string, port string) {
197 |
198 | var service = ipaddr + port
199 |
200 | conn, err := net.Dial("udp", service)
201 |
202 | if err != nil {
203 | log.Println("Could not resolve udp address or connect to it on ", service)
204 | log.Println(err)
205 | return
206 | }
207 | defer conn.Close()
208 |
209 | log.Println("Connected to udp server at ", service)
210 |
211 | // add our server ip to packet because esp8266 and Lua make it near impossible
212 | // to determine the ip the udp packet came from, so we'll include it in the payload
213 | sarm.ServerIp = conn.LocalAddr().String()
214 | // drop port, cuz we don't care about it. we have known ports
215 | re := regexp.MustCompile(":\\d+$")
216 | sarm.ServerIp = re.ReplaceAllString(sarm.ServerIp, "")
217 |
218 | bmsg, err := json.Marshal(sarm)
219 | if err != nil {
220 | log.Println("Error marshalling json for sarm:", sarm, "err:", err)
221 | return
222 | }
223 |
224 | n, err := conn.Write([]byte(bmsg))
225 | if err != nil {
226 | log.Println("error writing data to server", service)
227 | log.Println(err)
228 | return
229 | }
230 |
231 | if n > 0 {
232 | log.Println("Wrote ", n, " bytes to server at ", service)
233 | } else {
234 | log.Println("Wrote 0 bytes to server. Huh?")
235 | }
236 |
237 | }
238 |
239 | func makeTcpConnBackToDevice(ipaddr string) {
240 |
241 | var ip = ipaddr + ":8988"
242 | conn, err := net.Dial("tcp", ip)
243 | log.Println("Making TCP connection to:", ip)
244 |
245 | if err != nil {
246 | log.Println("Error trying to make TCP conn. err:", err)
247 | return
248 | }
249 | defer func() {
250 | log.Println("Closing TCP conn to:", ip)
251 | conn.Close()
252 | }()
253 |
254 | n, err := conn.Write([]byte("hello"))
255 | if err != nil {
256 | log.Println("Write to server failed:", err.Error())
257 | return
258 | }
259 |
260 | log.Println("Wrote n:", n, "bytes to server")
261 |
262 | connbuf := bufio.NewReader(conn)
263 | for {
264 | str, err := connbuf.ReadString('\n')
265 | if len(str) > 0 {
266 | log.Println("Got msg on TCP client from ip:", ip)
267 | log.Println(str)
268 | h.broadcastSys <- []byte(str)
269 | }
270 |
271 | if err != nil {
272 | break
273 | }
274 | }
275 | }
276 |
277 | // Called from hub.go as entry point
278 | func cayennSendTcp(s string) {
279 | // we get here if a client sent into spjs the command
280 | // cayenn-sendtcp 192.168.1.12 any-msg-to-end-of-line
281 | args := strings.SplitN(s, " ", 3)
282 |
283 | // make sure we got 3 args
284 | if len(args) < 3 {
285 | spErr("Error parsing cayenn-sendtcp. Returning. msg:" + s)
286 | return
287 | }
288 |
289 | ip := args[1]
290 | if len(ip) < 7 {
291 | spErr("Error parsing IP address for cayenn-sendtcp. Returning. msg:" + s)
292 | return
293 | }
294 | msg := args[2]
295 | log.Println("cayenn-sendtcp ip:", ip, "msg:", msg)
296 | cayennSendTcpMsg(ip, ":8988", msg)
297 | }
298 |
299 | // For now just connect, send, and then disconnect. This keeps stuff simple
300 | // but it does create overhead. However, it is similar to RESTful web calls
301 | func cayennSendTcpMsg(ipaddr string, port string, msg string) {
302 |
303 | // This method sends a message to a specific IP address / port over TCP
304 | var service = ipaddr + port
305 |
306 | conn, err := net.Dial("tcp", service)
307 | log.Println("Making TCP connection to:", service)
308 |
309 | if err != nil {
310 | log.Println("Error trying to make TCP conn. err:", err)
311 | return
312 | }
313 | defer func() {
314 | log.Println("Closing TCP conn to:", service)
315 | conn.Close()
316 | }()
317 |
318 | n, err := conn.Write([]byte(msg))
319 | if err != nil {
320 | log.Println("Write to server failed:", err.Error())
321 | return
322 | }
323 |
324 | log.Println("Wrote n:", n, "bytes to server")
325 |
326 | // close connection immediately
327 | conn.Close()
328 |
329 | // connbuf := bufio.NewReader(conn)
330 | // for {
331 | // str, err := connbuf.ReadString('\n')
332 | // if len(str) > 0 {
333 | // log.Println("Got msg on TCP client from ip:", service)
334 | // log.Println(str)
335 | // h.broadcastSys <- []byte(str)
336 | // }
337 |
338 | // if err != nil {
339 | // break
340 | // }
341 | // }
342 |
343 | }
344 |
345 | // This method is similar to cayennSendTcp but it takes in a struct and json
346 | // serializes it
347 | func sendTcp(sarm ServerAnnounceResponseMsg, ipaddr string, port string) {
348 |
349 | // This method sends a message to a specific IP address / port over TCP
350 | var service = ipaddr + port
351 |
352 | conn, err := net.Dial("tcp", service)
353 | log.Println("Making TCP connection to:", service)
354 |
355 | if err != nil {
356 | log.Println("Error trying to make TCP conn. err:", err)
357 | return
358 | }
359 | defer func() {
360 | log.Println("Closing TCP conn to:", service)
361 | conn.Close()
362 | }()
363 |
364 | // add our server ip to packet because esp8266 and Lua make it near impossible
365 | // to determine the ip the udp packet came from, so we'll include it in the payload
366 | sarm.ServerIp = conn.LocalAddr().String()
367 | // drop port, cuz we don't care about it. we have known ports
368 | re := regexp.MustCompile(":\\d+$")
369 | sarm.ServerIp = re.ReplaceAllString(sarm.ServerIp, "")
370 |
371 | bmsg, err := json.Marshal(sarm)
372 | if err != nil {
373 | log.Println("Error marshalling json for sarm:", sarm, "err:", err)
374 | return
375 | }
376 |
377 | n, err := conn.Write(bmsg)
378 | if err != nil {
379 | log.Println("Write to server failed:", err.Error())
380 | return
381 | }
382 |
383 | log.Println("Wrote n:", n, "bytes to server")
384 |
385 | // close connection immediately
386 | conn.Close()
387 | }
388 |
389 | func tcpServerRun() {
390 |
391 | ServerAddr, err := net.ResolveTCPAddr("tcp", ":8988")
392 | if err != nil {
393 | log.Println("Error: ", err)
394 | return
395 | }
396 |
397 | // Listen for incoming connections on all/any IP addresses
398 | // on port 8988
399 | l, err := net.ListenTCP("tcp", ServerAddr)
400 | if err != nil {
401 | log.Println("Error creating Cayenn TCP server. Consider using -disablecayenn command line switch to turn off the Cayenn TCP/UDP server. Error: ", err.Error())
402 | return
403 | }
404 |
405 | // Close the listener when the application closes.
406 | defer l.Close()
407 |
408 | log.Println("Cayenn TCP server running on port 8988 to listen for incoming guaranteed device messages.")
409 |
410 | for {
411 | // Listen for an incoming connection.
412 | ServerConn, err := l.Accept()
413 | if err != nil {
414 | log.Println("Error accepting: ", err.Error())
415 | }
416 | // Handle connections in a new goroutine.
417 | go handleTcpRequest(ServerConn)
418 | }
419 | }
420 |
421 | // Handles incoming requests.
422 | func handleTcpRequest(conn net.Conn) {
423 |
424 | // Make a buffer to hold incoming data.
425 | buf := make([]byte, 1024*10)
426 |
427 | // Read the incoming connection into the buffer.
428 | reqLen, err := conn.Read(buf)
429 | if err != nil {
430 | log.Println("Error reading incoming TCP data:", err.Error(), reqLen)
431 | return
432 | }
433 |
434 | // Send a response back to person contacting us.
435 | // conn.Write([]byte("Message received."))
436 |
437 | addr := conn.RemoteAddr()
438 | log.Println("TCP Received ", string(buf[0:reqLen]), " from ", addr)
439 |
440 | // drop port from ip string, cuz we don't care about it. we have known ports
441 | // i.e. the ip is 10.0.0.44:23423 and we want to drop :23423
442 | re := regexp.MustCompile(":\\d+$")
443 | RemoteIp := conn.RemoteAddr().String()
444 | RemotePort := re.FindString(RemoteIp)
445 | RemotePort = regexp.MustCompile("\\d+$").FindString(RemotePort)
446 | RemoteIp = re.ReplaceAllString(RemoteIp, "")
447 |
448 | m := DataAnnounce{}
449 | m.Addr.IP = RemoteIp
450 | m.Addr.Network = addr.Network()
451 | // m.Addr.Port = addr.Port
452 | portInt, errConvert := strconv.Atoi(RemotePort)
453 | if errConvert != nil {
454 | log.Println("Err converting remote port str to int: ", RemotePort)
455 | } else {
456 | m.Addr.Port = portInt
457 | }
458 | m.Addr.TcpOrUdp = "tcp"
459 |
460 | // if the tcp message was from us, i.e. we sent out a broadcast so
461 | // we got a copy back, just ignore
462 | MyIp := conn.LocalAddr().String()
463 | MyIp = re.ReplaceAllString(MyIp, "")
464 | externIP, _ := externalIP()
465 | print("Checking if from me ", RemoteIp, "<>", externIP)
466 | if RemoteIp == externIP {
467 | log.Println("Got msg back from ourself, so dropping.")
468 | // continue
469 | return
470 | }
471 |
472 | var am ClientAnnounceMsg
473 | err2 := json.Unmarshal(buf[0:reqLen], &am)
474 | if err2 != nil {
475 | log.Println("Err unmarshalling TCP inbound message from device. err:", err)
476 | // continue
477 | return
478 | }
479 | m.Announce = am.Announce
480 | m.Widget = am.Widget
481 | m.JsonTag = am.JsonTag
482 | m.DeviceId = am.MyDeviceId
483 |
484 | // send message to websocket clients, i.e. ChiliPeppr
485 | bm, err := json.Marshal(m)
486 | if err == nil {
487 | h.broadcastSys <- bm
488 | log.Println("Sending to websocket back to ChiliPeppr: ", string(bm))
489 | }
490 |
491 | // send back our own AnnounceRecv
492 | // but only if the incoming message was an "Announce":"i-am-a-client"
493 | //re2 := regexp.MustCompile('"Announce":"i-am-a-client"')
494 | //if re2.MatchString()
495 | if am.Announce == "i-am-a-client" {
496 |
497 | var arm ServerAnnounceResponseMsg
498 | arm.Announce = "i-am-your-server"
499 | arm.YourDeviceId = am.MyDeviceId
500 | arm.ServerIp = conn.LocalAddr().String()
501 | arm.Widget = am.Widget
502 | //arm.JsonTag = am.JsonTag
503 |
504 | // we send back reverse acknowledgement on both UDP and TCP
505 | // but long term we should likely just send a TCP response back
506 | // because Cayenn devices will likely long term need to store what
507 | // server they are interacting with, however, the debate is i have
508 | // Cayenn devices mostly just sending back broadcast messages to entire
509 | // network and it's working well
510 | // sendTcp(arm, m.Addr.IP, ":8988")
511 | go sendTcp(arm, m.Addr.IP, ":8988")
512 |
513 | // cayennSendTcpMsg(m.Addr.IP, ":8988", bmsg)
514 | // go makeTcpConnBackToDevice(m.Addr.IP)
515 | } else {
516 | log.Println("The incoming msg was not an i-am-client announce so not sending back response")
517 | }
518 |
519 | // ORIGINAL CODE
520 | /*
521 | m := DataAnnounce{}
522 | m.Addr.IP = addr.String()
523 | m.Addr.Network = addr.Network()
524 | // m.Addr.Port = addr.Port
525 | m.Addr.TcpOrUdp = "tcp"
526 |
527 | var am ClientAnnounceMsg
528 | err2 := json.Unmarshal([]byte(buf[0:reqLen]), &am)
529 | if err2 != nil {
530 | log.Println("Err unmarshalling TCP inbound message from device. err:", err2)
531 | return
532 | }
533 | m.Announce = am.Announce
534 | m.Widget = am.Widget
535 | m.JsonTag = am.JsonTag
536 | m.DeviceId = am.MyDeviceId
537 |
538 | // send message to websocket clients, i.e. ChiliPeppr
539 | bm, err := json.Marshal(m)
540 | if err == nil {
541 | h.broadcastSys <- bm
542 | }
543 | */
544 |
545 | // Close the connection when you're done with it.
546 | conn.Close()
547 | }
548 |
--------------------------------------------------------------------------------
/compile_go1_12_crosscompile_fromwindows.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # git submodule init
3 | # git submodule update
4 |
5 | echo "About to cross-compile Serial Port JSON Server with Go 1.12"
6 | if [ "$1" = "" ]; then
7 | echo "You need to pass in the version number as the first parameter like ./compile_go1_5_crosscompile 1.87"
8 | exit
9 | fi
10 |
11 | rm -rf snapshot/*
12 | mkdir snapshot
13 |
14 | cp README.md snapshot/
15 |
16 | echo "Building Linux amd64"
17 | mkdir snapshot/serial-port-json-server-$1_linux_amd64
18 | mkdir snapshot/serial-port-json-server-$1_linux_amd64/arduino
19 | cp README.md snapshot/serial-port-json-server-$1_linux_amd64/
20 | cp sample* snapshot/serial-port-json-server-$1_linux_amd64
21 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_linux_amd64/arduino/hardware
22 | cp -r arduino/tools_linux_64 snapshot/serial-port-json-server-$1_linux_amd64/arduino/tools
23 | env GOOS=linux GOARCH=amd64 go build -v -o snapshot/serial-port-json-server-$1_linux_amd64/serial-port-json-server
24 | cd snapshot
25 | tar -zcvf serial-port-json-server-$1_linux_amd64.tar.gz serial-port-json-server-$1_linux_amd64
26 | cd ..
27 |
28 | echo ""
29 | echo "Building Linux 386"
30 | mkdir snapshot/serial-port-json-server-$1_linux_386
31 | mkdir snapshot/serial-port-json-server-$1_linux_386/arduino
32 | cp README.md snapshot/serial-port-json-server-$1_linux_386/
33 | cp sample* snapshot/serial-port-json-server-$1_linux_386
34 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_linux_386/arduino/hardware
35 | cp -r arduino/tools_linux_32 snapshot/serial-port-json-server-$1_linux_386/arduino/tools
36 | env GOOS=linux GOARCH=386 go build -v -o snapshot/serial-port-json-server-$1_linux_386/serial-port-json-server
37 | cd snapshot
38 | tar -zcvf serial-port-json-server-$1_linux_386.tar.gz serial-port-json-server-$1_linux_386
39 | cd ..
40 |
41 | echo ""
42 | echo "Building Linux ARM (Raspi)"
43 | mkdir snapshot/serial-port-json-server-$1_linux_arm
44 | mkdir snapshot/serial-port-json-server-$1_linux_arm/arduino
45 | cp README.md snapshot/serial-port-json-server-$1_linux_arm/
46 | cp sample* snapshot/serial-port-json-server-$1_linux_arm
47 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_linux_arm/arduino/hardware
48 | cp -r arduino/tools_linux_arm snapshot/serial-port-json-server-$1_linux_arm/arduino/tools
49 | env GOOS=linux GOARCH=arm go build -v -o snapshot/serial-port-json-server-$1_linux_arm/serial-port-json-server
50 | cd snapshot
51 | tar -zcvf serial-port-json-server-$1_linux_arm.tar.gz serial-port-json-server-$1_linux_arm
52 | cd ..
53 |
54 | echo ""
55 | echo "Building Windows x32"
56 | mkdir snapshot/serial-port-json-server-$1_windows_386
57 | mkdir snapshot/serial-port-json-server-$1_windows_386/arduino
58 | mkdir snapshot/serial-port-json-server-$1_windows_386/drivers
59 | cp -r drivers/* snapshot/serial-port-json-server-$1_windows_386/drivers
60 | cp README.md snapshot/serial-port-json-server-$1_windows_386/
61 | cp sample* snapshot/serial-port-json-server-$1_windows_386
62 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_windows_386/arduino/hardware
63 | cp -r arduino/tools_windows snapshot/serial-port-json-server-$1_windows_386/arduino/tools
64 | env GOOS=windows GOARCH=386 go build -v -o snapshot/serial-port-json-server-$1_windows_386/serial-port-json-server.exe
65 | cd snapshot/serial-port-json-server-$1_windows_386
66 | #zip -r ../serial-port-json-server-$1_windows_386.zip *
67 | zip a ../serial-port-json-server-$1_windows_386.zip *
68 | cd ../..
69 |
70 | echo ""
71 | echo "Building Windows x64"
72 | mkdir snapshot/serial-port-json-server-$1_windows_amd64
73 | mkdir snapshot/serial-port-json-server-$1_windows_amd64/arduino
74 | mkdir snapshot/serial-port-json-server-$1_windows_amd64/drivers
75 | cp README.md snapshot/serial-port-json-server-$1_windows_amd64/
76 | cp -r drivers/* snapshot/serial-port-json-server-$1_windows_amd64/drivers
77 | cp sample* snapshot/serial-port-json-server-$1_windows_amd64
78 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_windows_amd64/arduino/hardware
79 | cp -r arduino/tools_windows snapshot/serial-port-json-server-$1_windows_amd64/arduino/tools
80 | env GOOS=windows GOARCH=amd64 go build -v -o snapshot/serial-port-json-server-$1_windows_amd64/serial-port-json-server.exe
81 | cd snapshot/serial-port-json-server-$1_windows_amd64
82 | #zip -r ../serial-port-json-server-$1_windows_amd64.zip *
83 | zip a ../serial-port-json-server-$1_windows_amd64.zip *
84 | cd ../..
85 |
86 | echo ""
87 | echo "Building Darwin x64"
88 | mkdir snapshot/serial-port-json-server-$1_darwin_amd64
89 | mkdir snapshot/serial-port-json-server-$1_darwin_amd64/arduino
90 | cp README.md snapshot/serial-port-json-server-$1_darwin_amd64/
91 | cp sample* snapshot/serial-port-json-server-$1_darwin_amd64
92 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_darwin_amd64/arduino/hardware
93 | cp -r arduino/tools_darwin snapshot/serial-port-json-server-$1_darwin_amd64/arduino/tools
94 | env GOOS=darwin GOARCH=amd64 go build -v -o snapshot/serial-port-json-server-$1_darwin_amd64/serial-port-json-server
95 | cd snapshot/serial-port-json-server-$1_darwin_amd64
96 | zip -r ../serial-port-json-server-$1_darwin_amd64.zip *
97 | cd ../..
98 |
99 | export GITHUB_TOKEN=d1ce644ff5eef10f4f1e5fbf27515d22c7d68e8b
100 |
--------------------------------------------------------------------------------
/compile_go1_5_crosscompile.sh:
--------------------------------------------------------------------------------
1 | # git submodule init
2 | # git submodule update
3 |
4 | echo "About to cross-compile Serial Port JSON Server with Go 1.5"
5 | if [ "$1" = "" ]; then
6 | echo "You need to pass in the version number as the first parameter like ./compile_go1_5_crosscompile 1.87"
7 | exit
8 | fi
9 |
10 | rm -rf snapshot/*
11 |
12 | cp README.md snapshot/
13 |
14 | echo "Building Linux amd64"
15 | mkdir snapshot/serial-port-json-server-$1_linux_amd64
16 | mkdir snapshot/serial-port-json-server-$1_linux_amd64/arduino
17 | cp sample* snapshot/serial-port-json-server-$1_linux_amd64
18 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_linux_amd64/arduino/hardware
19 | cp -r arduino/tools_linux_64 snapshot/serial-port-json-server-$1_linux_amd64/arduino/tools
20 | env GOOS=linux GOARCH=amd64 go build -v -o snapshot/serial-port-json-server-$1_linux_amd64/serial-port-json-server
21 | cd snapshot
22 | tar -zcvf serial-port-json-server-$1_linux_amd64.tar.gz serial-port-json-server-$1_linux_amd64
23 | cd ..
24 |
25 | echo ""
26 | echo "Building Linux 386"
27 | mkdir snapshot/serial-port-json-server-$1_linux_386
28 | mkdir snapshot/serial-port-json-server-$1_linux_386/arduino
29 | cp sample* snapshot/serial-port-json-server-$1_linux_386
30 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_linux_386/arduino/hardware
31 | cp -r arduino/tools_linux_32 snapshot/serial-port-json-server-$1_linux_386/arduino/tools
32 | env GOOS=linux GOARCH=386 go build -v -o snapshot/serial-port-json-server-$1_linux_386/serial-port-json-server
33 | cd snapshot
34 | tar -zcvf serial-port-json-server-$1_linux_386.tar.gz serial-port-json-server-$1_linux_386
35 | cd ..
36 |
37 | echo ""
38 | echo "Building Linux ARM (Raspi)"
39 | mkdir snapshot/serial-port-json-server-$1_linux_arm
40 | mkdir snapshot/serial-port-json-server-$1_linux_arm/arduino
41 | cp sample* snapshot/serial-port-json-server-$1_linux_arm
42 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_linux_arm/arduino/hardware
43 | cp -r arduino/tools_linux_arm snapshot/serial-port-json-server-$1_linux_arm/arduino/tools
44 | env GOOS=linux GOARCH=arm go build -v -o snapshot/serial-port-json-server-$1_linux_arm/serial-port-json-server
45 | cd snapshot
46 | tar -zcvf serial-port-json-server-$1_linux_arm.tar.gz serial-port-json-server-$1_linux_arm
47 | cd ..
48 |
49 | echo ""
50 | echo "Building Windows x32"
51 | mkdir snapshot/serial-port-json-server-$1_windows_386
52 | mkdir snapshot/serial-port-json-server-$1_windows_386/arduino
53 | mkdir snapshot/serial-port-json-server-$1_windows_386/drivers
54 | cp -r drivers/* snapshot/serial-port-json-server-$1_windows_386/drivers
55 | cp sample* snapshot/serial-port-json-server-$1_windows_386
56 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_windows_386/arduino/hardware
57 | cp -r arduino/tools_windows snapshot/serial-port-json-server-$1_windows_386/arduino/tools
58 | env GOOS=windows GOARCH=386 go build -v -o snapshot/serial-port-json-server-$1_windows_386/serial-port-json-server.exe
59 | cd snapshot/serial-port-json-server-$1_windows_386
60 | zip -r ../serial-port-json-server-$1_windows_386.zip *
61 | cd ../..
62 |
63 | echo ""
64 | echo "Building Windows x64"
65 | mkdir snapshot/serial-port-json-server-$1_windows_amd64
66 | mkdir snapshot/serial-port-json-server-$1_windows_amd64/arduino
67 | mkdir snapshot/serial-port-json-server-$1_windows_amd64/drivers
68 | cp -r drivers/* snapshot/serial-port-json-server-$1_windows_amd64/drivers
69 | cp sample* snapshot/serial-port-json-server-$1_windows_amd64
70 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_windows_amd64/arduino/hardware
71 | cp -r arduino/tools_windows snapshot/serial-port-json-server-$1_windows_amd64/arduino/tools
72 | env GOOS=windows GOARCH=amd64 go build -v -o snapshot/serial-port-json-server-$1_windows_amd64/serial-port-json-server.exe
73 | cd snapshot/serial-port-json-server-$1_windows_amd64
74 | zip -r ../serial-port-json-server-$1_windows_amd64.zip *
75 | cd ../..
76 |
77 | echo ""
78 | echo "Building Darwin x64"
79 | mkdir snapshot/serial-port-json-server-$1_darwin_amd64
80 | mkdir snapshot/serial-port-json-server-$1_darwin_amd64/arduino
81 | cp sample* snapshot/serial-port-json-server-$1_darwin_amd64
82 | cp -r arduino/hardware snapshot/serial-port-json-server-$1_darwin_amd64/arduino/hardware
83 | cp -r arduino/tools_darwin snapshot/serial-port-json-server-$1_darwin_amd64/arduino/tools
84 | env GOOS=darwin GOARCH=amd64 go build -v -o snapshot/serial-port-json-server-$1_darwin_amd64/serial-port-json-server
85 | cd snapshot/serial-port-json-server-$1_darwin_amd64
86 | zip -r ../serial-port-json-server-$1_darwin_amd64.zip *
87 | cd ../..
88 |
89 | export GITHUB_TOKEN=d1ce644ff5eef10f4f1e5fbf27515d22c7d68e8b
90 |
--------------------------------------------------------------------------------
/compile_spjs.sh:
--------------------------------------------------------------------------------
1 | # git submodule init
2 | # git submodule update
3 |
4 | echo "About to cross-compile Serial Port JSON Server"
5 | if [ "$1" = "" ]; then
6 | echo "You need to pass in the version number as the first parameter."
7 | exit
8 | fi
9 |
10 | rm -rf snapshot/*
11 |
12 | cp README.md snapshot/
13 |
14 | cp -r arduino/tools_linux_64 arduino/tools
15 | goxc -os="linux" -arch="amd64" --include="arduino/hardware,arduino/tools" -n="serial-port-json-server" -d=.
16 | rm -rf arduino/tools
17 | mv snapshot/serial-port-json-server_linux_amd64.tar.gz snapshot/serial-port-json-server-$1_linux_amd64.tar.gz
18 |
19 | cp -r arduino/tools_linux_32 arduino/tools
20 | goxc -os="linux" -arch="386" --include="arduino/hardware,arduino/tools" -n="serial-port-json-server" -d=.
21 | rm -rf arduino/tools
22 | mv snapshot/serial-port-json-server_linux_386.tar.gz snapshot/serial-port-json-server-$1_linux_386.tar.gz
23 |
24 | cp -r arduino/tools_linux_arm arduino/tools
25 | goxc -os="linux" -arch="arm" --include="arduino/hardware,arduino/tools" -n="serial-port-json-server" -d=.
26 | rm -rf arduino/tools
27 | mv snapshot/serial-port-json-server_linux_arm.tar.gz snapshot/serial-port-json-server-$1_linux_arm.tar.gz
28 |
29 | cp -r arduino/tools_windows arduino/tools
30 | goxc -os="windows" --include="arduino/hardware,arduino/tools,drivers/windows" -n="serial-port-json-server" -d=.
31 | rm -rf arduino/tools
32 | mv snapshot/serial-port-json-server_windows_386.zip snapshot/serial-port-json-server-$1_windows_386.zip
33 | mv snapshot/serial-port-json-server_windows_amd64.zip snapshot/serial-port-json-server-$1_windows_amd64.zip
34 |
35 | cp -r arduino/tools_darwin arduino/tools
36 | goxc -os="darwin" --include="arduino/hardware,arduino/tools" -n="serial-port-json-server" -d=.
37 | rm -rf arduino/tools
38 | mv snapshot/serial-port-json-server_darwin_386.zip snapshot/serial-port-json-server-$1_darwin_386.zip
39 | mv snapshot/serial-port-json-server_darwin_amd64.zip snapshot/serial-port-json-server-$1_darwin_amd64.zip
40 |
41 | // remove snapshot files
42 | rm snapshot/*snapshot*
43 |
44 | sudo mkdir "/media/sf_downloads/v$1"
45 | sudo cp snapshot/*.zip snapshot/*.gz snapshot/*.md snapshot/*.deb "/media/sf_downloads/v$1/"
46 |
--------------------------------------------------------------------------------
/compile_webidebridge.sh:
--------------------------------------------------------------------------------
1 | # git submodule init
2 | # git submodule update
3 |
4 | cp -r arduino/tools_linux_64 arduino/tools
5 | goxc -os="linux" -arch="amd64" --include="arduino/hardware,arduino/tools" -n="Arduino_WebIDE_Bridge" -d=.
6 | rm -rf arduino/tools
7 |
8 | cp -r arduino/tools_linux_32 arduino/tools
9 | goxc -os="linux" -arch="386" --include="arduino/hardware,arduino/tools" -n="Arduino_WebIDE_Bridge" -d=.
10 | rm -rf arduino/tools
11 |
12 | cp -r arduino/tools_linux_arm arduino/tools
13 | goxc -os="linux" -arch="arm" --include="arduino/hardware,arduino/tools" -n="Arduino_WebIDE_Bridge" -d=.
14 | rm -rf arduino/tools
15 |
16 | cp -r arduino/tools_windows arduino/tools
17 | goxc -os="windows" --include="arduino/hardware,arduino/tools" -n="Arduino_WebIDE_Bridge" -d=.
18 | rm -rf arduino/tools
19 |
20 | cp -r arduino/tools_darwin arduino/tools
21 | goxc -os="darwin" --include="arduino/hardware,arduino/tools" -n="Arduino_WebIDE_Bridge" -d=.
22 | rm -rf arduino/tools
23 |
24 |
--------------------------------------------------------------------------------
/conn.go:
--------------------------------------------------------------------------------
1 | // Supports Windows, Linux, Mac, and Raspberry Pi
2 |
3 | // Going to add SSL
4 |
5 | package main
6 |
7 | import (
8 | "log"
9 | "net/http"
10 |
11 | "github.com/gorilla/websocket"
12 | )
13 |
14 | type connection struct {
15 | // The websocket connection.
16 | ws *websocket.Conn
17 |
18 | // Buffered channel of outbound messages.
19 | send chan []byte
20 | }
21 |
22 | func (c *connection) reader() {
23 | for {
24 | _, message, err := c.ws.ReadMessage()
25 | if err != nil {
26 | break
27 | }
28 |
29 | h.broadcast <- message
30 | }
31 | c.ws.Close()
32 | }
33 |
34 | func (c *connection) writer() {
35 | for message := range c.send {
36 | err := c.ws.WriteMessage(websocket.TextMessage, message)
37 | if err != nil {
38 | break
39 | }
40 | }
41 | c.ws.Close()
42 | }
43 |
44 | func wsHandler(w http.ResponseWriter, r *http.Request) {
45 | log.Print("Started a new websocket handler")
46 | ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
47 | if _, ok := err.(websocket.HandshakeError); ok {
48 | http.Error(w, "Not a websocket handshake", 400)
49 | return
50 | } else if err != nil {
51 | return
52 | }
53 | //c := &connection{send: make(chan []byte, 256), ws: ws}
54 | c := &connection{send: make(chan []byte, 256*10), ws: ws}
55 | h.register <- c
56 | defer func() { h.unregister <- c }()
57 | go c.writer()
58 | c.reader()
59 | }
60 |
--------------------------------------------------------------------------------
/download.go:
--------------------------------------------------------------------------------
1 | // download.go
2 | package main
3 |
4 | import (
5 | "errors"
6 | "io"
7 | "io/ioutil"
8 | "log"
9 | "net/http"
10 | "os"
11 | "path/filepath"
12 | "strings"
13 | )
14 |
15 | func downloadFromUrl(url string) (filename string, err error) {
16 |
17 | // clean up url
18 | // remove newlines and space at end
19 | url = strings.TrimSpace(url)
20 |
21 | // create tmp dir
22 | tmpdir, err := ioutil.TempDir("", "serial-port-json-server")
23 | if err != nil {
24 | return "", errors.New("Could not create temp directory to store downloaded file. Do you have permissions?")
25 | }
26 | tokens := strings.Split(url, "/")
27 | filePrefix := tokens[len(tokens)-1]
28 | log.Println("The filePrefix is", filePrefix)
29 |
30 | fileName, _ := filepath.Abs(tmpdir + "/" + filePrefix)
31 | log.Println("Downloading", url, "to", fileName)
32 |
33 | // TODO: check file existence first with io.IsExist
34 | output, err := os.Create(fileName)
35 | if err != nil {
36 | log.Println("Error while creating", fileName, "-", err)
37 | return fileName, err
38 | }
39 | defer output.Close()
40 |
41 | response, err := http.Get(url)
42 | if err != nil {
43 | log.Println("Error while downloading", url, "-", err)
44 | return fileName, err
45 | }
46 | defer response.Body.Close()
47 |
48 | n, err := io.Copy(output, response.Body)
49 | if err != nil {
50 | log.Println("Error while downloading", url, "-", err)
51 | return fileName, err
52 | }
53 |
54 | log.Println(n, "bytes downloaded.")
55 |
56 | return fileName, nil
57 | }
58 |
--------------------------------------------------------------------------------
/drivers/windows/TinyGv2.inf:
--------------------------------------------------------------------------------
1 | [Strings]
2 | DriverPackageDisplayName="TinyG USB Driver"
3 | ManufacturerName="Synthetos (www.synthetos.com)"
4 | ServiceName="USB RS-232 Emulation Driver"
5 | due.bossa.name="Bossa Program Port"
6 | due.programming_port.name="Arduino Due Programming Port"
7 | due.sketch01.name="TinyG v2 (Control Channel)"
8 | due.sketch02.name="TinyG v2 (Data Channel)"
9 | due.original.name="Arduino (broken TinyGv2)"
10 |
11 | [DefaultInstall]
12 | CopyINF=arduino.inf
13 |
14 | [Version]
15 | Class=Ports
16 | ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318}
17 | Signature="$Windows NT$"
18 | Provider=%ManufacturerName%
19 | DriverPackageDisplayName=%DriverPackageDisplayName%
20 | CatalogFile=arduino.cat
21 | DriverVer=08/27/2014,1.0.1.0
22 |
23 | [Manufacturer]
24 | %ManufacturerName%=DeviceList, NTamd64, NTia64
25 |
26 | [DestinationDirs]
27 | FakeModemCopyFileSection=12
28 | DefaultDestDir=12
29 |
30 | [DeviceList]
31 | %due.bossa.name%=DriverInstall, USB\VID_03EB&PID_6124
32 | %due.programming_port.name%=DriverInstall, USB\VID_2341&PID_003D
33 | %due.sketch01.name%=DriverInstall, USB\VID_1D50&PID_606D&MI_00
34 | %due.sketch02.name%=DriverInstall, USB\VID_1D50&PID_606D&MI_01
35 | %due.original.name%=DriverInstall, USB\VID_2341&PID_003E&MI_00
36 |
37 | [DeviceList.NTamd64]
38 | %due.bossa.name%=DriverInstall, USB\VID_03EB&PID_6124
39 | %due.programming_port.name%=DriverInstall, USB\VID_2341&PID_003D
40 | %due.sketch01.name%=DriverInstall, USB\VID_1D50&PID_606D&MI_00
41 | %due.sketch02.name%=DriverInstall, USB\VID_1D50&PID_606D&MI_01
42 | %due.original.name%=DriverInstall, USB\VID_2341&PID_003E&MI_00
43 |
44 | [DeviceList.NTia64]
45 | %due.bossa.name%=DriverInstall, USB\VID_03EB&PID_6124
46 | %due.programming_port.name%=DriverInstall, USB\VID_2341&PID_003D
47 | %due.sketch01.name%=DriverInstall, USB\VID_1D50&PID_606D&MI_00
48 | %due.sketch02.name%=DriverInstall, USB\VID_1D50&PID_606D&MI_01
49 | %due.original.name%=DriverInstall, USB\VID_2341&PID_003E&MI_00
50 |
51 | [DriverInstall]
52 | include=mdmcpq.inf,usb.inf
53 | CopyFiles = FakeModemCopyFileSection
54 | AddReg=DriverAddReg
55 |
56 | [DriverAddReg]
57 | HKR,,DevLoader,,*ntkern
58 | HKR,,NTMPDriver,,usbser.sys
59 | HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider"
60 |
61 | [DriverInstall.Services]
62 | include=mdmcpq.inf
63 | AddService=usbser, 0x00000002, DriverService
64 |
65 | [DriverService]
66 | DisplayName=%ServiceName%
67 | ServiceType=1
68 | StartType=3
69 | ErrorControl=1
70 | ServiceBinary=%12%\usbser.sys
71 | LoadOrderGroup=Base
72 |
--------------------------------------------------------------------------------
/dummy.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "time"
7 | )
8 |
9 | type dummy struct {
10 | //myvar mytype string
11 | }
12 |
13 | var d = dummy{
14 | //myvar: make(mytype string),
15 | }
16 |
17 | func (d *dummy) run() {
18 | for {
19 | //h.broadcast <- message
20 | log.Print("dummy data")
21 | //h.broadcast <- []byte("dummy data")
22 | time.Sleep(8000 * time.Millisecond)
23 | h.broadcast <- []byte("list")
24 |
25 | // open com4 (tinyg)
26 | h.broadcast <- []byte("open com4 115200 tinyg")
27 | time.Sleep(1000 * time.Millisecond)
28 |
29 | // send some commands
30 | //h.broadcast <- []byte("send com4 ?\n")
31 | //time.Sleep(3000 * time.Millisecond)
32 | h.broadcast <- []byte("send com4 {\"qr\":\"\"}\n")
33 | h.broadcast <- []byte("send com4 g21 g90\n") // mm
34 | //h.broadcast <- []byte("send com4 {\"qr\":\"\"}\n")
35 | //h.broadcast <- []byte("send com4 {\"sv\":0}\n")
36 | //time.Sleep(3000 * time.Millisecond)
37 | for i := 0.0; i < 10.0; i = i + 0.001 {
38 | h.broadcast <- []byte("send com4 G1 X" + fmt.Sprintf("%.3f", i) + " F100\n")
39 | time.Sleep(10 * time.Millisecond)
40 | }
41 | /*
42 | h.broadcast <- []byte("send com4 G1 X1\n")
43 | h.broadcast <- []byte("send com4 G1 X2\n")
44 | h.broadcast <- []byte("send com4 G1 X3\n")
45 | h.broadcast <- []byte("send com4 G1 X4\n")
46 | h.broadcast <- []byte("send com4 G1 X5\n")
47 | h.broadcast <- []byte("send com4 G1 X6\n")
48 | h.broadcast <- []byte("send com4 G1 X7\n")
49 | h.broadcast <- []byte("send com4 G1 X8\n")
50 | h.broadcast <- []byte("send com4 G1 X9\n")
51 | h.broadcast <- []byte("send com4 G1 X10\n")
52 | h.broadcast <- []byte("send com4 G1 X1\n")
53 | h.broadcast <- []byte("send com4 G1 X2\n")
54 | h.broadcast <- []byte("send com4 G1 X3\n")
55 | h.broadcast <- []byte("send com4 G1 X4\n")
56 | h.broadcast <- []byte("send com4 G1 X5\n")
57 | h.broadcast <- []byte("send com4 G1 X6\n")
58 | h.broadcast <- []byte("send com4 G1 X7\n")
59 | h.broadcast <- []byte("send com4 G1 X8\n")
60 | h.broadcast <- []byte("send com4 G1 X9\n")
61 | h.broadcast <- []byte("send com4 G1 X10\n")
62 | h.broadcast <- []byte("send com4 G1 X1\n")
63 | h.broadcast <- []byte("send com4 G1 X2\n")
64 | h.broadcast <- []byte("send com4 G1 X3\n")
65 | h.broadcast <- []byte("send com4 G1 X4\n")
66 | h.broadcast <- []byte("send com4 G1 X5\n")
67 | h.broadcast <- []byte("send com4 G1 X6\n")
68 | h.broadcast <- []byte("send com4 G1 X7\n")
69 | h.broadcast <- []byte("send com4 G1 X8\n")
70 | h.broadcast <- []byte("send com4 G1 X9\n")
71 | h.broadcast <- []byte("send com4 G1 X10\n")
72 | */
73 | break
74 | }
75 | log.Println("dummy process exited")
76 | }
77 |
--------------------------------------------------------------------------------
/execprocess.go:
--------------------------------------------------------------------------------
1 | // The execprocess feature lets SPJS run anything on the command line as a pass-thru
2 | // scenario. Obviously there are security concerns here if somebody opens up their
3 | // SPJS to the Internet, however if a user opens SPJS to the Internet they are
4 | // exposing a lot of things, so we will trust that users implement their own
5 | // layer of security at their firewall, rather than SPJS managing it.
6 |
7 | package main
8 |
9 | import (
10 | "bufio"
11 | // "bytes"
12 | "fmt"
13 | "strings"
14 |
15 | "golang.org/x/crypto/ssh"
16 | // "go/scanner"
17 | "runtime"
18 |
19 | "encoding/json"
20 | "log"
21 | "os"
22 | "os/exec"
23 | "regexp"
24 | )
25 |
26 | type ExecCmd struct {
27 | ExecStatus string
28 | Id string
29 | Cmd string
30 | Args []string
31 | Output string
32 | //Stderr string
33 | }
34 |
35 | func execRun(command string) {
36 | log.Printf("About to execute command:%s\n", command)
37 |
38 | // we have to remove the word "exec " from the front
39 | re, _ := regexp.Compile("^exec\\s+")
40 | cleanCmd := re.ReplaceAllString(command, "")
41 |
42 | // see if there's an id, and if so, yank it out
43 | // grab any word after id: and do case insensitive (?i)
44 | reId := regexp.MustCompile("(?i)^id:[a-zA-z0-9_\\-]+")
45 | id := reId.FindString(cleanCmd)
46 | if len(id) > 0 {
47 | // we found an id at the start of the exec command, use it
48 | cleanCmd = reId.ReplaceAllString(cleanCmd, "")
49 | cleanCmd = strings.TrimPrefix(cleanCmd, " ")
50 | id = regexp.MustCompile("^id:").ReplaceAllString(id, "")
51 | }
52 |
53 | // grab username and password
54 | isAttemptedUserPassValidation := false
55 | reUser := regexp.MustCompile("(?i)^user:[a-zA-z0-9_\\-]+")
56 | user := reUser.FindString(cleanCmd)
57 | if len(user) > 0 {
58 | isAttemptedUserPassValidation = true
59 | // we found a username at the start of the exec command, use it
60 | cleanCmd = reUser.ReplaceAllString(cleanCmd, "")
61 | cleanCmd = strings.TrimPrefix(cleanCmd, " ")
62 | user = regexp.MustCompile("^user:").ReplaceAllString(user, "")
63 | }
64 | rePass := regexp.MustCompile("(?i)^pass:[a-zA-z0-9_\\-]+")
65 | pass := rePass.FindString(cleanCmd)
66 | if len(pass) > 0 {
67 | // we found a username at the start of the exec command, use it
68 | cleanCmd = rePass.ReplaceAllString(cleanCmd, "")
69 | cleanCmd = strings.TrimPrefix(cleanCmd, " ")
70 | pass = regexp.MustCompile("^pass:").ReplaceAllString(pass, "")
71 | }
72 |
73 | // trim front and back of string
74 | cleanCmd = regexp.MustCompile("^\\s*").ReplaceAllString(cleanCmd, "")
75 | cleanCmd = regexp.MustCompile("\\s*$").ReplaceAllString(cleanCmd, "")
76 | line := cleanCmd
77 | argArr := []string{line}
78 |
79 | // OLD APPROACH
80 | // now we have to split off the first command and pass the rest as args
81 | /*
82 | cmdArr := strings.Split(cleanCmd, " ")
83 | cmd := cmdArr[0]
84 | argArr := cmdArr[1:]
85 | oscmd := exec.Command(cmd, argArr...)
86 | */
87 | var cmd string
88 | var oscmd *exec.Cmd
89 |
90 | // allow user/pass authentication. assume not valid.
91 | isUserPassValid := false
92 |
93 | // NEW APPROACH borrowed from mattn/go-shellwords
94 | if runtime.GOOS == "windows" {
95 | shell := os.Getenv("COMSPEC")
96 | cmd = shell
97 | oscmd = exec.Command(shell, "/c", line)
98 | } else {
99 | shell := os.Getenv("SHELL")
100 | cmd = shell
101 | oscmd = exec.Command(shell, "-c", line)
102 |
103 | // if posix, i.e. linux or mac just check password via ssh
104 | if len(user) > 0 && len(pass) > 0 && checkUserPass(user, pass) {
105 | // the password was valid
106 | isUserPassValid = true
107 | log.Printf("User/pass was valid for request")
108 | }
109 | }
110 |
111 | if isAttemptedUserPassValidation {
112 | if isUserPassValid == false {
113 | errMsg := fmt.Sprintf("User:%s and password were not valid so not able to execute cmd.", user)
114 | log.Println(errMsg)
115 | mapD := ExecCmd{ExecStatus: "Error", Id: id, Cmd: cmd, Args: argArr, Output: errMsg}
116 | mapB, _ := json.Marshal(mapD)
117 | h.broadcastSys <- mapB
118 | return
119 | } else {
120 | log.Println("User:%s and password were valid. Running command.", user)
121 | }
122 | } else if *isAllowExec == false {
123 | log.Printf("Error trying to execute terminal command. No user/pass provided or command line switch was not specified to allow exec command. Provide a valied username/password or restart spjs with -allowexec command line option to exec command.")
124 | //h.broadcastSys <- []byte("Trying to execute terminal command, but command line switch was not specified to allow for this. Restart spjs with -allowexec command line option to enable.\n")
125 | mapD := ExecCmd{ExecStatus: "Error", Id: id, Cmd: cmd, Args: argArr, Output: "Error trying to execute terminal command. No user/pass provided or command line switch was not specified to allow exec command. Provide a valied username/password or restart spjs with -allowexec command line option to exec command."}
126 | mapB, _ := json.Marshal(mapD)
127 | h.broadcastSys <- mapB
128 | return
129 | } else {
130 | log.Println("Running cmd cuz -allowexec specified as command line option.")
131 | }
132 |
133 | // OLD APPROACH where we would queue up entire command and wait until done
134 | // will block here until results are done
135 | //cmdOutput, err := oscmd.CombinedOutput()
136 |
137 | //endProgress()
138 |
139 | // NEW APPROACH. Stream stdout while it is running so we get more real-time updates.
140 | cmdOutput := ""
141 | cmdReader, err := oscmd.StdoutPipe()
142 | if err != nil {
143 | log.Println(os.Stderr, "Error creating StdoutPipe for Cmd", err)
144 | //os.Exit(1)
145 | mapD := ExecCmd{ExecStatus: "Error", Id: id, Cmd: cmd, Args: argArr, Output: err.Error()}
146 | mapB, _ := json.Marshal(mapD)
147 | h.broadcastSys <- mapB
148 | return
149 | }
150 |
151 | scanner := bufio.NewScanner(cmdReader)
152 | go func() {
153 | for scanner.Scan() {
154 | log.Printf("stdout > %s\n", scanner.Text())
155 | mapD := ExecCmd{ExecStatus: "Progress", Id: id, Cmd: cmd, Args: argArr, Output: scanner.Text()}
156 | mapB, _ := json.Marshal(mapD)
157 | h.broadcastSys <- mapB
158 | cmdOutput += scanner.Text()
159 | }
160 | }()
161 |
162 | err = oscmd.Start()
163 | if err != nil {
164 | log.Println(os.Stderr, "Error starting Cmd", err)
165 | //os.Exit(1)
166 | mapD := ExecCmd{ExecStatus: "Error", Id: id, Cmd: cmd, Args: argArr, Output: fmt.Sprintf("Error starting Cmd", err)}
167 | mapB, _ := json.Marshal(mapD)
168 | h.broadcastSys <- mapB
169 | return
170 | }
171 |
172 | // block here until command done
173 | err = oscmd.Wait()
174 | /*if err != nil {
175 | fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err)
176 | // os.Exit(1)
177 | mapD := ExecCmd{ExecStatus: "Error", Id: id, Cmd: cmd, Args: argArr, Output: fmt.Sprintf(os.Stderr, "Error waiting for Cmd", err)}
178 | mapB, _ := json.Marshal(mapD)
179 | h.broadcastSys <- mapB
180 | return
181 | }*/
182 |
183 | if err != nil {
184 | log.Printf("Command finished with error: %v "+string(cmdOutput), err)
185 | //h.broadcastSys <- []byte("Could not program the board")
186 | //mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board. It is also possible your serial port is locked by another app and thus we can't grab it to use for programming. Make sure all other apps that may be trying to access this serial port are disconnected or exited.", "Output": string(cmdOutput)}
187 | mapD := ExecCmd{ExecStatus: "Error", Id: id, Cmd: cmd, Args: argArr, Output: string(cmdOutput) + err.Error()}
188 | mapB, _ := json.Marshal(mapD)
189 | h.broadcastSys <- mapB
190 | } else {
191 | log.Printf("Finished without error. Good stuff. stdout: " + string(cmdOutput))
192 | //h.broadcastSys <- []byte("Flash OK!")
193 | mapD := ExecCmd{ExecStatus: "Done", Id: id, Cmd: cmd, Args: argArr, Output: string(cmdOutput)}
194 | mapB, _ := json.Marshal(mapD)
195 | h.broadcastSys <- mapB
196 | // analyze stdin
197 |
198 | }
199 |
200 | }
201 |
202 | type ExecRuntime struct {
203 | ExecRuntimeStatus string
204 | OS string
205 | Arch string
206 | Goroot string
207 | NumCpu int
208 | }
209 |
210 | // Since SPJS runs on any OS, you will need to query to figure out
211 | // what OS we're on so you know the style of commands to send
212 | func execRuntime() {
213 | // create the struct and send data back
214 | info := ExecRuntime{"Done", runtime.GOOS, runtime.GOARCH, runtime.GOROOT(), runtime.NumCPU()}
215 | bm, err := json.Marshal(info)
216 | if err == nil {
217 | h.broadcastSys <- bm
218 | }
219 | }
220 |
221 | func checkUserPass(user string, pass string) bool {
222 | // We check the validity of the username/password
223 | // An SSH client is represented with a ClientConn. Currently only
224 | // the "password" authentication method is supported.
225 | //
226 | // To authenticate with the remote server you must pass at least one
227 | // implementation of AuthMethod via the Auth field in ClientConfig.
228 | config := &ssh.ClientConfig{
229 | User: user,
230 | Auth: []ssh.AuthMethod{
231 | ssh.Password(pass),
232 | },
233 | }
234 | client, err := ssh.Dial("tcp", "localhost:22", config)
235 | if err != nil {
236 | log.Println("Failed to dial: " + err.Error())
237 | return false
238 | }
239 |
240 | // Each ClientConn can support multiple interactive sessions,
241 | // represented by a Session.
242 | session, err := client.NewSession()
243 | if err != nil {
244 | log.Println("Failed to create session: " + err.Error())
245 | return false
246 | }
247 | defer session.Close()
248 |
249 | // Once a Session is created, you can execute a single command on
250 | // the remote side using the Run method.
251 | /*
252 | var b bytes.Buffer
253 | session.Stdout = &b
254 | if err := session.Run("echo spjs-authenticated"); err != nil {
255 | log.Println("Failed to run: " + err.Error())
256 | return false
257 | }
258 | log.Println(b.String())
259 | */
260 | return true
261 | }
262 |
--------------------------------------------------------------------------------
/feedrateoverride.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | //"fmt"
5 | "encoding/json"
6 | "log"
7 | "regexp"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | var (
13 | reFeedrate = regexp.MustCompile("(?i)F(\\d+\\.{0,1}\\d*)")
14 | isFroNeedTriggered = false
15 | isFroOn = false
16 | //fro = 0.0
17 | currentFeedrate = -1.0
18 | lastFeedrateSeen = -1.0
19 | portsWithFrOverrideOn = make(map[string]bool)
20 | )
21 |
22 | type froRequestJson struct {
23 | Cmd string
24 | Desc string
25 | Port string
26 | FeedRateOverride float32
27 | IsOn bool
28 | }
29 |
30 | // This is called from hub.go to actually parse the "fro COM7 1.5" command sent by the user
31 | func spFeedRateOverride(arg string) {
32 |
33 | // we will get a string of "fro COM9 2.4" or "fro /dev/ttyUSB0 0.1"
34 | log.Printf("Inside spFeedRateOverride arg: %v\n", strings.Replace(arg, "\n", "\\n", -1))
35 | arg = strings.TrimSpace(arg)
36 | arg = strings.TrimPrefix(arg, " ")
37 |
38 | args := strings.Split(arg, " ")
39 | log.Println(args)
40 |
41 | if len(args) != 2 && len(args) != 3 {
42 | errstr := "Could not parse feedrate override command: " + arg
43 | log.Println(errstr)
44 | spErr(errstr)
45 | return
46 | }
47 | portname := strings.Trim(args[1], " ")
48 | log.Println("The port to write to is:" + portname + "---")
49 |
50 | //log.Println("The data is:" + args[2] + "---")
51 |
52 | // see if we have this port open
53 | myport, isFound := findPortByName(portname)
54 |
55 | if !isFound {
56 | // we couldn't find the port, so send err
57 | //isFroOn = false
58 | spErr("We could not find the serial port " + portname + " that you were trying to apply the feedrate override to. This error is ok actually because it just means you have not opened the serial port yet.")
59 | return
60 | }
61 |
62 | // see if they are just querying status
63 | if len(args) == 2 {
64 | sendStatusOnFeedrateOverride(myport)
65 | return
66 | }
67 |
68 | // we found our port, so now parse our multiplier
69 | fro, err := strconv.ParseFloat(strings.TrimSpace(args[2]), 32)
70 | if err != nil {
71 | errstr := "Could not parse feedrate override multiplier value: " + args[2]
72 | log.Println(errstr)
73 | spErr(errstr)
74 | return
75 | }
76 |
77 | myport.isFeedRateOverrideOn = true
78 |
79 | myport.feedRateOverride = float32(fro)
80 |
81 | var frj froRequestJson
82 | frj.Cmd = "FeedRateOverride"
83 | frj.FeedRateOverride = myport.feedRateOverride
84 | frj.Port = myport.portConf.Name
85 | frj.Desc = "Successfully set the feedrate override."
86 |
87 | if frj.FeedRateOverride <= 0.0 {
88 | isFroOn = false
89 | log.Println("User turned off feedrate override by setting it to 0")
90 | frj.IsOn = false
91 | } else {
92 | isFroOn = true
93 | frj.IsOn = true
94 | }
95 |
96 | //ls, err := json.MarshalIndent(frj, "", "\t")
97 | ls, err := json.Marshal(frj)
98 | if err != nil {
99 | log.Println(err)
100 | h.broadcastSys <- []byte("Error creating json on feedrate override report " +
101 | err.Error())
102 | } else {
103 | //log.Print("Printing out json byte data...")
104 | //log.Print(ls)
105 | h.broadcastSys <- ls
106 | }
107 |
108 | // if we made it this far we truly have a feedrate override in play
109 | // so set boolean that we need to inject it into the next line
110 | isFroNeedTriggered = true
111 |
112 | }
113 |
114 | func sendStatusOnFeedrateOverride(myport *serport) {
115 | // they just want a status
116 | var frj froRequestJson
117 | frj.Cmd = "FeedRateOverride"
118 | frj.FeedRateOverride = myport.feedRateOverride
119 | frj.Port = myport.portConf.Name
120 | frj.Desc = "Providing you status of feed rate override."
121 |
122 | if frj.FeedRateOverride <= 0.0 {
123 | frj.IsOn = false
124 | } else {
125 | frj.IsOn = true
126 | }
127 |
128 | ls, err := json.Marshal(frj)
129 | if err != nil {
130 | log.Println(err)
131 | h.broadcastSys <- []byte("Error creating json on feedrate override report " +
132 | err.Error())
133 | } else {
134 | //log.Print("Printing out json byte data...")
135 | //log.Print(ls)
136 | h.broadcastSys <- ls
137 | }
138 | return
139 | }
140 |
141 | // Here is where we actually apply the feedrate override on a line of gcode
142 | func doFeedRateOverride(str string, feedrateoverride float32) (bool, string) {
143 |
144 | // myport, isFound := findPortByName(portname)
145 | // if myport == nil || myport.isFeedRateOverrideOn == false {
146 | // log.Println("This port has no feed rate override on. So returning...")
147 | // return false, ""
148 | // }
149 |
150 | //log.Println("Feed Rate Override Start")
151 | // any way we cut this, we MUST extract the feedrate from every line whether
152 | // fro is on or not because we need the currentFeedrate the moment the user asks
153 | // us to turn this on
154 | strArrFsSeen := reFeedrate.FindAllStringSubmatch(str, -1)
155 | if len(strArrFsSeen) > 0 {
156 | // we found some feedrate F values, so let's store it
157 | log.Printf("\tFRO: F's found:%v", strArrFsSeen)
158 | justFoundFeedrate := strArrFsSeen[len(strArrFsSeen)-1][1]
159 | lastFeedrateSeen, _ = strconv.ParseFloat(justFoundFeedrate, 64)
160 | currentFeedrate = lastFeedrateSeen
161 | log.Printf("\tFRO: Found an F so storing it for reference. lastFeedrateSeen:%v", lastFeedrateSeen)
162 | }
163 |
164 | if feedrateoverride == 0.0 && !isFroNeedTriggered {
165 | //log.Println("\tFRO: Feed Rate override is 0.0 so returning")
166 | return false, ""
167 | }
168 |
169 | // Typical line of gcode
170 | // N15 G2 F800.0 X39.0719 Y-3.7614 I-2.0806 J1.2144
171 | // Which, if the feedrate override is 2.6 we want to make look like
172 | // N15 G2 F2080.0 X39.0719 Y-3.7614 I-2.0806 J1.2144
173 |
174 | //str := "N15 G2 f800.0 X39.0719 Y-3.7614 F30 I-2.0806 J1.2144"
175 | //re := regexp.MustCompile("(?i)F(\\d+\\.{0,1}\\d*)")
176 | //strArr := re.FindAllString(str, -1)
177 | //fmt.Println(strArr)
178 | strArr2 := reFeedrate.FindAllStringSubmatch(str, -1)
179 | //log.Println(strArr2)
180 | if len(strArr2) == 0 {
181 |
182 | log.Println("\tFRO: No match found for feedrateoverride.")
183 |
184 | // see if the user asked for a feedrate override though
185 | // if they did, we need to inject one because we didn't find one to adjust
186 | if isFroNeedTriggered {
187 |
188 | log.Printf("\tFRO: We need to inject a feedrate...\n")
189 |
190 | if currentFeedrate == -1.0 {
191 |
192 | // this means we have no idea what the current feedrate is. that means
193 | // the gcode before us never specified it ever so we are stuck and can't
194 | // create the override
195 | log.Println("\tFRO: We have no idea what the current feedrate is, so giving up")
196 | return false, ""
197 |
198 | } else {
199 |
200 | myFro := feedrateoverride
201 | // since a value of 0 means turn off, we need to make it multiply like a 1, but leave it zero to mean turn off
202 | if myFro == 0.0 {
203 | myFro = 1.0
204 | }
205 |
206 | // if we get here we need to inject an F at the end of the line
207 | injectFr := currentFeedrate * float64(myFro)
208 | log.Printf("\tFRO: We do know the current feedrate: %v, so we will inject: F%v\n", currentFeedrate, injectFr)
209 |
210 | str = str + "F" + FloatToString(injectFr)
211 | log.Printf("\tFRO: New gcode line: %v\n", str)
212 |
213 | // set to false so next time through we don't inject again
214 | isFroNeedTriggered = false
215 |
216 | return true, str
217 | }
218 |
219 | }
220 |
221 | // no match found for feedrate, but also there is no need for an injection
222 | // so returning
223 | log.Printf("\tFRO: No need for injection of feedrate either cuz user never asked. currentFeedrate:%v. Returning.", currentFeedrate)
224 | return false, ""
225 | }
226 |
227 | // set to false so next time through we don't override again
228 | isFroNeedTriggered = false
229 |
230 | indxArr := reFeedrate.FindAllStringSubmatchIndex(str, -1)
231 | //log.Println(indxArr)
232 |
233 | fro := float64(feedrateoverride)
234 | //fro := float64(2.6)
235 | //fro :=
236 |
237 | // keep track of whether we set the override yet in this method
238 | // this only matters if there are 2 or more F's in one gcode line
239 | // which should almost never happen, but just in case, since we iterate
240 | // in reverse, only use the first time through
241 | isAlreadySetCurrentFeedrate := false
242 |
243 | // loop in reverse so we can inject the new feedrate string at end and not have
244 | // our indexes thrown off
245 | for i := len(strArr2) - 1; i >= 0; i-- {
246 |
247 | itemArr := strArr2[i]
248 | //log.Println(itemArr)
249 |
250 | fr, err := strconv.ParseFloat(itemArr[1], 32)
251 | if err != nil {
252 | log.Println("\tFRO: Error parsing feedrate val", err)
253 | } else {
254 |
255 | // set this as current feedrate
256 | if !isAlreadySetCurrentFeedrate {
257 | currentFeedrate = fr
258 | isAlreadySetCurrentFeedrate = true
259 | log.Printf("\tFRO: Just set current feedrate: %v\n", currentFeedrate)
260 | }
261 |
262 | // only if fro is on should we proceed with the actual swap
263 | if isFroOn == true {
264 |
265 | newFr := fr * fro
266 | //log.Println(newFr)
267 |
268 | // swap out the string for our new string
269 | // because we are looping in reverse, these indexes are valid
270 | str = str[:indxArr[i][2]] + FloatToString(newFr) + str[indxArr[i][3]:]
271 | log.Println("\tFRO: " + strings.Replace(str, "\n", "\\n", -1))
272 | }
273 | }
274 |
275 | }
276 |
277 | return true, str
278 |
279 | }
280 |
281 | func FloatToString(input_num float64) string {
282 | // to convert a float number to a string
283 | return strconv.FormatFloat(input_num, 'f', 3, 64)
284 | }
285 |
--------------------------------------------------------------------------------
/gpio.go:
--------------------------------------------------------------------------------
1 | // +build ignore
2 | //+build !linux,!arm
3 | // Ignore this file for now, but it would be nice to get GPIO going natively
4 |
5 | package main
6 |
7 | import (
8 | "encoding/json"
9 | "io/ioutil"
10 | "log"
11 | "os"
12 | "os/signal"
13 | )
14 |
15 | var (
16 | gpio GPIO
17 | )
18 |
19 | type GPIO struct {
20 | pinStates map[string]PinState
21 | pinStateChanged chan PinState
22 | pinAdded chan PinState
23 | pinRemoved chan string
24 | }
25 |
26 | type Direction int
27 | type PullUp int
28 |
29 | type PinState struct {
30 | Pin interface{} `json:"-"`
31 | PinId string
32 | Dir Direction
33 | State byte
34 | Pullup PullUp
35 | Name string
36 | }
37 |
38 | type PinDef struct {
39 | ID string
40 | Aliases []string
41 | Capabilities []string
42 | DigitalLogical int
43 | AnalogLogical int
44 | }
45 |
46 | const STATE_FILE = "pinstates.json"
47 |
48 | const (
49 | In Direction = 0
50 | Out Direction = 1
51 | PWM Direction = 2
52 |
53 | Pull_None PullUp = 0
54 | Pull_Up PullUp = 1
55 | Pull_Down PullUp = 2
56 | )
57 |
58 | type GPIOInterface interface {
59 | PreInit()
60 | Init(chan PinState, chan PinState, chan string, map[string]PinState) error
61 | Close() error
62 | PinMap() ([]PinDef, error)
63 | Host() (string, error)
64 | PinStates() (map[string]PinState, error)
65 | PinInit(string, Direction, PullUp, string) error
66 | PinSet(string, byte) error
67 | PinRemove(string) error
68 | }
69 |
70 | func (g *GPIO) CleanupGpio() {
71 | pinStates, err := gpio.PinStates()
72 | if err != nil {
73 | log.Println("Error getting pinstates on cleanup: " + err.Error())
74 | } else {
75 | data, err := json.Marshal(pinStates)
76 | if err != nil {
77 | log.Println("Error marshalling pin states : " + err.Error())
78 | }
79 | ioutil.WriteFile(STATE_FILE, data, 0644)
80 | }
81 | gpio.Close()
82 | os.Exit(1)
83 | }
84 |
85 | // I took what Ben had in his main.go file and moved it here
86 | func (g *GPIO) PreInit() {
87 |
88 | c := make(chan os.Signal, 1)
89 | signal.Notify(c, os.Interrupt)
90 |
91 | go func() {
92 | for sig := range c {
93 | // sig is a ^C, handle it
94 | log.Printf("captured %v, cleaning up gpio and exiting..", sig)
95 | gpio.CleanupGpio()
96 | }
97 | }()
98 |
99 | stateChanged := make(chan PinState)
100 | pinRemoved := make(chan string)
101 | pinAdded := make(chan PinState)
102 | go func() {
103 | for {
104 | // start listening on stateChanged and pinRemoved channels and update hub as appropriate
105 | select {
106 | case pinState := <-stateChanged:
107 | go h.sendMsg("PinState", pinState)
108 | case pinName := <-pinRemoved:
109 | go h.sendMsg("PinRemoved", pinName)
110 | case pinState := <-pinAdded:
111 | go h.sendMsg("PinAdded", pinState)
112 | }
113 | }
114 | }()
115 |
116 | pinStates := make(map[string]PinState)
117 |
118 | // read existing pin states
119 | if _, err := os.Stat(STATE_FILE); err == nil {
120 | log.Println("Reading prexisting pinstate file : " + STATE_FILE)
121 | dat, err := ioutil.ReadFile(STATE_FILE)
122 | if err != nil {
123 | log.Println("Failed to read state file : " + STATE_FILE + " : " + err.Error())
124 | return
125 | }
126 | err = json.Unmarshal(dat, &pinStates)
127 | if err != nil {
128 | log.Println("Failed to unmarshal json : " + err.Error())
129 | return
130 | }
131 | }
132 |
133 | gpio.Init(stateChanged, pinAdded, pinRemoved, pinStates)
134 |
135 | }
136 |
137 | func (g *GPIO) Init(pinStateChanged chan PinState, pinAdded chan PinState, pinRemoved chan string, states map[string]PinState) error {
138 | g.pinStateChanged = pinStateChanged
139 | g.pinRemoved = pinRemoved
140 | g.pinAdded = pinAdded
141 | g.pinStates = states
142 |
143 | // now init pins
144 | for key, pinState := range g.pinStates {
145 | if pinState.Name == "" {
146 | pinState.Name = pinState.PinId
147 | }
148 | g.PinInit(key, pinState.Dir, pinState.Pullup, pinState.Name)
149 | g.PinSet(key, pinState.State)
150 | }
151 | return nil
152 | }
153 |
154 | func (g *GPIO) Close() error {
155 | return nil
156 | }
157 | func (g *GPIO) PinMap() ([]PinDef, error) {
158 | // return a mock pinmap for this mock interface
159 | pinmap := []PinDef{
160 | {
161 | "P8_07",
162 | []string{"66", "GPIO_66", "TIMER4"},
163 | []string{"analog", "digital", "pwm"},
164 | 66,
165 | 0,
166 | }, {
167 | "P8_08",
168 | []string{"67", "GPIO_67", "TIMER7"},
169 | []string{"analog", "digital", "pwm"},
170 | 67,
171 | 0,
172 | }, {
173 | "P8_09",
174 | []string{"69", "GPIO_69", "TIMER5"},
175 | []string{"analog", "digital", "pwm"},
176 | 69,
177 | 0,
178 | }, {
179 | "P8_10",
180 | []string{"68", "GPIO_68", "TIMER6"},
181 | []string{"analog", "digital", "pwm"},
182 | 68,
183 | 0,
184 | }, {
185 | "P8_11",
186 | []string{"45", "GPIO_45"},
187 | []string{"analog", "digital", "pwm"},
188 | 45,
189 | 0,
190 | },
191 | }
192 | return pinmap, nil
193 | }
194 | func (g *GPIO) Host() (string, error) {
195 | return "fake", nil
196 | }
197 | func (g *GPIO) PinStates() (map[string]PinState, error) {
198 | return g.pinStates, nil
199 | }
200 | func (g *GPIO) PinInit(pinId string, dir Direction, pullup PullUp, name string) error {
201 | // add a pin
202 |
203 | // look up internal ID (we're going to assume its correct already)
204 |
205 | // make a pinstate object
206 | pinState := PinState{
207 | nil,
208 | pinId,
209 | dir,
210 | 0,
211 | pullup,
212 | name,
213 | }
214 |
215 | g.pinStates[pinId] = pinState
216 |
217 | g.pinAdded <- pinState
218 | return nil
219 | }
220 | func (g *GPIO) PinSet(pinId string, val byte) error {
221 | // change pin state
222 | if pin, ok := g.pinStates[pinId]; ok {
223 | // we have a value....
224 | pin.State = val
225 | g.pinStates[pinId] = pin
226 | // notify channel of new pinstate
227 | g.pinStateChanged <- pin
228 | }
229 | return nil
230 | }
231 | func (g *GPIO) PinRemove(pinId string) error {
232 | // remove a pin
233 | if _, ok := g.pinStates[pinId]; ok {
234 | // normally you would close the pin here
235 | delete(g.pinStates, pinId)
236 | g.pinRemoved <- pinId
237 | }
238 | return nil
239 | }
240 |
--------------------------------------------------------------------------------
/gpio_linux_arm.go:
--------------------------------------------------------------------------------
1 | // +build ignore
2 | // Ignore this file for now, but it would be nice to get GPIO going natively
3 |
4 | package main
5 |
6 | import (
7 | "encoding/json"
8 | "errors"
9 | "io/ioutil"
10 | "log"
11 | "os"
12 | "os/signal"
13 | "strconv"
14 |
15 | "github.com/kidoman/embd"
16 | _ "github.com/kidoman/embd/host/all"
17 | )
18 |
19 | var (
20 | gpio GPIO
21 | )
22 |
23 | type GPIO struct {
24 | pinStates map[string]PinState
25 | pinStateChanged chan PinState
26 | pinAdded chan PinState
27 | pinRemoved chan string
28 | }
29 |
30 | type Direction int
31 | type PullUp int
32 |
33 | type PinState struct {
34 | Pin interface{} `json:"-"`
35 | PinId string
36 | Dir Direction
37 | State byte
38 | Pullup PullUp
39 | Name string
40 | }
41 |
42 | type PinDef struct {
43 | ID string
44 | Aliases []string
45 | Capabilities []string
46 | DigitalLogical int
47 | AnalogLogical int
48 | }
49 |
50 | const STATE_FILE = "pinstates.json"
51 |
52 | const (
53 | In Direction = 0
54 | Out Direction = 1
55 | PWM Direction = 2
56 |
57 | Pull_None PullUp = 0
58 | Pull_Up PullUp = 1
59 | Pull_Down PullUp = 2
60 | )
61 |
62 | type GPIOInterface interface {
63 | PreInit()
64 | CleanupGpio()
65 | Init(chan PinState, chan PinState, chan string, map[string]PinState) error
66 | Close() error
67 | PinMap() ([]PinDef, error)
68 | Host() (string, error)
69 | PinStates() (map[string]PinState, error)
70 | PinInit(string, Direction, PullUp, string) error
71 | PinSet(string, byte) error
72 | PinRemove(string) error
73 | }
74 |
75 | func (g *GPIO) CleanupGpio() {
76 | pinStates, err := gpio.PinStates()
77 | if err != nil {
78 | log.Println("Error getting pinstates on cleanup: " + err.Error())
79 | } else {
80 | data, err := json.Marshal(pinStates)
81 | if err != nil {
82 | log.Println("Error marshalling pin states : " + err.Error())
83 | }
84 | ioutil.WriteFile(STATE_FILE, data, 0644)
85 | }
86 | gpio.Close()
87 | os.Exit(1)
88 | }
89 |
90 | // I took what Ben had in his main.go file and moved it here
91 | func (g *GPIO) PreInit() {
92 |
93 | c := make(chan os.Signal, 1)
94 | signal.Notify(c, os.Interrupt)
95 |
96 | go func() {
97 | for sig := range c {
98 | // sig is a ^C, handle it
99 | log.Printf("captured %v, cleaning up gpio and exiting..", sig)
100 | gpio.CleanupGpio()
101 | }
102 | }()
103 |
104 | stateChanged := make(chan PinState)
105 | pinRemoved := make(chan string)
106 | pinAdded := make(chan PinState)
107 | go func() {
108 | for {
109 | // start listening on stateChanged and pinRemoved channels and update hub as appropriate
110 | select {
111 | case pinState := <-stateChanged:
112 | go h.sendMsg("PinState", pinState)
113 | case pinName := <-pinRemoved:
114 | go h.sendMsg("PinRemoved", pinName)
115 | case pinState := <-pinAdded:
116 | go h.sendMsg("PinAdded", pinState)
117 | }
118 | }
119 | }()
120 |
121 | pinStates := make(map[string]PinState)
122 |
123 | // read existing pin states
124 | if _, err := os.Stat(STATE_FILE); err == nil {
125 | log.Println("Reading prexisting pinstate file : " + STATE_FILE)
126 | dat, err := ioutil.ReadFile(STATE_FILE)
127 | if err != nil {
128 | log.Println("Failed to read state file : " + STATE_FILE + " : " + err.Error())
129 | return
130 | }
131 | err = json.Unmarshal(dat, &pinStates)
132 | if err != nil {
133 | log.Println("Failed to unmarshal json : " + err.Error())
134 | return
135 | }
136 | }
137 |
138 | gpio.Init(stateChanged, pinAdded, pinRemoved, pinStates)
139 |
140 | }
141 |
142 | func (g *GPIO) Init(pinStateChanged chan PinState, pinAdded chan PinState, pinRemoved chan string, states map[string]PinState) error {
143 | g.pinStateChanged = pinStateChanged
144 | g.pinRemoved = pinRemoved
145 | g.pinAdded = pinAdded
146 | g.pinStates = states
147 |
148 | // if its a raspberry pi initialize pi-blaster too
149 | host, _, err := embd.DetectHost()
150 | if err != nil {
151 | return err
152 | }
153 | if host == embd.HostRPi {
154 | InitBlaster()
155 | }
156 |
157 | err = embd.InitGPIO()
158 | if err != nil {
159 | return err
160 | }
161 | // now init pins
162 | for key, pinState := range g.pinStates {
163 | if pinState.Name == "" {
164 | pinState.Name = pinState.PinId
165 | }
166 | g.PinInit(key, pinState.Dir, pinState.Pullup, pinState.Name)
167 | g.PinSet(key, pinState.State)
168 | }
169 | return nil
170 | }
171 |
172 | func (g *GPIO) Close() error {
173 | // close all the pins we have open if any
174 | for _, pinState := range g.pinStates {
175 | if pinState.Pin != nil {
176 | switch pinObj := pinState.Pin.(type) {
177 | case embd.DigitalPin:
178 | pinObj.Close()
179 | case embd.PWMPin:
180 | pinObj.Close()
181 | case BlasterPin:
182 | pinObj.Close()
183 | }
184 | }
185 | }
186 |
187 | // if its a raspberry pi close pi-blaster too
188 | host, _, err := embd.DetectHost()
189 | if err != nil {
190 | return err
191 | }
192 | if host == embd.HostRPi {
193 | CloseBlaster()
194 | }
195 |
196 | err = embd.CloseGPIO()
197 | if err != nil {
198 | return err
199 | }
200 | return nil
201 | }
202 | func (g *GPIO) PinMap() ([]PinDef, error) {
203 | desc, err := embd.DescribeHost()
204 | if err != nil {
205 | return nil, err
206 | }
207 |
208 | // wrap pinmap in a struct to make the json easier to parse on the other end
209 | embdMap := desc.GPIODriver().PinMap()
210 | pinMap := make([]PinDef, len(embdMap))
211 | // convert to PinDef format
212 | for i := 0; i < len(embdMap); i++ {
213 | pinDesc := embdMap[i]
214 | caps := make([]string, 0)
215 |
216 | if pinDesc.Caps&embd.CapDigital != 0 {
217 | caps = append(caps, "Digital")
218 | }
219 | if pinDesc.Caps&embd.CapAnalog != 0 {
220 | caps = append(caps, "Analog")
221 | }
222 | if pinDesc.Caps&embd.CapPWM != 0 {
223 | caps = append(caps, "PWM")
224 | }
225 | if pinDesc.Caps&embd.CapI2C != 0 {
226 | caps = append(caps, "I2C")
227 | }
228 | if pinDesc.Caps&embd.CapUART != 0 {
229 | caps = append(caps, "UART")
230 | }
231 | if pinDesc.Caps&embd.CapSPI != 0 {
232 | caps = append(caps, "SPI")
233 | }
234 | if pinDesc.Caps&embd.CapGPMC != 0 {
235 | caps = append(caps, "GPMC")
236 | }
237 | if pinDesc.Caps&embd.CapLCD != 0 {
238 | caps = append(caps, "LCD")
239 | }
240 |
241 | pinMap[i] = PinDef{
242 | pinDesc.ID,
243 | pinDesc.Aliases,
244 | caps,
245 | pinDesc.DigitalLogical,
246 | pinDesc.AnalogLogical,
247 | }
248 | }
249 | return pinMap, nil
250 | }
251 | func (g *GPIO) Host() (string, error) {
252 | host, _, err := embd.DetectHost()
253 | if err != nil {
254 | return "", err
255 | }
256 | return string(host), nil
257 | }
258 | func (g *GPIO) PinStates() (map[string]PinState, error) {
259 | return g.pinStates, nil
260 | }
261 | func (g *GPIO) PinInit(pinId string, dir Direction, pullup PullUp, name string) error {
262 | var pin interface{}
263 | state := byte(0)
264 |
265 | if dir == PWM {
266 |
267 | host, _, err := embd.DetectHost()
268 | if err != nil {
269 | return err
270 | }
271 | if host == embd.HostRPi {
272 | // use pi blaster pin
273 | log.Println("Creating PWM pin on Pi")
274 |
275 | // get the host descriptor
276 | desc, err := embd.DescribeHost()
277 | if err != nil {
278 | return err
279 | }
280 | // get the pinmap
281 | embdMap := desc.GPIODriver().PinMap()
282 | // lookup the pinId in the map
283 | var pinDesc *embd.PinDesc
284 | for i := range embdMap {
285 | pd := embdMap[i]
286 |
287 | if pd.ID == pinId {
288 | pinDesc = pd
289 | break
290 | }
291 |
292 | for j := range pd.Aliases {
293 | if pd.Aliases[j] == pinId {
294 | pinDesc = pd
295 | break
296 | }
297 | }
298 | }
299 | if pinDesc != nil {
300 | // we found a pin with that name....what is its first Alias?
301 | pinIdInt, err := strconv.Atoi(pinDesc.Aliases[0])
302 | if err != nil {
303 | log.Println("Failed to parse int from alias : ", pinDesc.Aliases[0])
304 | return err
305 | }
306 | p := NewBlasterPin(pinIdInt)
307 | pin = p
308 | } else {
309 | log.Println("Failed to find Pin ", pinId)
310 | return errors.New("Failed to find pin " + pinId)
311 | }
312 | } else {
313 | // bbb, so use embd since pwm pins work there
314 | p, err := embd.NewPWMPin(pinId)
315 | if err != nil {
316 | log.Println("Failed to create PWM Pin using key ", pinId, " : ", err.Error())
317 | return err
318 | }
319 | pin = p
320 | }
321 | } else {
322 | // add a pin
323 | p, err := embd.NewDigitalPin(pinId)
324 | if err != nil {
325 | return err
326 | }
327 | pin = p
328 |
329 | err = p.SetDirection(embd.Direction(dir))
330 | if err != nil {
331 | return err
332 | }
333 |
334 | if pullup == Pull_Up {
335 | err = p.PullUp()
336 |
337 | // pullup and down not implemented on rpi host so we need to manually set initial states
338 | // not ideal as a pullup really isn't the same thing but it works for most use cases
339 |
340 | if err != nil {
341 | log.Println("Failed to set pullup on " + pinId + " setting high state instead : " + err.Error())
342 | // we failed to set pullup, so lets set initial state high instead
343 | err = p.Write(1)
344 | state = 1
345 | if err != nil {
346 | return err
347 | }
348 | }
349 | } else if pullup == Pull_Down {
350 | err = p.PullDown()
351 |
352 | if err != nil {
353 |
354 | log.Println("Failed to set pulldown on " + pinId + " setting low state instead : " + err.Error())
355 |
356 | err = p.Write(0)
357 | state = 1
358 | if err != nil {
359 | return err
360 | }
361 | }
362 | }
363 | }
364 |
365 | // test to see if we already have a state for this pin
366 | existingPin, exists := g.pinStates[pinId]
367 | if exists {
368 | existingPin.Pin = pin
369 | existingPin.Name = name
370 | existingPin.Dir = dir
371 | existingPin.State = state
372 | existingPin.Pullup = pullup
373 | g.pinStates[pinId] = existingPin
374 |
375 | g.pinStateChanged <- existingPin
376 | g.pinRemoved <- pinId
377 | g.pinAdded <- g.pinStates[pinId]
378 | } else {
379 | g.pinStates[pinId] = PinState{pin, pinId, dir, state, pullup, name}
380 | g.pinAdded <- g.pinStates[pinId]
381 | }
382 |
383 | return nil
384 | }
385 | func (g *GPIO) PinSet(pinId string, val byte) error {
386 | // change pin state
387 | if pin, ok := g.pinStates[pinId]; ok {
388 | // we have a value....
389 | switch pinObj := pin.Pin.(type) {
390 | case embd.DigitalPin:
391 | err := pinObj.Write(int(val))
392 | if err != nil {
393 | return err
394 | }
395 | case embd.PWMPin:
396 | if err := pinObj.SetAnalog(val); err != nil {
397 | return err
398 | }
399 | case BlasterPin:
400 | err := pinObj.Write(val)
401 | if err != nil {
402 | return err
403 | }
404 | }
405 | pin.State = val
406 | g.pinStates[pinId] = pin
407 | // notify channel of new pinstate
408 | g.pinStateChanged <- pin
409 | }
410 | return nil
411 | }
412 | func (g *GPIO) PinRemove(pinId string) error {
413 | // remove a pin
414 | if pin, ok := g.pinStates[pinId]; ok {
415 | var err error
416 | switch pinObj := pin.Pin.(type) {
417 | case embd.DigitalPin:
418 | err = pinObj.Close()
419 | if err != nil {
420 | return err
421 | }
422 | case embd.PWMPin:
423 | err = pinObj.Close()
424 | if err != nil {
425 | return err
426 | }
427 | case BlasterPin:
428 | err = pinObj.Close()
429 | if err != nil {
430 | return err
431 | }
432 | }
433 | delete(g.pinStates, pinId)
434 | g.pinRemoved <- pinId
435 | }
436 | return nil
437 | }
438 |
439 | type BlasterPin struct {
440 | id int
441 | value float64
442 | }
443 |
444 | func InitBlaster() error {
445 | // check the file actually exists, throw error if not so Pi can bail out
446 | if _, err := os.Stat("/dev/pi-blaster"); os.IsNotExist(err) {
447 | return errors.New("/dev/pi-blaster does not exists, is pi-blaster correctly installed?")
448 | }
449 | return nil
450 | }
451 | func CloseBlaster() error {
452 | return nil
453 | }
454 | func NewBlasterPin(pinId int) BlasterPin {
455 | log.Println("Creating pi blaster pin on ", string(pinId))
456 | return BlasterPin{
457 | pinId,
458 | 0.0,
459 | }
460 | }
461 | func (b *BlasterPin) Close() error {
462 | f, err := os.Create("/dev/pi-blaster")
463 | if err != nil {
464 | return err
465 | }
466 | defer f.Close()
467 | _, err = f.WriteString("release " + strconv.Itoa(b.id))
468 | if err != nil {
469 | return err
470 | }
471 | f.Sync()
472 | return nil
473 | }
474 | func (b *BlasterPin) Write(value byte) error {
475 | f, err := os.Create("/dev/pi-blaster")
476 | if err != nil {
477 | return err
478 | }
479 | defer f.Close()
480 |
481 | v := (float64(value) / 255.0)
482 | if v > 1.0 {
483 | v = 1.0
484 | } else if v < 0.0 {
485 | v = 0.0
486 | }
487 | toVal := strconv.FormatFloat(v, 'f', 2, 64)
488 | msg := strconv.Itoa(b.id) + "=" + string(toVal)
489 | _, err = f.WriteString(msg + "\n")
490 | if err != nil {
491 | log.Println("PiBlaster: Failed to write :", err.Error())
492 | return err
493 | }
494 | b.value = v
495 | f.Sync()
496 | return nil
497 | }
498 |
--------------------------------------------------------------------------------
/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Serial Port Example
4 |
5 |
46 |
83 |
84 |
85 |
86 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/hub.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/kardianos/osext"
8 | //"os"
9 | "os/exec"
10 | //"path"
11 | //"path/filepath"
12 | //"runtime"
13 | //"debug"
14 | "encoding/json"
15 | "runtime"
16 | "runtime/debug"
17 | "strconv"
18 | "strings"
19 | )
20 |
21 | type hub struct {
22 | // Registered connections.
23 | connections map[*connection]bool
24 |
25 | // Inbound messages from the connections.
26 | broadcast chan []byte
27 |
28 | // Inbound messages from the system
29 | broadcastSys chan []byte
30 |
31 | // Register requests from the connections.
32 | register chan *connection
33 |
34 | // Unregister requests from connections.
35 | unregister chan *connection
36 | }
37 |
38 | var h = hub{
39 | // buffered. go with 1000 cuz should never surpass that
40 | broadcast: make(chan []byte, 1000),
41 | broadcastSys: make(chan []byte, 1000),
42 | // non-buffered
43 | //broadcast: make(chan []byte),
44 | //broadcastSys: make(chan []byte),
45 | register: make(chan *connection),
46 | unregister: make(chan *connection),
47 | connections: make(map[*connection]bool),
48 | }
49 |
50 | func (h *hub) run() {
51 | for {
52 | select {
53 | case c := <-h.register:
54 | h.connections[c] = true
55 | // send supported commands
56 | c.send <- []byte("{\"Version\" : \"" + version + "\"} ")
57 | c.send <- []byte("{\"Commands\" : [\"list\", \"open [portName] [baud] [bufferAlgorithm (optional)]\", \"send [portName] [cmd]\", \"sendnobuf [portName] [cmd]\", \"sendjson {P:portName, Data:[{D:cmdStr, Id:idStr}]}\", \"close [portName]\", \"bufferalgorithms\", \"baudrates\", \"restart\", \"exit\", \"broadcast [anythingToRegurgitate]\", \"hostname\", \"version\", \"program [portName] [core:architecture:name] [path/to/binOrHexFile]\", \"programfromurl [portName] [core:architecture:name] [urlToBinOrHexFile]\", \"execruntime\", \"exec [command] [arg1] [arg2] [...]\"]} ")
58 | c.send <- []byte("{\"Hostname\" : \"" + *hostname + "\"} ")
59 | case c := <-h.unregister:
60 | delete(h.connections, c)
61 | // put close in func cuz it was creating panics and want
62 | // to isolate
63 | func() {
64 | // this method can panic if websocket gets disconnected
65 | // from users browser and we see we need to unregister a couple
66 | // of times, i.e. perhaps from incoming data from serial triggering
67 | // an unregister. (NOT 100% sure why seeing c.send be closed twice here)
68 | defer func() {
69 | if e := recover(); e != nil {
70 | log.Println("Got panic: ", e)
71 | }
72 | }()
73 | close(c.send)
74 | }()
75 | case m := <-h.broadcast:
76 | //log.Print("Got a broadcast")
77 | //log.Print(m)
78 | //log.Print(len(m))
79 | if len(m) > 0 {
80 | //log.Print(string(m))
81 | //log.Print(h.broadcast)
82 | checkCmd(m)
83 | //log.Print("-----")
84 |
85 | for c := range h.connections {
86 | select {
87 | case c.send <- m:
88 | //log.Print("did broadcast to ")
89 | //log.Print(c.ws.RemoteAddr())
90 | //c.send <- []byte("hello world")
91 | default:
92 | delete(h.connections, c)
93 | close(c.send)
94 | go c.ws.Close()
95 | }
96 | }
97 | }
98 | case m := <-h.broadcastSys:
99 | //log.Printf("Got a system broadcast: %v\n", string(m))
100 | //log.Print(string(m))
101 | //log.Print("-----")
102 |
103 | for c := range h.connections {
104 | select {
105 | case c.send <- m:
106 | //log.Print("did broadcast to ")
107 | //log.Print(c.ws.RemoteAddr())
108 | //c.send <- []byte("hello world")
109 | default:
110 | delete(h.connections, c)
111 | close(c.send)
112 | go c.ws.Close()
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
119 | func checkCmd(m []byte) {
120 | //log.Print("Inside checkCmd")
121 | s := string(m[:])
122 | log.Print(s)
123 |
124 | sl := strings.ToLower(s)
125 |
126 | if strings.HasPrefix(sl, "open") {
127 |
128 | // check if user wants to open this port as a secondary port
129 | // this doesn't mean much other than allowing the UI to show
130 | // a port as primary and make other ports sort of act less important
131 | isSecondary := false
132 | if strings.HasPrefix(s, "open secondary") {
133 | isSecondary = true
134 | // swap out the word secondary
135 | s = strings.Replace(s, "open secondary", "open", 1)
136 | }
137 |
138 | // remove newline
139 | args := strings.Split(strings.TrimSpace(s), " ")
140 | if len(args) < 3 {
141 | go spErr("You did not specify a port and baud rate in your open cmd")
142 | return
143 | }
144 | if len(args[1]) < 1 {
145 | go spErr("You did not specify a serial port")
146 | return
147 | }
148 |
149 | baudStr := strings.Replace(args[2], "\n", "", -1)
150 | baud, err := strconv.Atoi(baudStr)
151 | if err != nil {
152 | go spErr("Problem converting baud rate " + args[2])
153 | return
154 | }
155 | // pass in buffer type now as string. if user does not
156 | // ask for a buffer type pass in empty string
157 | bufferAlgorithm := ""
158 | if len(args) > 3 {
159 | // cool. we got a buffer type request
160 | buftype := strings.Replace(args[3], "\n", "", -1)
161 | bufferAlgorithm = buftype
162 | }
163 | go spHandlerOpen(args[1], baud, bufferAlgorithm, isSecondary)
164 |
165 | } else if strings.HasPrefix(sl, "close") {
166 |
167 | log.Printf("About to split close commands. cmd:\"%v\"", s)
168 | // remove newline
169 | args := strings.Split(strings.TrimSpace(s), " ")
170 | //args := strings.Split(s, " ")
171 | log.Printf("The split args for close:%v", args)
172 | if len(args) > 1 {
173 | go spClose(args[1])
174 | } else {
175 | go spErr("You did not specify a port to close")
176 | }
177 |
178 | } else if strings.HasPrefix(sl, "programkill") {
179 |
180 | // kill the running process (assumes singleton for now)
181 | go spHandlerProgramKill()
182 |
183 | } else if strings.HasPrefix(sl, "programfromurl") {
184 |
185 | args := strings.Split(s, " ")
186 | if len(args) == 4 {
187 | go spProgramFromUrl(args[1], args[2], args[3])
188 | } else {
189 | go spErr("You did not specify a port, a board to program and/or a URL")
190 | }
191 |
192 | } else if strings.HasPrefix(sl, "program") {
193 |
194 | args := strings.Split(s, " ")
195 | if len(args) > 3 {
196 | var slice []string = args[3:len(args)]
197 | go spProgram(args[1], args[2], strings.Join(slice, " "))
198 | } else {
199 | go spErr("You did not specify a port, a board to program and/or a filename")
200 | }
201 |
202 | } else if strings.HasPrefix(sl, "sendjson") {
203 | // will catch sendjson
204 |
205 | go spWriteJson(s)
206 |
207 | } else if strings.HasPrefix(sl, "send") {
208 | // will catch send and sendnobuf
209 |
210 | //args := strings.Split(s, "send ")
211 | go spWrite(s)
212 |
213 | } else if strings.HasPrefix(sl, "list") {
214 | go spList()
215 | //go getListViaWmiPnpEntity()
216 | } else if strings.HasPrefix(sl, "fro") {
217 | // User is wanting us to tweak the feedrate on-the-fly
218 | go spFeedRateOverride(s)
219 |
220 | } else if strings.HasPrefix(sl, "bufferalgorithm") {
221 | go spBufferAlgorithms()
222 | } else if strings.HasPrefix(sl, "baudrate") {
223 | go spBaudRates()
224 | } else if strings.HasPrefix(sl, "broadcast") {
225 | go broadcast(s)
226 | } else if strings.HasPrefix(sl, "restart") {
227 | restart()
228 | } else if strings.HasPrefix(sl, "exit") {
229 | exit()
230 | } else if strings.HasPrefix(sl, "memstats") {
231 | memoryStats()
232 | } else if strings.HasPrefix(sl, "gc") {
233 | garbageCollection()
234 | } else if strings.HasPrefix(sl, "bufflowdebug") {
235 | bufflowdebug(sl)
236 | } else if strings.HasPrefix(sl, "hostname") {
237 | getHostname()
238 | } else if strings.HasPrefix(sl, "version") {
239 | getVersion()
240 | } else if strings.HasPrefix(sl, "execruntime") {
241 | execRuntime()
242 | } else if strings.HasPrefix(sl, "exec") {
243 | go execRun(s)
244 | } else if strings.HasPrefix(sl, "cayenn-sendudp") {
245 | cayennSendUdp(s)
246 | } else if strings.HasPrefix(sl, "cayenn-sendtcp") {
247 | cayennSendTcp(s)
248 | } else if strings.HasPrefix(sl, "usblist") {
249 | SendUsbList()
250 | /*
251 | } else if strings.HasPrefix(sl, "gethost") {
252 | hostname, err := gpio.Host()
253 | if err != nil {
254 | go h.sendErr(err.Error())
255 | }
256 | go h.sendMsg("Host", hostname)
257 |
258 | } else if strings.HasPrefix(sl, "getpinmap") {
259 | pinMap, err := gpio.PinMap()
260 | if err != nil {
261 | go h.sendErr(err.Error())
262 | }
263 | go h.sendMsg("PinMap", pinMap)
264 | } else if strings.HasPrefix(sl, "getpinstates") {
265 | pinStates, err := gpio.PinStates()
266 | if err != nil {
267 | go h.sendErr(err.Error())
268 | }
269 | go h.sendMsg("PinStates", pinStates)
270 |
271 | } else if strings.HasPrefix(sl, "initpin") {
272 | // format : setpin pinId dir pullup
273 | args := strings.Split(s, " ")
274 | if len(args) < 4 {
275 | go h.sendErr("You did not specify a pin and a direction [0|1|low|high] and a name")
276 | return
277 | }
278 | if len(args[1]) < 1 {
279 | go h.sendErr("You did not specify a pin")
280 | return
281 | }
282 | pin := args[1]
283 | dirStr := args[2]
284 | name := args[4]
285 | dir := In
286 | switch {
287 | case dirStr == "1" || dirStr == "out" || dirStr == "output":
288 | dir = Out
289 | case dirStr == "0" || dirStr == "in" || dirStr == "input":
290 | dir = In
291 | case dirStr == "pwm":
292 | dir = PWM
293 | }
294 | pullup := Pull_None
295 | switch {
296 | case args[3] == "1" || args[3] == "up":
297 | pullup = Pull_Up
298 | case args[3] == "0" || args[3] == "down":
299 | pullup = Pull_Down
300 | }
301 | err := gpio.PinInit(pin, dir, pullup, name)
302 | if err != nil {
303 | go h.sendErr(err.Error())
304 | }
305 | } else if strings.HasPrefix(sl, "removepin") {
306 | // format : removepin pinId
307 | args := strings.Split(s, " ")
308 | if len(args) < 2 {
309 | go h.sendErr("You did not specify a pin id")
310 | return
311 | }
312 | err := gpio.PinRemove(args[1])
313 | if err != nil {
314 | go h.sendErr(err.Error())
315 | }
316 | } else if strings.HasPrefix(sl, "setpin") {
317 | // format : setpin pinId high/low/1/0
318 | args := strings.Split(s, " ")
319 | if len(args) < 3 {
320 | go h.sendErr("You did not specify a pin and a state [0|1|low|high]")
321 | return
322 | }
323 | if len(args[1]) < 1 {
324 | go h.sendErr("You did not specify a pin")
325 | return
326 | }
327 | pin := args[1]
328 | stateStr := args[2]
329 | state := 0
330 | switch {
331 | case stateStr == "1" || stateStr == "high":
332 | state = 1
333 | case stateStr == "0" || stateStr == "low":
334 | state = 0
335 | default:
336 | // assume its a pwm value...if it converts to integer in 0-255 range
337 | s, err := strconv.Atoi(stateStr)
338 | if err != nil {
339 | go h.sendErr("Invalid value, must be between 0 and 255 : " + stateStr)
340 | return
341 | }
342 | if s < 0 || s > 255 {
343 | go h.sendErr("Invalid value, must be between 0 and 255 : " + stateStr)
344 | return
345 | }
346 | state = s
347 | }
348 |
349 | err := gpio.PinSet(pin, byte(state))
350 | if err != nil {
351 | go h.sendErr(err.Error())
352 | }
353 | */
354 | } else {
355 | go spErr("Could not understand command.")
356 | }
357 |
358 | //log.Print("Done with checkCmd")
359 | }
360 |
361 | func bufflowdebug(sl string) {
362 | log.Println("bufflowdebug start")
363 | if strings.HasPrefix(sl, "bufflowdebug on") {
364 | *bufFlowDebugType = "on"
365 | } else if strings.HasPrefix(sl, "bufflowdebug off") {
366 | *bufFlowDebugType = "off"
367 | }
368 | h.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *bufFlowDebugType + "\"}")
369 | log.Println("bufflowdebug end")
370 | }
371 |
372 | func memoryStats() {
373 | var memStats runtime.MemStats
374 | runtime.ReadMemStats(&memStats)
375 | json, _ := json.Marshal(memStats)
376 | log.Printf("memStats:%v\n", string(json))
377 | h.broadcastSys <- json
378 | }
379 |
380 | func getHostname() {
381 | h.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}")
382 | }
383 |
384 | func getVersion() {
385 | h.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}")
386 | }
387 |
388 | func garbageCollection() {
389 | log.Printf("Starting garbageCollection()\n")
390 | h.broadcastSys <- []byte("{\"gc\":\"starting\"}")
391 | memoryStats()
392 | debug.SetGCPercent(100)
393 | debug.FreeOSMemory()
394 | debug.SetGCPercent(-1)
395 | log.Printf("Done with garbageCollection()\n")
396 | h.broadcastSys <- []byte("{\"gc\":\"done\"}")
397 | memoryStats()
398 | }
399 |
400 | func exit() {
401 | log.Println("Starting new spjs process")
402 | h.broadcastSys <- []byte("{\"Exiting\" : true}")
403 | log.Fatal("Exited current spjs cuz asked to")
404 |
405 | }
406 |
407 | func restart() {
408 | // relaunch ourself and exit
409 | // the relaunch works because we pass a cmdline in
410 | // that has serial-port-json-server only initialize 5 seconds later
411 | // which gives us time to exit and unbind from serial ports and TCP/IP
412 | // sockets like :8989
413 | log.Println("Starting new spjs process")
414 | h.broadcastSys <- []byte("{\"Restarting\" : true}")
415 |
416 | // figure out current path of executable so we know how to restart
417 | // this process
418 | /*
419 | dir, err2 := filepath.Abs(filepath.Dir(os.Args[0]))
420 | if err2 != nil {
421 | //log.Fatal(err2)
422 | fmt.Printf("Error getting executable file path. err: %v\n", err2)
423 | }
424 | fmt.Printf("The path to this exe is: %v\n", dir)
425 |
426 | // alternate approach
427 | _, filename, _, _ := runtime.Caller(1)
428 | f, _ := os.Open(path.Join(path.Dir(filename), "serial-port-json-server"))
429 | fmt.Println(f)
430 | */
431 |
432 | // using osext
433 | exePath, err3 := osext.Executable()
434 | if err3 != nil {
435 | fmt.Printf("Error getting exe path using osext lib. err: %v\n", err3)
436 | }
437 | fmt.Printf("exePath using osext: %v\n", exePath)
438 |
439 | // figure out garbageCollection flag
440 | //isGcFlag := "false"
441 |
442 | var cmd *exec.Cmd
443 | /*if *isGC {
444 | //isGcFlag = "true"
445 | cmd = exec.Command(exePath, "-ls", "-addr", *addr, "-regex", *regExpFilter, "-gc")
446 | } else {
447 | cmd = exec.Command(exePath, "-ls", "-addr", *addr, "-regex", *regExpFilter)
448 |
449 | }*/
450 | cmd = exec.Command(exePath, "-ls", "-addr", *addr, "-regex", *regExpFilter, "-gc", *gcType)
451 |
452 | //cmd := exec.Command("./serial-port-json-server", "ls")
453 | err := cmd.Start()
454 | if err != nil {
455 | log.Printf("Got err restarting spjs: %v\n", err)
456 | h.broadcastSys <- []byte("{\"Error\" : \"" + fmt.Sprintf("%v", err) + "\"}")
457 | } else {
458 | h.broadcastSys <- []byte("{\"Restarted\" : true}")
459 | }
460 | log.Fatal("Exited current spjs for restart")
461 | //log.Printf("Waiting for command to finish...")
462 | //err = cmd.Wait()
463 | //log.Printf("Command finished with error: %v", err)
464 | }
465 |
466 | type CmdBroadcast struct {
467 | Cmd string
468 | Msg string
469 | }
470 |
471 | func broadcast(arg string) {
472 | // we will get a string of broadcast asdf asdf asdf
473 | log.Println("Inside broadcast arg: " + arg)
474 | arg = strings.TrimPrefix(arg, " ")
475 | //log.Println("arg after trim: " + arg)
476 | args := strings.SplitN(arg, " ", 2)
477 | if len(args) != 2 {
478 | errstr := "Could not parse broadcast command: " + arg
479 | log.Println(errstr)
480 | spErr(errstr)
481 | return
482 | }
483 | broadcastcmd := strings.Trim(args[1], " ")
484 | log.Println("The broadcast cmd is:" + broadcastcmd + "---")
485 |
486 | bcmd := CmdBroadcast{
487 | Cmd: "Broadcast",
488 | Msg: broadcastcmd,
489 | }
490 | json, _ := json.Marshal(bcmd)
491 | log.Printf("bcmd:%v\n", string(json))
492 | h.broadcastSys <- json
493 |
494 | }
495 |
496 | func (h *hub) sendErr(msg string) {
497 | msgMap := map[string]string{"error": msg}
498 | log.Println("Error: " + msg)
499 | bytes, err := json.Marshal(msgMap)
500 | if err != nil {
501 | log.Println("Failed to marshal data!")
502 | return
503 | }
504 | h.broadcastSys <- bytes
505 | }
506 |
507 | func (h *hub) sendMsg(name string, msg interface{}) {
508 | msgMap := make(map[string]interface{})
509 | msgMap[name] = msg
510 | msgMap["Type"] = name
511 | //log.Println("Sent: " + name)
512 | bytes, err := json.Marshal(msgMap)
513 | if err != nil {
514 | log.Println("Failed to marshal data!")
515 | return
516 | }
517 | h.broadcastSys <- bytes
518 | }
519 |
--------------------------------------------------------------------------------
/initd_script.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "os"
7 | "os/exec"
8 | )
9 |
10 | func check(e error) {
11 | if e != nil {
12 | panic(e)
13 | }
14 | }
15 |
16 | func createStartupScript() {
17 |
18 | log.Println("Creating startup script")
19 | // exeName := os.Args[0]
20 | exeName, err := os.Executable()
21 | if err != nil {
22 | log.Println("Got error trying to find executable name. Err:", err)
23 | }
24 | log.Println("exeName", exeName)
25 | script := `#! /bin/sh
26 | ### BEGIN INIT INFO
27 | # Provides: serial-port-json-server
28 | # Required-Start: $all
29 | # Required-Stop:
30 | # Default-Start: 2 3 4 5
31 | # Default-Stop: 0 1 6
32 | # Short-Description: Manage my cool stuff
33 | ### END INIT INFO
34 |
35 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin
36 |
37 | . /lib/init/vars.sh
38 | . /lib/lsb/init-functions
39 | # If you need to source some other scripts, do it here
40 |
41 | case "$1" in
42 | start)
43 | log_begin_msg "Starting Serial Port JSON Server service"
44 | # do something
45 | ` + exeName + ` &
46 | log_end_msg $?
47 | exit 0
48 | ;;
49 | stop)
50 | log_begin_msg "Stopping the Serial Port JSON Server"
51 |
52 | # do something to kill the service or cleanup or nothing
53 | killall serial-port-json-server
54 | log_end_msg $?
55 | exit 0
56 | ;;
57 | *)
58 | echo "Usage: /etc/init.d/serial-port-json-server {start|stop}"
59 | exit 1
60 | ;;
61 | esac
62 | `
63 | log.Println(script)
64 |
65 | d1 := []byte(script)
66 | err2 := ioutil.WriteFile("/etc/init.d/serial-port-json-server", d1, 0755)
67 | check(err2)
68 |
69 | // install it
70 | // sudo update-rc.d serial-port-json-server defaults
71 | cmd := exec.Command("update-rc.d", "serial-port-json-server", "defaults")
72 | err3 := cmd.Start()
73 | if err3 != nil {
74 | log.Fatal(err3)
75 | }
76 | log.Printf("Waiting for command to finish...")
77 | err4 := cmd.Wait()
78 | if err4 != nil {
79 | log.Printf("Command finished with error: %v", err4)
80 | } else {
81 | log.Printf("Successfully created your startup script in /etc/init.d")
82 | log.Printf("You can now run /etc/init.d/serial-port-json-server start and this will run automatically on startup")
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Version 1.95
2 | // Supports Windows, Linux, Mac, and Raspberry Pi, Beagle Bone Black
3 |
4 | package main
5 |
6 | import (
7 | "flag"
8 | "go/build"
9 | "log"
10 | "net/http"
11 | //"path/filepath"
12 | "errors"
13 | "fmt"
14 | "net"
15 | "os"
16 | //"net/http/pprof"
17 | //"runtime"
18 | "io"
19 | "runtime/debug"
20 | "text/template"
21 | "time"
22 | )
23 |
24 | var (
25 | version = "1.96"
26 | versionFloat = float32(1.96)
27 | addr = flag.String("addr", ":8989", "http service address. example :8800 to run on port 8800, example 10.0.0.2:9000 to run on specific IP address and port, example 10.0.0.2 to run on specific IP address")
28 | // addr = flag.String("addr", ":8980", "http service address. example :8800 to run on port 8800, example 10.0.0.2:9000 to run on specific IP address and port, example 10.0.0.2 to run on specific IP address")
29 | saddr = flag.String("saddr", ":8990", "https service address. example :8801 to run https on port 8801")
30 | scert = flag.String("scert", "cert.pem", "https certificate file")
31 | skey = flag.String("skey", "key.pem", "https key file")
32 | //assets = flag.String("assets", defaultAssetPath(), "path to assets")
33 | // verbose = flag.Bool("v", true, "show debug logging")
34 | verbose = flag.Bool("v", false, "show debug logging")
35 | //homeTempl *template.Template
36 | isLaunchSelf = flag.Bool("ls", false, "Launch self 5 seconds later. This flag is used when you ask for a restart from a websocket client.")
37 | isAllowExec = flag.Bool("allowexec", false, "Allow terminal commands to be executed (default false)")
38 |
39 | // regular expression to sort the serial port list
40 | // typically this wouldn't be provided, but if the user wants to clean
41 | // up their list with a regexp so it's cleaner inside their end-user interface
42 | // such as ChiliPeppr, this can make the massive list that Linux gives back
43 | // to you be a bit more manageable
44 | regExpFilter = flag.String("regex", "", "Regular expression to filter serial port list, i.e. -regex usb|acm")
45 |
46 | // allow garbageCollection()
47 | //isGC = flag.Bool("gc", false, "Is garbage collection on? Off by default.")
48 | //isGC = flag.Bool("gc", true, "Is garbage collection on? Off by default.")
49 | gcType = flag.String("gc", "std", "Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage)")
50 |
51 | // whether to do buffer flow debugging
52 | bufFlowDebugType = flag.String("bufflowdebug", "off", "off = (default) We do not send back any debug JSON, on = We will send back a JSON response with debug info based on the configuration of the buffer flow that the user picked")
53 |
54 | // hostname. allow user to override, otherwise we look it up
55 | hostname = flag.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
56 |
57 | // turn off cayenn
58 | isDisableCayenn = flag.Bool("disablecayenn", false, "Disable loading of Cayenn TCP/UDP server on port 8988")
59 | // isLoadCayenn = flag.Bool("allowcayenn", false, "Allow loading of Cayenn TCP/UDP server on port 8988")
60 |
61 | createScript = flag.Bool("createstartupscript", false, "Create an /etc/init.d/serial-port-json-server startup script. Available only on Linux.")
62 |
63 | // createScript = flag.Bool("createstartupscript", true, "Create an /etc/init.d/serial-port-json-server startup script. Available only on Linux.")
64 | )
65 |
66 | type NullWriter int
67 |
68 | func (NullWriter) Write([]byte) (int, error) { return 0, nil }
69 |
70 | func defaultAssetPath() string {
71 | //p, err := build.Default.Import("gary.burd.info/go-websocket-chat", "", build.FindOnly)
72 | p, err := build.Default.Import("github.com/johnlauer/serial-port-json-server", "", build.FindOnly)
73 | if err != nil {
74 | return "."
75 | }
76 | return p.Dir
77 | }
78 |
79 | func homeHandler(c http.ResponseWriter, req *http.Request) {
80 | homeTemplate.Execute(c, req.Host)
81 | }
82 |
83 | func launchSelfLater() {
84 | log.Println("Going to launch myself 5 seconds later.")
85 | time.Sleep(2 * 1000 * time.Millisecond)
86 | log.Println("Done waiting 5 secs. Now launching...")
87 | }
88 |
89 | func main() {
90 |
91 | // Test USB list
92 | // GetUsbList()
93 |
94 | // parse all passed in command line arguments
95 | flag.Parse()
96 |
97 | // setup logging
98 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
99 |
100 | // see if we are supposed to wait 5 seconds
101 | if *isLaunchSelf {
102 | launchSelfLater()
103 | }
104 |
105 | // see if they want to just create startup script
106 | if *createScript {
107 | createStartupScript()
108 | return
109 | }
110 |
111 | //getList()
112 | log.Println("Version:" + version)
113 |
114 | // hostname
115 | hn, _ := os.Hostname()
116 | if *hostname == "unknown-hostname" {
117 | *hostname = hn
118 | }
119 | log.Println("Hostname:", *hostname)
120 |
121 | // turn off garbage collection
122 | // this is dangerous, as u could overflow memory
123 | //if *isGC {
124 | if *gcType == "std" {
125 | log.Println("Garbage collection is on using Standard mode, meaning we just let Golang determine when to garbage collect.")
126 | } else if *gcType == "max" {
127 | log.Println("Garbage collection is on for MAXIMUM real-time collecting on each send/recv from serial port. Higher CPU, but less stopping of the world to garbage collect since it is being done on a constant basis.")
128 | } else {
129 | log.Println("Garbage collection is off. Memory use will grow unbounded. You WILL RUN OUT OF RAM unless you send in the gc command to manually force garbage collection. Lower CPU, but progressive memory footprint.")
130 | debug.SetGCPercent(-1)
131 | }
132 |
133 | if *isAllowExec {
134 | log.Println("Enabling exec commands because you passed in -allowexec")
135 | }
136 |
137 | ip, err := externalIP()
138 | if err != nil {
139 | log.Println(err)
140 | }
141 |
142 | //homeTempl = template.Must(template.ParseFiles(filepath.Join(*assets, "home.html")))
143 |
144 | // see if they provided a regex filter
145 | if len(*regExpFilter) > 0 {
146 | log.Printf("You specified a serial port regular expression filter: %v\n", *regExpFilter)
147 | }
148 |
149 | //GetDarwinMeta()
150 |
151 | if !*verbose {
152 | log.Println("You can enter verbose mode to see all logging by starting with the -v command line switch.")
153 | // log.SetOutput(new(NullWriter)) //route all logging to nullwriter
154 | }
155 |
156 | // list serial ports
157 | portList, _ := GetList()
158 | metaports, _ := GetMetaList()
159 |
160 | /*if errSys != nil {
161 | log.Printf("Got system error trying to retrieve serial port list. Err:%v\n", errSys)
162 | log.Fatal("Exiting")
163 | }*/
164 |
165 | // serial port list thread
166 | go func() {
167 | time.Sleep(1300 * time.Millisecond)
168 | log.SetOutput(io.Writer(os.Stdout))
169 | log.Println("Your serial ports:")
170 | if len(portList) == 0 {
171 | log.Println("\tThere are no serial ports to list.")
172 | }
173 | for _, element := range portList {
174 | // if we have meta data for this port, use it
175 | setMetaDataForOsSerialPort(&element, metaports)
176 | log.Printf("\t%v\n", element)
177 |
178 | }
179 | if !*verbose {
180 | //log.Println("You can enter verbose mode to see all logging by starting with the -v command line switch.")
181 | log.SetOutput(new(NullWriter)) //route all logging to nullwriter
182 | }
183 | }()
184 |
185 | // launch the hub routine which is the singleton for the websocket server
186 | go h.run()
187 | // launch our serial port routine
188 | go sh.run()
189 | // launch our dummy data routine
190 | //go d.run()
191 |
192 | // Run the UDP & TCP Server that are part of the Cayenn protocol
193 | // This lets us listen for devices announcing they
194 | // are alive on our local network, or are sending data from sensors,
195 | // or acknowledgements to commands we send the device.
196 | // This is used by Cayenn devices such as ESP8266 devices that
197 | // can speak to SPJS and allow SPJS to pass through their data back to
198 | // clients such as ChiliPeppr.
199 | if *isDisableCayenn == false {
200 | log.Println("Attempting to load Cayenn TCP/UDP server on port 8988...")
201 | go udpServerRun()
202 | go tcpServerRun()
203 | } else {
204 | log.Println("Disabled loading of Cayenn TCP/UDP server on port 8988")
205 | }
206 |
207 | // Setup GPIO server
208 | // Ignore GPIO for now, but it would be nice to get GPIO going natively
209 | //gpio.PreInit()
210 | // when the app exits, clean up our gpio ports
211 | //defer gpio.CleanupGpio()
212 |
213 | http.HandleFunc("/", homeHandler)
214 | http.HandleFunc("/ws", wsHandler)
215 |
216 | go startHttp(ip)
217 | go startHttps(ip)
218 |
219 | log.Println("The Serial Port JSON Server is now running.")
220 | log.Println("If you are using ChiliPeppr, you may go back to it and connect to this server.")
221 |
222 | // turn off logging output unless user wanted verbose mode
223 | // actually, this is now done after the serial port list thread completes
224 | if !*verbose {
225 | // log.SetOutput(new(NullWriter)) //route all logging to nullwriter
226 | }
227 |
228 | // wait
229 | ch := make(chan bool)
230 | <-ch
231 | }
232 |
233 | func startHttp(ip string) {
234 | f := flag.Lookup("addr")
235 | log.Println("Starting http server and websocket on " + ip + "" + f.Value.String())
236 | if err := http.ListenAndServe(*addr, nil); err != nil {
237 | fmt.Printf("Error trying to bind to http port: %v, so exiting...\n", err)
238 | fmt.Printf("This can sometimes mean you are already running SPJS and accidentally trying to run a second time, thus why the port would be in use. Also, check your permissions/credentials to make sure you can bind to IP address ports.")
239 | log.Fatal("Error ListenAndServe:", err)
240 | }
241 | }
242 |
243 | func startHttps(ip string) {
244 | // generate self-signed cert for testing or local trusted networks
245 | // openssl req -x509 -nodes -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365
246 |
247 | f := flag.Lookup("saddr")
248 | cert, certErr := os.Open(*scert)
249 | key, keyErr := os.Open(*skey)
250 |
251 | cert.Close()
252 | key.Close()
253 |
254 | if certErr != nil || keyErr != nil {
255 | log.Println("Missing tls cert and/or key. Will not start HTTPS server.")
256 | //fmt.Println("Missing tls cert and/or key. Will not start HTTPS server.")
257 | return
258 | }
259 |
260 | log.Println("Starting https server and websocket on " + ip + "" + f.Value.String())
261 | if err := http.ListenAndServeTLS(*saddr, *scert, *skey, nil); err != nil {
262 | fmt.Printf("Error trying to bind to https port: %v, so exiting...\n", err)
263 | log.Fatal("Error ListenAndServeTLS:", err)
264 | }
265 | }
266 |
267 | func externalIP() (string, error) {
268 | //log.Println("Getting external IP")
269 | ifaces, err := net.Interfaces()
270 | if err != nil {
271 | log.Println("Got err getting external IP addr")
272 | return "", err
273 | }
274 | for _, iface := range ifaces {
275 | if iface.Flags&net.FlagUp == 0 {
276 | //log.Println("Iface down")
277 | continue // interface down
278 | }
279 | if iface.Flags&net.FlagLoopback != 0 {
280 | //log.Println("Loopback")
281 | continue // loopback interface
282 | }
283 | addrs, err := iface.Addrs()
284 | if err != nil {
285 | log.Println("Got err on iface.Addrs()")
286 | return "", err
287 | }
288 | for _, addr := range addrs {
289 | var ip net.IP
290 | switch v := addr.(type) {
291 | case *net.IPNet:
292 | ip = v.IP
293 | case *net.IPAddr:
294 | ip = v.IP
295 | }
296 | if ip == nil || ip.IsLoopback() {
297 | //log.Println("Ip was nil or loopback")
298 | continue
299 | }
300 | ip = ip.To4()
301 | if ip == nil {
302 | //log.Println("Was not ipv4 addr")
303 | continue // not an ipv4 address
304 | }
305 | //log.Println("IP is ", ip.String())
306 | return ip.String(), nil
307 | }
308 | }
309 | return "", errors.New("are you connected to the network?")
310 | }
311 |
312 | var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHtml))
313 |
314 | // If you navigate to this server's homepage, you'll get this HTML
315 | // so you can directly interact with the serial port server
316 | const homeTemplateHtml = `
317 |
318 |
319 | Serial Port Example
320 |
321 |
362 |
399 |
400 |
401 |
402 |
406 |
407 |
408 | `
409 |
--------------------------------------------------------------------------------
/programmer.go:
--------------------------------------------------------------------------------
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 |
23 | startDownloadProgress()
24 |
25 | filename, err := downloadFromUrl(url)
26 |
27 | endDownloadProgress()
28 |
29 | mapB, _ = json.Marshal(map[string]string{"Filename": filename, "Url": url, "ProgrammerStatus": "DownloadDone"})
30 | h.broadcastSys <- mapB
31 |
32 | if err != nil {
33 | spErr(err.Error())
34 | } else {
35 | spProgram(portname, boardname, filename)
36 | }
37 |
38 | // delete file
39 |
40 | }
41 |
42 | func spProgram(portname string, boardname string, filePath string) {
43 |
44 | isFound, flasher, mycmd := assembleCompilerCommand(boardname, portname, filePath)
45 | mapD := map[string]string{"ProgrammerStatus": "CommandReady", "IsFound": strconv.FormatBool(isFound), "Flasher": flasher, "Cmd": strings.Join(mycmd, " ")}
46 | mapB, _ := json.Marshal(mapD)
47 | h.broadcastSys <- mapB
48 |
49 | if isFound {
50 | spHandlerProgram(flasher, mycmd)
51 | } else {
52 | spErr("We could not find the serial port " + portname + " or the board " + boardname + " that you were trying to program. It is also possible your serial port is locked by another app and thus we can't grab it to use for programming. Make sure all other apps that may be trying to access this serial port are disconnected or exited.")
53 | }
54 | }
55 |
56 | var oscmd *exec.Cmd
57 | var isRunning = false
58 |
59 | func spHandlerProgram(flasher string, cmdString []string) {
60 |
61 | // Extra protection code to ensure we aren't getting called from multiple threads
62 | if isRunning {
63 | mapD := map[string]string{"ProgrammerStatus": "ThreadError", "Msg": "You tried to run a 2nd (or further) program command while the 1st one was already running. Only 1 program cmd can run at once."}
64 | mapB, _ := json.Marshal(mapD)
65 | h.broadcastSys <- mapB
66 | return
67 | }
68 |
69 | isRunning = true
70 |
71 | //h.broadcastSys <- []byte("Start flashing with command " + cmdString)
72 | log.Printf("Flashing with command:" + strings.Join(cmdString, " "))
73 | mapD := map[string]string{"ProgrammerStatus": "Starting", "Cmd": strings.Join(cmdString, " ")}
74 | mapB, _ := json.Marshal(mapD)
75 | h.broadcastSys <- mapB
76 |
77 | // if runtime.GOOS == "darwin" {
78 | // sh, _ := exec.LookPath("sh")
79 | // // prepend the flasher to run it via sh
80 | // cmdString = append([]string{flasher}, cmdString...)
81 | // oscmd = exec.Command(sh, cmdString...)
82 | // } else {
83 | oscmd = exec.Command(flasher, cmdString...)
84 | // }
85 |
86 | // Stdout buffer
87 | //var cmdOutput []byte
88 |
89 | // start sending back signals to the browser as the programmer runs
90 | // just so user sees that things are chugging along
91 | startProgress()
92 |
93 | // will block here until results are done
94 | cmdOutput, err := oscmd.CombinedOutput()
95 |
96 | endProgress()
97 |
98 | if err != nil {
99 | log.Printf("Command finished with error: %v "+string(cmdOutput), err)
100 | h.broadcastSys <- []byte("Could not program the board")
101 | //mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board. It is also possible your serial port is locked by another app and thus we can't grab it to use for programming. Make sure all other apps that may be trying to access this serial port are disconnected or exited.", "Output": string(cmdOutput)}
102 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board.", "Output": string(cmdOutput)}
103 | mapB, _ := json.Marshal(mapD)
104 | h.broadcastSys <- mapB
105 | } else {
106 | log.Printf("Finished without error. Good stuff. stdout: " + string(cmdOutput))
107 | h.broadcastSys <- []byte("Flash OK!")
108 | mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok", "Output": string(cmdOutput)}
109 | mapB, _ := json.Marshal(mapD)
110 | h.broadcastSys <- mapB
111 | // analyze stdin
112 |
113 | }
114 |
115 | isRunning = false
116 | }
117 |
118 | func spHandlerProgramKill() {
119 |
120 | // Kill the process if there is one running
121 | if oscmd != nil && oscmd.Process.Pid > 0 {
122 | h.broadcastSys <- []byte("{\"ProgrammerStatus\": \"PreKilled\", \"Pid\": " + strconv.Itoa(oscmd.Process.Pid) + ", \"ProcessState\": \"" + oscmd.ProcessState.String() + "\"}")
123 | oscmd.Process.Kill()
124 | h.broadcastSys <- []byte("{\"ProgrammerStatus\": \"Killed\", \"Pid\": " + strconv.Itoa(oscmd.Process.Pid) + ", \"ProcessState\": \"" + oscmd.ProcessState.String() + "\"}")
125 |
126 | } else {
127 | if oscmd != nil {
128 | h.broadcastSys <- []byte("{\"ProgrammerStatus\": \"KilledError\", \"Msg\": \"No current process\", \"Pid\": " + strconv.Itoa(oscmd.Process.Pid) + ", \"ProcessState\": \"" + oscmd.ProcessState.String() + "\"}")
129 | } else {
130 | h.broadcastSys <- []byte("{\"ProgrammerStatus\": \"KilledError\", \"Msg\": \"No current process\"}")
131 | }
132 | }
133 | }
134 |
135 | // send back pseudo-status to browser while programming in progress
136 | // so user doesn't think it's dead
137 | // this is not multi-threaded, but will work for now since
138 | // this is just a nice-to-have informational progress
139 | var inProgress bool
140 |
141 | type ProgressState struct {
142 | ProgrammerStatus string
143 | Duration int
144 | Pid int
145 | //ProcessState string
146 | }
147 |
148 | func startProgress() {
149 | inProgress = true
150 | go func() {
151 | duration := 0
152 | for {
153 | time.Sleep(1 * time.Second)
154 | duration++
155 | if inProgress == false {
156 | break
157 | }
158 | // break after 5 minutes max
159 | if duration > 60*5 {
160 | break
161 | }
162 |
163 | progmsg := ProgressState{
164 | "Progress",
165 | duration,
166 | oscmd.Process.Pid,
167 | //oscmd.ProcessState.String(),
168 | }
169 | bm, _ := json.Marshal(progmsg)
170 | h.broadcastSys <- []byte(bm)
171 | }
172 | }()
173 | }
174 |
175 | func endProgress() {
176 | inProgress = false
177 | }
178 |
179 | // send back pseudo-status to browser while downloading in progress
180 | // so user doesn't think it's dead
181 | // this is not multi-threaded, but will work for now since
182 | // this is just a nice-to-have informational progress
183 | var inDownloadProgress bool
184 |
185 | func startDownloadProgress() {
186 | inDownloadProgress = true
187 | go func() {
188 | duration := 0
189 | for {
190 | time.Sleep(1 * time.Second)
191 | duration++
192 | h.broadcastSys <- []byte("{\"ProgrammerStatus\": \"DownloadProgress\", \"Duration\": " + strconv.Itoa(duration) + "}")
193 | if inDownloadProgress == false {
194 | break
195 | }
196 | // break after 5 minutes max
197 | if duration > 60*5 {
198 | break
199 | }
200 | }
201 | }()
202 | }
203 |
204 | func endDownloadProgress() {
205 | inDownloadProgress = false
206 | }
207 |
208 | func formatCmdline(cmdline string, boardOptions map[string]string) (string, bool) {
209 |
210 | list := strings.Split(cmdline, "{")
211 | if len(list) == 1 {
212 | return cmdline, false
213 | }
214 | cmdline = ""
215 | for _, item := range list {
216 | item_s := strings.Split(item, "}")
217 | item = boardOptions[item_s[0]]
218 | if len(item_s) == 2 {
219 | cmdline += item + item_s[1]
220 | } else {
221 | if item != "" {
222 | cmdline += item
223 | } else {
224 | cmdline += item_s[0]
225 | }
226 | }
227 | }
228 | log.Println(cmdline)
229 | return cmdline, true
230 | }
231 |
232 | func containsStr(s []string, e string) bool {
233 | for _, a := range s {
234 | if strings.ToLower(a) == strings.ToLower(e) {
235 | return true
236 | }
237 | }
238 | return false
239 | }
240 |
241 | func findNewPortName(slice1 []string, slice2 []string) string {
242 | m := map[string]int{}
243 |
244 | for _, s1Val := range slice1 {
245 | m[s1Val] = 1
246 | }
247 | for _, s2Val := range slice2 {
248 | m[s2Val] = m[s2Val] + 1
249 | }
250 |
251 | for mKey, mVal := range m {
252 | if mVal == 1 {
253 | return mKey
254 | }
255 | }
256 |
257 | return ""
258 | }
259 |
260 | func assembleCompilerCommand(boardname string, portname string, filePath string) (bool, string, []string) {
261 |
262 | // get executable (self)path and use it as base for all other paths
263 | execPath, _ := osext.Executable()
264 |
265 | boardFields := strings.Split(boardname, ":")
266 | if len(boardFields) != 3 {
267 | h.broadcastSys <- []byte("Board need to be specified in core:architecture:name format")
268 | return false, "", nil
269 | }
270 | tempPath := (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/boards.txt")
271 | file, err := os.Open(tempPath)
272 | if err != nil {
273 | //h.broadcastSys <- []byte("Could not find board: " + boardname)
274 | log.Println("Error:", err)
275 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not find board: " + boardname}
276 | mapB, _ := json.Marshal(mapD)
277 | h.broadcastSys <- mapB
278 |
279 | return false, "", nil
280 | }
281 | scanner := bufio.NewScanner(file)
282 |
283 | boardOptions := make(map[string]string)
284 | uploadOptions := make(map[string]string)
285 |
286 | for scanner.Scan() {
287 | // map everything matching with boardname
288 | if strings.Contains(scanner.Text(), boardFields[2]) {
289 | arr := strings.Split(scanner.Text(), "=")
290 | arr[0] = strings.Replace(arr[0], boardFields[2]+".", "", 1)
291 | boardOptions[arr[0]] = arr[1]
292 | }
293 | }
294 |
295 | if len(boardOptions) == 0 {
296 | errmsg := "Board " + boardFields[2] + " is not part of " + boardFields[0] + ":" + boardFields[1]
297 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": errmsg}
298 | mapB, _ := json.Marshal(mapD)
299 | h.broadcastSys <- mapB
300 |
301 | return false, "", nil
302 | }
303 |
304 | boardOptions["serial.port"] = portname
305 | boardOptions["serial.port.file"] = filepath.Base(portname)
306 |
307 | // filepath need special care; the project_name var is the filename minus its extension (hex or bin)
308 | // if we are going to modify standard IDE files we also could pass ALL filename
309 | filePath = strings.Trim(filePath, "\n")
310 | boardOptions["build.path"] = filepath.Dir(filePath)
311 | boardOptions["build.project_name"] = strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filepath.Base(filePath)))
312 |
313 | file.Close()
314 |
315 | // get infos about the programmer
316 | tempPath = (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/platform.txt")
317 | file, err = os.Open(tempPath)
318 | if err != nil {
319 | errmsg := "Could not find board: " + boardname
320 | log.Println("Error:", err)
321 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": errmsg}
322 | mapB, _ := json.Marshal(mapD)
323 | h.broadcastSys <- mapB
324 | return false, "", nil
325 | }
326 | scanner = bufio.NewScanner(file)
327 |
328 | tool := boardOptions["upload.tool"]
329 |
330 | for scanner.Scan() {
331 | // map everything matching with upload
332 | if strings.Contains(scanner.Text(), tool) {
333 | arr := strings.Split(scanner.Text(), "=")
334 | uploadOptions[arr[0]] = arr[1]
335 | arr[0] = strings.Replace(arr[0], "tools."+tool+".", "", 1)
336 | boardOptions[arr[0]] = arr[1]
337 | // we have a "=" in command line
338 | if len(arr) > 2 {
339 | boardOptions[arr[0]] = arr[1] + "=" + arr[2]
340 | }
341 | }
342 | }
343 | file.Close()
344 |
345 | // multiple verisons of the same programmer can be handled if "version" is specified
346 | version := uploadOptions["runtime.tools."+tool+".version"]
347 | path := (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/" + version)
348 | if err != nil {
349 | errmsg := "Could not find board: " + boardname
350 | log.Println("Error:", err)
351 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": errmsg}
352 | mapB, _ := json.Marshal(mapD)
353 | h.broadcastSys <- mapB
354 | return false, "", nil
355 | }
356 |
357 | boardOptions["runtime.tools."+tool+".path"] = path
358 |
359 | cmdline := boardOptions["upload.pattern"]
360 | // remove cmd.path as it is handled differently
361 | cmdline = strings.Replace(cmdline, "\"{cmd.path}\"", " ", 1)
362 | cmdline = strings.Replace(cmdline, "\"{path}/{cmd}\"", " ", 1)
363 | cmdline = strings.Replace(cmdline, "\"", "", -1)
364 |
365 | initialPortName := portname
366 |
367 | // some boards (eg. Leonardo, Yun) need a special procedure to enter bootloader
368 | if boardOptions["upload.use_1200bps_touch"] == "true" {
369 | // triggers bootloader mode
370 | // the portname could change in this occasion, so fail gently
371 | log.Println("Restarting in bootloader mode")
372 |
373 | mode := &serial.Mode{
374 | BaudRate: 1200,
375 | Vmin: 1,
376 | Vtimeout: 0,
377 | }
378 | port, err := serial.OpenPort(portname, mode)
379 | if err != nil {
380 | log.Println(err)
381 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": err.Error()}
382 | mapB, _ := json.Marshal(mapD)
383 | h.broadcastSys <- mapB
384 | return false, "", nil
385 | }
386 | log.Println("Was able to open port in 1200 baud mode")
387 | //port.SetDTR(false)
388 | port.Close()
389 | time.Sleep(time.Second / 2.0)
390 |
391 | timeout := false
392 | go func() {
393 | time.Sleep(2 * time.Second)
394 | timeout = true
395 | }()
396 |
397 | // time.Sleep(time.Second / 4)
398 | // wait for port to reappear
399 | if boardOptions["upload.wait_for_upload_port"] == "true" {
400 | after_reset_ports, _ := serial.GetPortsList()
401 | log.Println(after_reset_ports)
402 | var ports []string
403 | for {
404 | ports, _ = serial.GetPortsList()
405 | log.Println(ports)
406 | time.Sleep(time.Millisecond * 200)
407 | portname = findNewPortName(ports, after_reset_ports)
408 | if portname != "" {
409 | break
410 | }
411 | if timeout {
412 | break
413 | }
414 | }
415 | }
416 | }
417 |
418 | if portname == "" {
419 | portname = initialPortName
420 | }
421 |
422 | // some boards (eg. TinyG) need a ctrl+x to enter bootloader
423 | if boardOptions["upload.send_ctrl_x_to_enter_bootloader"] == "true" {
424 | // triggers bootloader mode
425 | // the portname could change in this occasion, so fail gently
426 | log.Println("Sending ctrl+x to enter bootloader mode")
427 |
428 | // Extra protection code to ensure we aren't getting called from multiple threads
429 | if isRunning {
430 | mapD := map[string]string{"AssembleStatus": "ThreadError", "Msg": "You tried to run a 2nd (or further) Ctrl+x prep command while the 1st one was already running."}
431 | mapB, _ := json.Marshal(mapD)
432 | h.broadcastSys <- mapB
433 | return false, "", nil
434 | }
435 |
436 | isRunning = true
437 |
438 | mode := &serial.Mode{
439 | BaudRate: 115200,
440 | Vmin: 1,
441 | Vtimeout: 0,
442 | }
443 | port, err := serial.OpenPort(portname, mode)
444 | if err != nil {
445 | log.Println(err)
446 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": err.Error()}
447 | mapB, _ := json.Marshal(mapD)
448 | h.broadcastSys <- mapB
449 | isRunning = false
450 | return false, "", nil
451 | }
452 | log.Println("Was able to open port in 115200 baud mode for ctrl+x send")
453 |
454 | port.Write([]byte(string(24))) // byte value which is ascii 24, ctrl+x
455 | port.Close()
456 | log.Println("Sent ctrl+x and then closed port. Go ahead and upload cuz we are in bootloader mode.")
457 | }
458 | isRunning = false
459 |
460 | boardOptions["serial.port"] = portname
461 | boardOptions["serial.port.file"] = filepath.Base(portname)
462 |
463 | // split the commandline in substrings and recursively replace mapped strings
464 | cmdlineSlice := strings.Split(cmdline, " ")
465 | var winded = true
466 | for index, _ := range cmdlineSlice {
467 | winded = true
468 | for winded != false {
469 | cmdlineSlice[index], winded = formatCmdline(cmdlineSlice[index], boardOptions)
470 | }
471 | }
472 |
473 | tool = (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/bin/" + tool)
474 | // the file doesn't exist, we are on windows
475 | if _, err := os.Stat(tool); err != nil {
476 | tool = tool + ".exe"
477 | // convert all "/" to "\"
478 | tool = strings.Replace(tool, "/", "\\", -1)
479 | }
480 |
481 | // remove blanks from cmdlineSlice
482 | var cmdlineSliceOut []string
483 | for _, element := range cmdlineSlice {
484 | if element != "" {
485 | cmdlineSliceOut = append(cmdlineSliceOut, element)
486 | }
487 | }
488 |
489 | // add verbose
490 | cmdlineSliceOut = append(cmdlineSliceOut, "-v")
491 |
492 | log.Printf("Tool:%v, cmdline:%v\n", tool, cmdlineSliceOut)
493 | return (tool != ""), tool, cmdlineSliceOut
494 | }
495 |
--------------------------------------------------------------------------------
/queue.go:
--------------------------------------------------------------------------------
1 | //
2 | // queue.go
3 | //
4 | // Created by Hicham Bouabdallah
5 | // Copyright (c) 2012 SimpleRocket LLC
6 | //
7 | // Permission is hereby granted, free of charge, to any person
8 | // obtaining a copy of this software and associated documentation
9 | // files (the "Software"), to deal in the Software without
10 | // restriction, including without limitation the rights to use,
11 | // copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the
13 | // Software is furnished to do so, subject to the following
14 | // conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be
17 | // included in all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 | // OTHER DEALINGS IN THE SOFTWARE.
27 | //
28 |
29 | package main
30 |
31 | import (
32 | "strings"
33 | "sync"
34 | )
35 |
36 | type queuenode struct {
37 | data string
38 | id string
39 | next *queuenode
40 | }
41 |
42 | // A go-routine safe FIFO (first in first out) data stucture.
43 | type Queue struct {
44 | head *queuenode
45 | tail *queuenode
46 | count int
47 | lock *sync.Mutex
48 | lenOfCmds int
49 | }
50 |
51 | // Creates a new pointer to a new queue.
52 | func NewQueue() *Queue {
53 | q := &Queue{}
54 | q.lock = &sync.Mutex{}
55 | return q
56 | }
57 |
58 | // Returns the number of elements in the queue (i.e. size/length)
59 | // go-routine safe.
60 | func (q *Queue) Len() int {
61 | q.lock.Lock()
62 | defer q.lock.Unlock()
63 | return q.count
64 | }
65 |
66 | // Returns the length of the data (gcode cmd) in the queue (i.e. size/length)
67 | // go-routine safe.
68 | func (q *Queue) LenOfCmds() int {
69 | q.lock.Lock()
70 | defer q.lock.Unlock()
71 | return q.lenOfCmds
72 | }
73 |
74 | // Pushes/inserts a value at the end/tail of the queue.
75 | // Note: this function does mutate the queue.
76 | // go-routine safe.
77 | func (q *Queue) Push(item string, id string) {
78 | q.lock.Lock()
79 | defer q.lock.Unlock()
80 |
81 | n := &queuenode{data: item, id: id}
82 |
83 | if q.tail == nil {
84 | q.tail = n
85 | q.head = n
86 | } else {
87 | q.tail.next = n
88 | q.tail = n
89 | }
90 | q.count++
91 | q.lenOfCmds += len(item)
92 | }
93 |
94 | // Returns the value at the front of the queue.
95 | // i.e. the oldest value in the queue.
96 | // Note: this function does mutate the queue.
97 | // go-routine safe.
98 | func (q *Queue) Poll() (string, string) {
99 | q.lock.Lock()
100 | defer q.lock.Unlock()
101 |
102 | if q.head == nil {
103 | return "", ""
104 | }
105 |
106 | n := q.head
107 | q.head = n.next
108 |
109 | if q.head == nil {
110 | q.tail = nil
111 | }
112 | q.count--
113 | q.lenOfCmds -= len(n.data)
114 |
115 | return n.data, n.id
116 | }
117 |
118 | // Returns a read value at the front of the queue.
119 | // i.e. the oldest value in the queue.
120 | // Note: this function does NOT mutate the queue.
121 | // go-routine safe.
122 | func (q *Queue) Peek() (string, string) {
123 | q.lock.Lock()
124 | defer q.lock.Unlock()
125 |
126 | n := q.head
127 | if n == nil || n.data == "" {
128 | return "", ""
129 | }
130 |
131 | return n.data, n.id
132 | }
133 |
134 | // Returns a read value at the front of the queue.
135 | // i.e. the oldest value in the queue.
136 | // Note: this function does NOT mutate the queue.
137 | // go-routine safe.
138 | func (q *Queue) Delete() {
139 | q.lock.Lock()
140 | defer q.lock.Unlock()
141 |
142 | q.head = nil
143 | q.tail = nil
144 | q.count = 0
145 | q.lenOfCmds = 0
146 | }
147 |
148 | func (q *Queue) DebugStr() string {
149 | q.lock.Lock()
150 | defer q.lock.Unlock()
151 |
152 | str := ""
153 | if q.head != nil {
154 | str += q.head.id + ":" + strings.Replace(q.head.data, "\n", "\\n", -1)
155 |
156 | n := q.head.next
157 | for n != nil {
158 | str += ", " + n.id + ":" + strings.Replace(n.data, "\n", "\\n", -1)
159 | n = n.next
160 | }
161 | }
162 |
163 | return str
164 | }
165 |
--------------------------------------------------------------------------------
/queue_tid.go:
--------------------------------------------------------------------------------
1 | //
2 | // queue.go
3 | //
4 | // Created by Hicham Bouabdallah
5 | // Copyright (c) 2012 SimpleRocket LLC
6 | //
7 | // Permission is hereby granted, free of charge, to any person
8 | // obtaining a copy of this software and associated documentation
9 | // files (the "Software"), to deal in the Software without
10 | // restriction, including without limitation the rights to use,
11 | // copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the
13 | // Software is furnished to do so, subject to the following
14 | // conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be
17 | // included in all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 | // OTHER DEALINGS IN THE SOFTWARE.
27 | //
28 |
29 | package main
30 |
31 | import "sync"
32 |
33 | type queueTidNode struct {
34 | data string
35 | id string // their id so we can regurgitate
36 | tid int // our transaction id that tinyg will send back to us so we can match
37 | next *queueTidNode
38 | }
39 |
40 | // A go-routine safe FIFO (first in first out) data stucture.
41 | type QueueTid struct {
42 | head *queueTidNode
43 | tail *queueTidNode
44 | count int
45 | lock *sync.Mutex
46 | lenOfCmds int
47 | }
48 |
49 | // Creates a new pointer to a new queue.
50 | func NewQueueTid() *QueueTid {
51 | q := &QueueTid{}
52 | q.lock = &sync.Mutex{}
53 | return q
54 | }
55 |
56 | // Returns the number of elements in the queue (i.e. size/length)
57 | // go-routine safe.
58 | func (q *QueueTid) Len() int {
59 | q.lock.Lock()
60 | defer q.lock.Unlock()
61 | return q.count
62 | }
63 |
64 | // Returns the length of the data (gcode cmd) in the queue (i.e. size/length)
65 | // go-routine safe.
66 | func (q *QueueTid) LenOfCmds() int {
67 | q.lock.Lock()
68 | defer q.lock.Unlock()
69 | return q.lenOfCmds
70 | }
71 |
72 | // Pushes/inserts a value at the end/tail of the queue.
73 | // Note: this function does mutate the queue.
74 | // go-routine safe.
75 | func (q *QueueTid) Push(item string, id string, tid int) {
76 | q.lock.Lock()
77 | defer q.lock.Unlock()
78 |
79 | n := &queueTidNode{data: item, id: id, tid: tid}
80 |
81 | if q.tail == nil {
82 | q.tail = n
83 | q.head = n
84 | } else {
85 | q.tail.next = n
86 | q.tail = n
87 | }
88 | q.count++
89 | q.lenOfCmds += len(item)
90 | }
91 |
92 | // Shifts/inserts a value at the front of the queue.
93 | // Note: this function does mutate the queue.
94 | // go-routine safe.
95 | func (q *QueueTid) Shift(item string, id string, tid int) {
96 | q.lock.Lock()
97 | defer q.lock.Unlock()
98 |
99 | n := &queueTidNode{data: item, id: id, tid: tid}
100 |
101 | n.next = q.head // make the current node at front of queue now be the "next" for our new node
102 | q.head = n // make the head be our newly defined node
103 |
104 | if q.tail == nil {
105 | q.tail = n.next // if the tail was empty, make our old head node be the tail
106 | //q.head = n
107 | } else {
108 | // do nothing??
109 | //q.tail.next = n
110 | //q.tail = n
111 | }
112 | q.count++
113 | q.lenOfCmds += len(item)
114 | }
115 |
116 | // Returns the value at the front of the queue.
117 | // i.e. the oldest value in the queue.
118 | // Note: this function does mutate the queue.
119 | // go-routine safe.
120 | func (q *QueueTid) Poll() (string, string, int) {
121 | q.lock.Lock()
122 | defer q.lock.Unlock()
123 |
124 | if q.head == nil {
125 | return "", "", -1
126 | }
127 |
128 | n := q.head
129 | q.head = n.next
130 |
131 | if q.head == nil {
132 | q.tail = nil
133 | }
134 | q.count--
135 | q.lenOfCmds -= len(n.data)
136 |
137 | return n.data, n.id, n.tid
138 | }
139 |
140 | // Returns a read value at the front of the queue.
141 | // i.e. the oldest value in the queue.
142 | // Note: this function does NOT mutate the queue.
143 | // go-routine safe.
144 | func (q *QueueTid) Peek() (string, string, int) {
145 | q.lock.Lock()
146 | defer q.lock.Unlock()
147 |
148 | n := q.head
149 | if n == nil || n.data == "" {
150 | return "", "", -1
151 | }
152 |
153 | return n.data, n.id, n.tid
154 | }
155 |
156 | // Returns a read value at the front of the queue.
157 | // i.e. the oldest value in the queue.
158 | // Note: this function does NOT mutate the queue.
159 | // go-routine safe.
160 | func (q *QueueTid) Delete() {
161 | q.lock.Lock()
162 | defer q.lock.Unlock()
163 |
164 | q.head = nil
165 | q.tail = nil
166 | q.count = 0
167 | q.lenOfCmds = 0
168 | }
169 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # You need to:
3 | # go get github.com/aktau/github-release
4 | # and that will add github-release to your go path command line
5 | go get github.com/aktau/github-release
6 |
7 | export GITHUB_TOKEN=d1ce644ff5eef10f4f1e5fbf27515d22c7d68e8b
8 |
9 | export GITHUB_USER=chilipeppr
10 |
11 | export GITHUB_REPO=serial-port-json-server
12 |
13 | echo "About to create a Github release for Serial Port JSON Server"
14 | if [ "$1" = "" ]; then
15 | echo "You need to pass in the version number as the first parameter like ./release 1.87"
16 | exit
17 | fi
18 |
19 | echo ""
20 | echo "Before creating release"
21 | bin/github-release info
22 |
23 | bin/github-release release \
24 | --tag v$1 \
25 | --name "Serial Port JSON Server" \
26 | --description "A server for the Internet of Things. Lets you serve up serial ports to websockets so you can write front-end apps for your IoT devices in the browser." \
27 |
28 | echo ""
29 | echo "After creating release"
30 | bin/github-release info
31 |
32 | echo ""
33 | echo "Uploading binaries"
34 |
35 | # upload a file, for example the OSX/AMD64 binary of my gofinance app
36 | bin/github-release upload \
37 | --tag v$1 \
38 | --name "serial-port-json-server-$1_linux_amd64.tar.gz" \
39 | --file snapshot/serial-port-json-server-$1_linux_amd64.tar.gz
40 | bin/github-release upload \
41 | --tag v$1 \
42 | --name "serial-port-json-server-$1_linux_386.tar.gz" \
43 | --file snapshot/serial-port-json-server-$1_linux_386.tar.gz
44 | bin/github-release upload \
45 | --tag v$1 \
46 | --name "serial-port-json-server-$1_linux_arm.tar.gz" \
47 | --file snapshot/serial-port-json-server-$1_linux_arm.tar.gz
48 | bin/github-release upload \
49 | --tag v$1 \
50 | --name "serial-port-json-server-$1_windows_386.zip" \
51 | --file snapshot/serial-port-json-server-$1_windows_386.zip
52 | bin/github-release upload \
53 | --tag v$1 \
54 | --name "serial-port-json-server-$1_windows_amd64.zip" \
55 | --file snapshot/serial-port-json-server-$1_windows_amd64.zip
56 |
57 | echo ""
58 | echo "Done"
--------------------------------------------------------------------------------
/release_windows.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # You need to:
3 | # go get github.com/aktau/github-release
4 | # and that will add github-release to your go path command line
5 | echo "Installing github-release to commandline"
6 | echo "go get github.com/aktau/github-release"
7 | go get github.com/aktau/github-release
8 |
9 | # You need to create a personal github token and create an environment variable like below
10 | #export GITHUB_TOKEN=dd3b6cac1e0a426b33e2b0852df3ab85c25b2368
11 |
12 | export GITHUB_USER=chilipeppr
13 |
14 | export GITHUB_REPO=serial-port-json-server
15 |
16 | echo "About to create a Github release for Serial Port JSON Server"
17 | if [ "$1" = "" ]; then
18 | echo "You need to pass in the version number as the first parameter like ./release 1.87"
19 | exit
20 | fi
21 |
22 | echo ""
23 | echo "Before creating release"
24 | github-release info
25 |
26 | github-release release \
27 | --tag v$1 \
28 | --name "Serial Port JSON Server" \
29 | --description "A server for the Internet of Things. Lets you serve up serial ports to websockets so you can write front-end apps for your IoT devices in the browser." \
30 |
31 | echo ""
32 | echo "After creating release"
33 | github-release info
34 |
35 | echo ""
36 | echo "Uploading binaries"
37 |
38 | # upload a file, for example the OSX/AMD64 binary of my gofinance app
39 | github-release upload \
40 | --tag v$1 \
41 | --name "serial-port-json-server-$1_linux_amd64.tar.gz" \
42 | --file snapshot/serial-port-json-server-$1_linux_amd64.tar.gz
43 | github-release upload \
44 | --tag v$1 \
45 | --name "serial-port-json-server-$1_linux_386.tar.gz" \
46 | --file snapshot/serial-port-json-server-$1_linux_386.tar.gz
47 | github-release upload \
48 | --tag v$1 \
49 | --name "serial-port-json-server-$1_linux_arm.tar.gz" \
50 | --file snapshot/serial-port-json-server-$1_linux_arm.tar.gz
51 | github-release upload \
52 | --tag v$1 \
53 | --name "serial-port-json-server-$1_windows_386.zip" \
54 | --file snapshot/serial-port-json-server-$1_windows_386.zip
55 | github-release upload \
56 | --tag v$1 \
57 | --name "serial-port-json-server-$1_windows_amd64.zip" \
58 | --file snapshot/serial-port-json-server-$1_windows_amd64.zip
59 | github-release upload \
60 | --tag v$1 \
61 | --name "serial-port-json-server-$1_darwin_amd64.zip" \
62 | --file snapshot/serial-port-json-server-$1_darwin_amd64.zip
63 |
64 | echo ""
65 | echo "Done"
--------------------------------------------------------------------------------
/sample-cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIETjCCAzagAwIBAgIJANLvDOX4UE/MMA0GCSqGSIb3DQEBBQUAMHcxCzAJBgNV
3 | BAYTAlhYMQswCQYDVQQIEwJZWTELMAkGA1UEBxMCWloxDDAKBgNVBAoTA0FCQzEM
4 | MAoGA1UECxMDREVGMRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgkqhkiG9w0BCQEW
5 | D2FkbWluQGxvY2FsaG9zdDAeFw0xNjA0MjAxODEzNDdaFw0xNzA0MjAxODEzNDda
6 | MHcxCzAJBgNVBAYTAlhYMQswCQYDVQQIEwJZWTELMAkGA1UEBxMCWloxDDAKBgNV
7 | BAoTA0FCQzEMMAoGA1UECxMDREVGMRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgkq
8 | hkiG9w0BCQEWD2FkbWluQGxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP
9 | ADCCAQoCggEBALgVUetvxfIYmcdw6XBvMWtXxnws6eI69NuPIXPPnuFtCVjh2CRF
10 | +T2M5rVPtA+HyWPfbNrdX+9Mg1DgTSOMREKEJcEo6hh4O68Y2CtrcLR4HDZUbeqX
11 | pgzUSi9G49ByGDlAMUKieD+LzxNq6biTbAXKpTrHbfwCkTvNtxzAac7ygeWL26Hi
12 | hn67xp/KBi9RGM/w50nMe4AY9UDzM9UXpbYTGVr7GF5x8V8ETcO2BpaQw/fDV7TB
13 | NJccaNYJbSYjCgu+7IxFrHwFCjC3xSmkVXexVdoPHqEuEy2QY71Mwz8YRFzBpuKh
14 | /msxiQV4tpa8OhN9Ny6vJO2uDNTymACWvV0CAwEAAaOB3DCB2TAdBgNVHQ4EFgQU
15 | qsW79WmhsWzbv8zC0KLTmJv4FEUwgakGA1UdIwSBoTCBnoAUqsW79WmhsWzbv8zC
16 | 0KLTmJv4FEWhe6R5MHcxCzAJBgNVBAYTAlhYMQswCQYDVQQIEwJZWTELMAkGA1UE
17 | BxMCWloxDDAKBgNVBAoTA0FCQzEMMAoGA1UECxMDREVGMRIwEAYDVQQDEwlsb2Nh
18 | bGhvc3QxHjAcBgkqhkiG9w0BCQEWD2FkbWluQGxvY2FsaG9zdIIJANLvDOX4UE/M
19 | MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBACQd5Qt+9Z46MKHTteIU
20 | l+roIHHtpO5eP2450/lbvmYHtf0IFX0CiTszS8KtgDdAPcRJNNsNOOh7VLDX09IT
21 | aeAhNH1X2MAxNmM0FsDKWfT72G27TvXrP6I3Mgt0q1SJKGrxRxK/HesPzZ3TVypq
22 | Yie8OQ7ZxNxzeAH0ZoHmHpFBe+cOyr87xjzkih2ls9bYkiqBGxqzGn0uATdVimjj
23 | attRFU4zq/K9Rq1NVQIJDQWt0CgmJyR0V5GbEA2QcrS0ZLHPM1thxB3pSCTUXVQz
24 | T2Z5eG4gEsXfrM0s17DFg6kx18SsYj1jQUVFoJJCVbudDQ1BUlUNfc7mRghvuuG8
25 | pC4=
26 | -----END CERTIFICATE-----
27 |
--------------------------------------------------------------------------------
/sample-key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpQIBAAKCAQEAuBVR62/F8hiZx3DpcG8xa1fGfCzp4jr0248hc8+e4W0JWOHY
3 | JEX5PYzmtU+0D4fJY99s2t1f70yDUOBNI4xEQoQlwSjqGHg7rxjYK2twtHgcNlRt
4 | 6pemDNRKL0bj0HIYOUAxQqJ4P4vPE2rpuJNsBcqlOsdt/AKRO823HMBpzvKB5Yvb
5 | oeKGfrvGn8oGL1EYz/DnScx7gBj1QPMz1RelthMZWvsYXnHxXwRNw7YGlpDD98NX
6 | tME0lxxo1gltJiMKC77sjEWsfAUKMLfFKaRVd7FV2g8eoS4TLZBjvUzDPxhEXMGm
7 | 4qH+azGJBXi2lrw6E303Lq8k7a4M1PKYAJa9XQIDAQABAoIBAQCNSDeuZgPbckwc
8 | SSmxFHYud5ir12DONAeXt3uqQfAj/aaN5BAajuXi+pBbGcgNn17O2zzVodAufl/O
9 | o8gwf0gocPn1DSzWCPltwriuYnCG4iRtAlG2Ghvkfs5NCLByXA8BaaYlCUMXKnuM
10 | 4KJu+h6PN1+nBEcCgwnqh1GBrJ/IpwQVawrX2YVO9usSmg2XdyO4UlSwFpXpVrry
11 | zDlwMGQEWG6Rurgj1TX1idqreeRC9LycvKb8BjcaLEj310bkwdOOPd9gAArJ0u/5
12 | GDGKkUeiWqpUMjUdfqz243pFCUKNll91wU7zx7BSiQbSFcVNmZqeeP3qiZD7glBz
13 | KstGgf5hAoGBANqw4OX0BRClQzJWBGSe/uZZvj0GdWgcJpNK+7ZbqfvwxwW2OB8l
14 | o5cq7oSf9cVBO0ZPdQx3p24KRR0VQXRU+Nophg1sUll8JihbNX+dRspaZ4LMwgnz
15 | fzFvMVAPtb/XDRNGwC2YvWg7ZYygHhZ+09PQNIV2IxNtLJOjarQXlFupAoGBANd8
16 | /Bs7AxoaP/UpCQf+YmA61T2yBs7n1vAyOKKmyEgk3eIM0PhyCqEOerC6ecA07wzg
17 | GKa8RHyPqeqopnf8F3NletbhX0ZjY2Zykm2Ekv404x0QquurEsCC0O9I+1X3C6t/
18 | 3/20EXIQnxgaWeaSYOOBpfvkvMSMIPm5D0FSkMSVAoGBALuWbdPSdWXJ3NIYprwJ
19 | Bm1hHaYLHDqpqw09jJzoE+9goddsbseI6cKJuP9alt8VDVtKXQTMvnnNpWtFCFhZ
20 | avz4EV0CRcEslS6YzlMg/dAlrBSuvuL6U2h8ELi0QRrFxRl73u09z3rROFJgJm71
21 | a46NUgAJTh0j0NDzpFvGviPBAoGBAMtSjdnvs3yUmhZrkq7tMsuTl5LwAIaHsIHR
22 | ESk/byjLSGS7LQ3PJQJUVCWevbRC/e/LHtdsOr7BG1Vjrjb2MPZcISzRWAFlU+vd
23 | XRZjCgM7ybOp/2wAbeAhTp0I4sV5JZS7QpDyr6dN0Z9/daYeJbdkpEXpzMczZQXb
24 | vG4pRpmFAoGAAfTb/nacIxGx5XNqq5fuxQVs4eknAFI+mXWAdjpIisuB166kgDsZ
25 | weoA32Ju+aU5P5OvYBpMJWTlrgM8IfkSVqr62I5Q1FSuoQEEWlXiWRMQPSFCa/f8
26 | Qi8tXnSR1qJZUEgykio0XxENjxN5P7Iu8MUKIBTH2RK8Hfwcxb/0r2M=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/seriallist.go:
--------------------------------------------------------------------------------
1 | // Supports Windows, Linux, Mac, and Raspberry Pi
2 |
3 | package main
4 |
5 | import (
6 | "encoding/xml"
7 | "strings"
8 | //"fmt"
9 | "github.com/facchinm/go-serial"
10 | //"io/ioutil"
11 | "log"
12 | //"os"
13 | "regexp"
14 | )
15 |
16 | type OsSerialPort struct {
17 | Name string
18 | FriendlyName string
19 | RelatedNames []string // for some devices there are 2 or more ports, i.e. TinyG v9 has 2 serial ports
20 | SerialNumber string
21 | DeviceClass string
22 | Manufacturer string
23 | Product string
24 | IdProduct string
25 | IdVendor string
26 | }
27 |
28 | func GetList() ([]OsSerialPort, error) {
29 |
30 | //log.Println("Doing GetList()")
31 |
32 | ports, err := serial.GetPortsList()
33 |
34 | arrPorts := []OsSerialPort{}
35 | for _, element := range ports {
36 | friendly := strings.Replace(element, "/dev/", "", -1)
37 | arrPorts = append(arrPorts, OsSerialPort{Name: element, FriendlyName: friendly})
38 | }
39 |
40 | // see if we should filter the list
41 | if len(*regExpFilter) > 0 {
42 | // yes, user asked for a filter
43 | reFilter := regexp.MustCompile("(?i)" + *regExpFilter)
44 |
45 | newarrPorts := []OsSerialPort{}
46 | for _, element := range arrPorts {
47 | // if matches regex, include
48 | if reFilter.MatchString(element.Name) {
49 | newarrPorts = append(newarrPorts, element)
50 | } else if reFilter.MatchString(element.FriendlyName) {
51 | newarrPorts = append(newarrPorts, element)
52 | } else {
53 | log.Printf("serial port did not match. port: %v\n", element)
54 | }
55 |
56 | }
57 | arrPorts = newarrPorts
58 | }
59 |
60 | //log.Printf("Done doing GetList(). arrPorts:%v\n", arrPorts)
61 |
62 | return arrPorts, err
63 | }
64 |
65 | func GetMetaList() ([]OsSerialPort, error) {
66 | metaportlist, err := getMetaList()
67 | if err.Err != nil {
68 | return nil, err.Err
69 | }
70 | return metaportlist, err.Err
71 | }
72 |
73 | func GetFriendlyName(portname string) string {
74 | log.Println("GetFriendlyName from base class")
75 | return ""
76 | }
77 |
78 | type Dict struct {
79 | Keys []string `xml:"key"`
80 | Arrays []Dict `xml:"array"`
81 | Strings []string `xml:"string"`
82 | Dicts []Dict `xml:"dict"`
83 | }
84 |
85 | type Result struct {
86 | XMLName xml.Name `xml:"plist"`
87 | //Strings []string `xml:"dict>string"`
88 | Dict `xml:"dict"`
89 | //Phone string
90 | //Groups []string `xml:"Group>Value"`
91 | }
92 |
93 | /*
94 | func GetDarwinMeta() {
95 | xmlFile, err := os.Open("out.xml")
96 | if err != nil {
97 | fmt.Println("Error opening file:", err)
98 | return
99 | }
100 | defer xmlFile.Close()
101 |
102 | XMLdata, _ := ioutil.ReadAll(xmlFile)
103 |
104 | var v Result
105 | //v := Result{}
106 | xml.Unmarshal(XMLdata, &v)
107 | log.Printf("Result:%v", len(v.Dicts[0].Arrays)) //, v.Dict.Dicts[0].Keys) // Dicts[0] .Keys[0])
108 | log.Printf("Result:%v", v.Dicts[0].Arrays[1].Dicts[0].Keys[0]) // Dicts[0] .Keys[0])
109 | log.Printf("Result:%v", v.Dicts[0].Keys[0]) // Dicts[0] .Keys[0])
110 |
111 | }
112 | */
113 |
--------------------------------------------------------------------------------
/seriallist_darwin.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | //"fmt"
5 | //"github.com/tarm/goserial"
6 | "log"
7 | "os"
8 | "strings"
9 | //"encoding/binary"
10 | //"strconv"
11 | //"syscall"
12 | //"fmt"
13 | //"encoding/xml"
14 | "io/ioutil"
15 | )
16 |
17 | func getMetaList() ([]OsSerialPort, os.SyscallError) {
18 | //return getListViaWmiPnpEntity()
19 | return getListViaTtyList()
20 |
21 | // query the out.xml file for now, but in real life
22 | // we would run the ioreg -a -p IOUSB command to get the output
23 | // and then parse it
24 |
25 | }
26 |
27 | func getListViaTtyList() ([]OsSerialPort, os.SyscallError) {
28 | var err os.SyscallError
29 |
30 | log.Println("getting serial list on darwin")
31 |
32 | // make buffer of 100 max serial ports
33 | // return a slice
34 | list := make([]OsSerialPort, 100)
35 |
36 | files, _ := ioutil.ReadDir("/dev/")
37 | ctr := 0
38 | for _, f := range files {
39 | if strings.HasPrefix(f.Name(), "tty.") {
40 | // it is a legitimate serial port
41 | list[ctr].Name = "/dev/" + f.Name()
42 | list[ctr].FriendlyName = f.Name()
43 | log.Println("Added serial port to list: ", list[ctr])
44 | ctr++
45 | }
46 | // stop-gap in case going beyond 100 (which should never happen)
47 | // i mean, really, who has more than 100 serial ports?
48 | if ctr > 99 {
49 | ctr = 99
50 | }
51 | //fmt.Println(f.Name())
52 | //fmt.Println(f.)
53 | }
54 | /*
55 | list := make([]OsSerialPort, 3)
56 | list[0].Name = "tty.serial1"
57 | list[0].FriendlyName = "tty.serial1"
58 | list[1].Name = "tty.serial2"
59 | list[1].FriendlyName = "tty.serial2"
60 | list[2].Name = "tty.Bluetooth-Modem"
61 | list[2].FriendlyName = "tty.Bluetooth-Modem"
62 | */
63 |
64 | return list[0:ctr], err
65 | }
66 |
--------------------------------------------------------------------------------
/seriallist_linux.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | //"fmt"
5 | //"github.com/tarm/goserial"
6 | //"log"
7 | "os"
8 | "os/exec"
9 | "strings"
10 | //"encoding/binary"
11 | //"strconv"
12 | //"syscall"
13 | //"fmt"
14 | //"io"
15 | "bytes"
16 | "io/ioutil"
17 | "log"
18 | "path/filepath"
19 | "regexp"
20 | "sort"
21 | )
22 |
23 | func getMetaList() ([]OsSerialPort, os.SyscallError) {
24 |
25 | //return getListViaTtyList()
26 | return getAllPortsViaManufacturer()
27 | }
28 |
29 | func getListViaTtyList() ([]OsSerialPort, os.SyscallError) {
30 | var err os.SyscallError
31 |
32 | //log.Println("getting serial list on darwin")
33 |
34 | // make buffer of 1000 max serial ports
35 | // return a slice
36 | list := make([]OsSerialPort, 1000)
37 |
38 | files, _ := ioutil.ReadDir("/dev/")
39 | ctr := 0
40 | for _, f := range files {
41 | if strings.HasPrefix(f.Name(), "tty") {
42 | // it is a legitimate serial port
43 | list[ctr].Name = "/dev/" + f.Name()
44 | list[ctr].FriendlyName = f.Name()
45 |
46 | // see if we can get a better friendly name
47 | //friendly, ferr := getMetaDataForPort(f.Name())
48 | //if ferr == nil {
49 | // list[ctr].FriendlyName = friendly
50 | //}
51 |
52 | //log.Println("Added serial port to list: ", list[ctr])
53 | ctr++
54 | }
55 | // stop-gap in case going beyond 1000 (which should never happen)
56 | // i mean, really, who has more than 1000 serial ports?
57 | if ctr > 999 {
58 | ctr = 999
59 | }
60 | //fmt.Println(f.Name())
61 | //fmt.Println(f.)
62 | }
63 | /*
64 | list := make([]OsSerialPort, 3)
65 | list[0].Name = "tty.serial1"
66 | list[0].FriendlyName = "tty.serial1"
67 | list[1].Name = "tty.serial2"
68 | list[1].FriendlyName = "tty.serial2"
69 | list[2].Name = "tty.Bluetooth-Modem"
70 | list[2].FriendlyName = "tty.Bluetooth-Modem"
71 | */
72 |
73 | return list[0:ctr], err
74 | }
75 |
76 | type deviceClass struct {
77 | BaseClass int
78 | Description string
79 | }
80 |
81 | func getDeviceClassList() {
82 | // TODO: take list from http://www.usb.org/developers/defined_class
83 | // and create mapping.
84 | }
85 |
86 | func getAllPortsViaManufacturer() ([]OsSerialPort, os.SyscallError) {
87 | var err os.SyscallError
88 | var list []OsSerialPort
89 |
90 | // LOOK FOR THE WORD MANUFACTURER
91 | // search /sys folder
92 | files := findFiles("/sys", "^manufacturer$")
93 |
94 | // LOOK FOR THE WORD PRODUCT
95 | filesFromProduct := findFiles("/sys", "^product$")
96 |
97 | // append both arrays so we have one (then we'll have to de-dupe)
98 | files = append(files, filesFromProduct...)
99 |
100 | // Now get directories from each file
101 | re := regexp.MustCompile("/(manufacturer|product)$")
102 | var mapfile map[string]int
103 | mapfile = make(map[string]int)
104 | for _, element := range files {
105 | // make this directory be a key so it's unique. increment int so we know
106 | // for debug how many times this directory appeared
107 | mapfile[re.ReplaceAllString(element, "")]++
108 | }
109 |
110 | // sort the directory keys
111 | mapfilekeys := make([]string, len(mapfile))
112 | i := 0
113 | for key, _ := range mapfile {
114 | mapfilekeys[i] = key
115 | i++
116 | }
117 | sort.Strings(mapfilekeys)
118 | log.Printf("The list of directories with serial port device data:%v", mapfilekeys)
119 |
120 | //reRemoveManuf, _ := regexp.Compile("/manufacturer$")
121 | reNewLine, _ := regexp.Compile("\n")
122 |
123 | // loop on unique directories
124 | for _, directory := range mapfilekeys {
125 |
126 | if len(directory) == 0 {
127 | continue
128 | }
129 |
130 | // search folder that had manufacturer file in it
131 | log.Printf("\tDirectory searching: %v", directory)
132 |
133 | // for each manufacturer or product file, we need to read the val from the file
134 | // but more importantly find the tty ports for this directory
135 |
136 | // for example, for the TinyG v9 which creates 2 ports, the cmd:
137 | // find /sys/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/ -name tty[AU]* -print
138 | // will result in:
139 | /*
140 | /sys/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3:1.0/tty/ttyACM0
141 | /sys/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3:1.2/tty/ttyACM1
142 | */
143 |
144 | // figure out the directory
145 | //directory := reRemoveManuf.ReplaceAllString(element, "")
146 |
147 | // read the device class so we can remove stuff we don't want like hubs
148 | deviceClassBytes, errRead4 := ioutil.ReadFile(directory + "/bDeviceClass")
149 | deviceClass := ""
150 | if errRead4 != nil {
151 | // there must be a permission issue or the file doesn't exist
152 | //log.Printf("Problem reading in serial number text file. Permissions maybe? err:%v", errRead3)
153 | //return nil, err
154 | }
155 | deviceClass = string(deviceClassBytes)
156 | deviceClass = reNewLine.ReplaceAllString(deviceClass, "")
157 |
158 | if deviceClass == "09" || deviceClass == "9" || deviceClass == "09h" {
159 | log.Printf("This is a hub, so skipping. %v", directory)
160 | continue
161 | }
162 |
163 | // read the manufacturer
164 | manufBytes, errRead := ioutil.ReadFile(directory + "/manufacturer")
165 | manuf := ""
166 | if errRead != nil {
167 | // the file could possibly just not exist, which is normal
168 | log.Printf("Problem reading in manufacturer text file. It does not exist or Permissions maybe? err:%v", errRead)
169 | //return nil, err
170 | //continue
171 | }
172 | manuf = string(manufBytes)
173 | manuf = reNewLine.ReplaceAllString(manuf, "")
174 |
175 | // read the product
176 | productBytes, errRead2 := ioutil.ReadFile(directory + "/product")
177 | product := ""
178 | if errRead2 != nil {
179 | // the file could possibly just not exist, which is normal
180 | //log.Printf("Problem reading in product text file. Permissions maybe? err:%v", errRead2)
181 | //return nil, err
182 | }
183 | product = string(productBytes)
184 | product = reNewLine.ReplaceAllString(product, "")
185 |
186 | // read the serial number
187 | serialNumBytes, errRead3 := ioutil.ReadFile(directory + "/serial")
188 | serialNum := ""
189 | if errRead3 != nil {
190 | // the file could possibly just not exist, which is normal
191 | //log.Printf("Problem reading in serial number text file. Permissions maybe? err:%v", errRead3)
192 | //return nil, err
193 | }
194 | serialNum = string(serialNumBytes)
195 | serialNum = reNewLine.ReplaceAllString(serialNum, "")
196 |
197 | // read idvendor
198 | idVendorBytes, _ := ioutil.ReadFile(directory + "/idVendor")
199 | idVendor := ""
200 | idVendor = reNewLine.ReplaceAllString(string(idVendorBytes), "")
201 |
202 | // read idProduct
203 | idProductBytes, _ := ioutil.ReadFile(directory + "/idProduct")
204 | idProduct := ""
205 | idProduct = reNewLine.ReplaceAllString(string(idProductBytes), "")
206 |
207 | log.Printf("%v : %v (%v) DevClass:%v", manuf, product, serialNum, deviceClass)
208 |
209 | // -name tty[AU]* -print
210 | filesTty := findDirs(directory, "^tty(A|U).*")
211 |
212 | // generate a unique list of tty ports below
213 | //var ttyPorts []string
214 | var m map[string]int
215 | m = make(map[string]int)
216 | for _, fileTty := range filesTty {
217 | if len(fileTty) == 0 {
218 | continue
219 | }
220 | log.Printf("\t%v", fileTty)
221 | ttyPort := regexp.MustCompile("^.*/").ReplaceAllString(fileTty, "")
222 | ttyPort = reNewLine.ReplaceAllString(ttyPort, "")
223 | m[ttyPort]++
224 | //ttyPorts = append(ttyPorts, ttyPort)
225 | }
226 | log.Printf("\tlist of ports on this. map:%v\n", m)
227 | log.Printf("\t.")
228 | //sort.Strings(ttyPorts)
229 |
230 | // create order array of ttyPorts so they're in order when
231 | // we send back via json. this makes for more human friendly reading
232 | // cuz anytime you do a hash map you can get out of order
233 | ttyPorts := []string{}
234 | for key, _ := range m {
235 | ttyPorts = append(ttyPorts, key)
236 | }
237 | sort.Strings(ttyPorts)
238 |
239 | // we now have a very nice list of ttyports for this device. many are just 1 port
240 | // however, for some advanced devices there are 2 or more ports associated and
241 | // we have this data correct now, so build out the final OsSerialPort list
242 | for _, key := range ttyPorts {
243 | listitem := OsSerialPort{
244 | Name: "/dev/" + key,
245 | FriendlyName: manuf, // + " " + product,
246 | SerialNumber: serialNum,
247 | DeviceClass: deviceClass,
248 | Manufacturer: manuf,
249 | Product: product,
250 | IdVendor: idVendor,
251 | IdProduct: idProduct,
252 | }
253 | if len(product) > 0 {
254 | listitem.FriendlyName += " " + product
255 | }
256 |
257 | listitem.FriendlyName += " (" + key + ")"
258 | listitem.FriendlyName = friendlyNameCleanup(listitem.FriendlyName)
259 |
260 | // append related tty ports
261 | for _, keyRelated := range ttyPorts {
262 | if key == keyRelated {
263 | continue
264 | }
265 | listitem.RelatedNames = append(listitem.RelatedNames, "/dev/"+keyRelated)
266 | }
267 | list = append(list, listitem)
268 | }
269 |
270 | }
271 |
272 | // sort ports by item.Name
273 | sort.Sort(ByName(list))
274 |
275 | log.Printf("Final port list: %v", list)
276 | return list, err
277 | }
278 |
279 | func findFiles(rootpath string, regexpstr string) []string {
280 |
281 | var matchedFiles []string
282 | re := regexp.MustCompile(regexpstr)
283 | numScanned := 0
284 | filepath.Walk(rootpath, func(path string, fi os.FileInfo, _ error) error {
285 | numScanned++
286 |
287 | if fi.IsDir() == false && re.MatchString(fi.Name()) == true {
288 | matchedFiles = append(matchedFiles, path)
289 | }
290 | return nil
291 | })
292 | log.Printf("Rootpath:%v, Numscanned:%v\nMatchedfiles:\n%v", rootpath, numScanned, strings.Join(matchedFiles, "\n"))
293 | return matchedFiles
294 | }
295 |
296 | func findDirs(rootpath string, regexpstr string) []string {
297 |
298 | var matchedFiles []string
299 | re := regexp.MustCompile(regexpstr)
300 | numScanned := 0
301 | filepath.Walk(rootpath, func(path string, fi os.FileInfo, _ error) error {
302 | numScanned++
303 |
304 | if fi.IsDir() == true && re.MatchString(fi.Name()) == true {
305 | matchedFiles = append(matchedFiles, path)
306 | }
307 | return nil
308 | })
309 | log.Printf("Rootpath:%v, Numscanned:%v\nMatcheddirs:\n%v", rootpath, numScanned, strings.Join(matchedFiles, "\n"))
310 | return matchedFiles
311 | }
312 |
313 | // ByAge implements sort.Interface for []Person based on
314 | // the Age field.
315 | type ByName []OsSerialPort
316 |
317 | func (a ByName) Len() int { return len(a) }
318 | func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
319 | func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
320 |
321 | func friendlyNameCleanup(fnin string) (fnout string) {
322 | // This is an industry intelligence method to just cleanup common names
323 | // out there so we don't get ugly friendly names back
324 | fnout = regexp.MustCompile("\\(www.arduino.cc\\)").ReplaceAllString(fnin, "")
325 | fnout = regexp.MustCompile("Arduino\\s+Arduino").ReplaceAllString(fnout, "Arduino")
326 | fnout = regexp.MustCompile("\\s+").ReplaceAllString(fnout, " ") // multi space to single space
327 | fnout = regexp.MustCompile("^\\s+|\\s+$").ReplaceAllString(fnout, "") // trim
328 | return fnout
329 | }
330 |
331 | func getMetaDataForPort(port string) (string, error) {
332 | // search the folder structure on linux for this port name
333 |
334 | // search /sys folder
335 | oscmd := exec.Command("find", "/sys/devices", "-name", port, "-print") //, "2>", "/dev/null")
336 |
337 | // Stdout buffer
338 | cmdOutput := &bytes.Buffer{}
339 | // Attach buffer to command
340 | oscmd.Stdout = cmdOutput
341 |
342 | err := oscmd.Start()
343 | if err != nil {
344 | log.Fatal(err)
345 | }
346 | log.Printf("Waiting for command to finish... %v", oscmd)
347 |
348 | err = oscmd.Wait()
349 |
350 | if err != nil {
351 | log.Printf("Command finished with error: %v", err)
352 | } else {
353 | log.Printf("Finished without error. Good stuff. stdout:%v", string(cmdOutput.Bytes()))
354 | // analyze stdin
355 |
356 | }
357 |
358 | return port + "coolio", nil
359 | }
360 |
361 | func getMetaDataForPortOld(port string) (string, error) {
362 | // search the folder structure on linux for this port name
363 |
364 | // search /sys folder
365 | oscmd := exec.Command("find", "/sys/devices", "-name", port, "-print") //, "2>", "/dev/null")
366 |
367 | // Stdout buffer
368 | cmdOutput := &bytes.Buffer{}
369 | // Attach buffer to command
370 | oscmd.Stdout = cmdOutput
371 |
372 | err := oscmd.Start()
373 | if err != nil {
374 | log.Fatal(err)
375 | }
376 | log.Printf("Waiting for command to finish... %v", oscmd)
377 |
378 | err = oscmd.Wait()
379 |
380 | if err != nil {
381 | log.Printf("Command finished with error: %v", err)
382 | } else {
383 | log.Printf("Finished without error. Good stuff. stdout:%v", string(cmdOutput.Bytes()))
384 | // analyze stdin
385 |
386 | }
387 |
388 | return port + "coolio", nil
389 | }
390 |
--------------------------------------------------------------------------------
/seriallist_windows.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | //"fmt"
5 | //"github.com/lxn/win"
6 | //"github.com/mattn/go-ole"
7 | //"github.com/mattn/go-ole/oleutil"
8 | "github.com/go-ole/go-ole"
9 | "github.com/go-ole/go-ole/oleutil"
10 | //"github.com/tarm/goserial"
11 | //"github.com/johnlauer/goserial"
12 | "log"
13 | "os"
14 | "strings"
15 |
16 | "github.com/facchinm/go-serial"
17 | //"encoding/binary"
18 | "strconv"
19 | "sync"
20 | //"syscall"
21 | "regexp"
22 | )
23 |
24 | var (
25 | serialListWindowsWg sync.WaitGroup
26 | isSerialListWait bool
27 | )
28 |
29 | func getMetaList() ([]OsSerialPort, os.SyscallError) {
30 | // use a queue to do this to avoid conflicts
31 | // we've been getting crashes when this getList is requested
32 | // too many times too fast. i think it's something to do with
33 | // the unsafe syscalls overwriting memory
34 |
35 | // see if we are in a waitGroup and if so exit cuz it was
36 | // causing a crash
37 | if isSerialListWait {
38 | var err os.SyscallError
39 | list := make([]OsSerialPort, 0)
40 | return list, err
41 | }
42 |
43 | // this will only block if waitgroupctr > 0. so first time
44 | // in shouldn't block
45 | serialListWindowsWg.Wait()
46 | isSerialListWait = true
47 |
48 | serialListWindowsWg.Add(1)
49 | arr, sysCallErr := getListViaWmiPnpEntity()
50 | isSerialListWait = false
51 | serialListWindowsWg.Done()
52 | //arr = make([]OsSerialPort, 0)
53 |
54 | // see if array has any data, if not fallback to the traditional
55 | // com port list model
56 | /*
57 | if len(arr) == 0 {
58 | // assume it failed
59 | arr, sysCallErr = getListViaOpen()
60 | }
61 | */
62 |
63 | // see if array has any data, if not fallback to looking at
64 | // the registry list
65 | /*
66 | arr = make([]OsSerialPort, 0)
67 | if len(arr) == 0 {
68 | // assume it failed
69 | arr, sysCallErr = getListViaRegistry()
70 | }
71 | */
72 |
73 | return arr, sysCallErr
74 | }
75 |
76 | func getListSynchronously() {
77 |
78 | }
79 |
80 | func getListViaWmiPnpEntity() ([]OsSerialPort, os.SyscallError) {
81 |
82 | //log.Println("Doing getListViaWmiPnpEntity()")
83 |
84 | // this method panics a lot and i'm not sure why, just catch
85 | // the panic and return empty list
86 | defer func() {
87 | if e := recover(); e != nil {
88 | // e is the interface{} typed-value we passed to panic()
89 | log.Println("Got panic: ", e) // Prints "Whoops: boom!"
90 | }
91 | }()
92 |
93 | var err os.SyscallError
94 |
95 | //var friendlyName string
96 |
97 | // init COM, oh yeah
98 | ole.CoInitialize(0)
99 | defer ole.CoUninitialize()
100 |
101 | unknown, _ := oleutil.CreateObject("WbemScripting.SWbemLocator")
102 | defer unknown.Release()
103 |
104 | wmi, _ := unknown.QueryInterface(ole.IID_IDispatch)
105 | defer wmi.Release()
106 |
107 | // service is a SWbemServices
108 | serviceRaw, _ := oleutil.CallMethod(wmi, "ConnectServer")
109 | service := serviceRaw.ToIDispatch()
110 | defer service.Release()
111 |
112 | // result is a SWBemObjectSet
113 | //pname := syscall.StringToUTF16("SELECT * FROM Win32_PnPEntity where Name like '%" + "COM35" + "%'")
114 | pname := "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0 and Name like '%(COM%'"
115 | //pname := "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0"
116 | resultRaw, err2 := oleutil.CallMethod(service, "ExecQuery", pname)
117 | //log.Println("Got result from oleutil.CallMethod")
118 | if err2 != nil {
119 | // we got back an error or empty list
120 | log.Printf("Got an error back from oleutil.CallMethod. err:%v", err2)
121 | return nil, err
122 | }
123 |
124 | result := resultRaw.ToIDispatch()
125 | defer result.Release()
126 |
127 | countVar, _ := oleutil.GetProperty(result, "Count")
128 | count := int(countVar.Val)
129 |
130 | list := make([]OsSerialPort, count)
131 |
132 | for i := 0; i < count; i++ {
133 |
134 | // items we're looping thru look like below and
135 | // thus we can query for any of these names
136 | /*
137 | __GENUS : 2
138 | __CLASS : Win32_PnPEntity
139 | __SUPERCLASS : CIM_LogicalDevice
140 | __DYNASTY : CIM_ManagedSystemElement
141 | __RELPATH : Win32_PnPEntity.DeviceID="USB\\VID_1D50&PID_606D&MI_02\\6&2F09EA14&0&0002"
142 | __PROPERTY_COUNT : 24
143 | __DERIVATION : {CIM_LogicalDevice, CIM_LogicalElement, CIM_ManagedSystemElement}
144 | __SERVER : JOHN-ATIV
145 | __NAMESPACE : root\cimv2
146 | __PATH : \\JOHN-ATIV\root\cimv2:Win32_PnPEntity.DeviceID="USB\\VID_1D50&PID_606D&MI_02\\6&2F09EA14
147 | &0&0002"
148 | Availability :
149 | Caption : TinyG v2 (Data Channel) (COM12)
150 | ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318}
151 | CompatibleID : {USB\Class_02&SubClass_02&Prot_01, USB\Class_02&SubClass_02, USB\Class_02}
152 | ConfigManagerErrorCode : 0
153 | ConfigManagerUserConfig : False
154 | CreationClassName : Win32_PnPEntity
155 | Description : TinyG v2 (Data Channel)
156 | DeviceID : USB\VID_1D50&PID_606D&MI_02\6&2F09EA14&0&0002
157 | ErrorCleared :
158 | ErrorDescription :
159 | HardwareID : {USB\VID_1D50&PID_606D&REV_0097&MI_02, USB\VID_1D50&PID_606D&MI_02}
160 | InstallDate :
161 | LastErrorCode :
162 | Manufacturer : Synthetos (www.synthetos.com)
163 | Name : TinyG v2 (Data Channel) (COM12)
164 | PNPDeviceID : USB\VID_1D50&PID_606D&MI_02\6&2F09EA14&0&0002
165 | PowerManagementCapabilities :
166 | PowerManagementSupported :
167 | Service : usbser
168 | Status : OK
169 | StatusInfo :
170 | SystemCreationClassName : Win32_ComputerSystem
171 | SystemName : JOHN-ATIV
172 | PSComputerName : JOHN-ATIV
173 | */
174 |
175 | // item is a SWbemObject, but really a Win32_Process
176 | itemRaw, _ := oleutil.CallMethod(result, "ItemIndex", i)
177 | item := itemRaw.ToIDispatch()
178 | defer item.Release()
179 |
180 | asString, _ := oleutil.GetProperty(item, "Name")
181 |
182 | //log.Println(asString.ToString())
183 |
184 | // get the com port
185 | //if false {
186 | s := strings.Split(asString.ToString(), "(COM")[1]
187 | s = "COM" + s
188 | s = strings.Split(s, ")")[0]
189 | list[i].Name = s
190 | list[i].FriendlyName = asString.ToString()
191 | //}
192 |
193 | // get the deviceid so we can figure out related ports
194 | // it will look similar to
195 | // USB\VID_1D50&PID_606D&MI_00\6&2F09EA14&0&0000
196 | deviceIdStr, _ := oleutil.GetProperty(item, "DeviceID")
197 | devIdItems := strings.Split(deviceIdStr.ToString(), "&")
198 | log.Printf("DeviceId elements:%v", devIdItems)
199 | if len(devIdItems) > 3 {
200 | list[i].SerialNumber = devIdItems[3]
201 | //list[i].IdProduct = strings.Replace(devIdItems[1], "PID_", "", 1)
202 | //list[i].IdVendor = strings.Replace(devIdItems[0], "USB\\VID_", "", 1)
203 | } else {
204 | list[i].SerialNumber = deviceIdStr.ToString()
205 | }
206 |
207 | pidMatch := regexp.MustCompile("PID_(....)").FindAllStringSubmatch(deviceIdStr.ToString(), -1)
208 | if len(pidMatch) > 0 {
209 | if len(pidMatch[0]) > 1 {
210 | list[i].IdProduct = pidMatch[0][1]
211 | }
212 | }
213 | vidMatch := regexp.MustCompile("VID_(....)").FindAllStringSubmatch(deviceIdStr.ToString(), -1)
214 | if len(vidMatch) > 0 {
215 | if len(vidMatch[0]) > 1 {
216 | list[i].IdVendor = vidMatch[0][1]
217 | }
218 | }
219 |
220 | manufStr, _ := oleutil.GetProperty(item, "Manufacturer")
221 | list[i].Manufacturer = manufStr.ToString()
222 | descStr, _ := oleutil.GetProperty(item, "Description")
223 | list[i].Product = descStr.ToString()
224 | //classStr, _ := oleutil.GetProperty(item, "CreationClassName")
225 | //list[i].DeviceClass = classStr.ToString()
226 |
227 | }
228 |
229 | for index, element := range list {
230 |
231 | log.Printf("index:%v, name:%v, friendly:%v ", index, element.Name, element.FriendlyName)
232 | log.Printf("pid:%v, vid:%v", element.IdProduct, element.IdVendor)
233 |
234 | for index2, element2 := range list {
235 | if index == index2 {
236 | continue
237 | }
238 | if element.SerialNumber == element2.SerialNumber {
239 | log.Printf("Found related element1:%v, element2:%v", element, element2)
240 | list[index].RelatedNames = append(list[index].RelatedNames, element2.Name)
241 | }
242 | }
243 |
244 | }
245 |
246 | return list, err
247 | }
248 |
249 | func getListViaOpen() ([]OsSerialPort, os.SyscallError) {
250 |
251 | log.Println("Doing getListViaOpen(). Will try to open COM1 to COM99.")
252 | var err os.SyscallError
253 | list := make([]OsSerialPort, 100)
254 | var igood int = 0
255 | for i := 0; i < 100; i++ {
256 | prtname := "COM" + strconv.Itoa(i)
257 | //conf := &serial.Config{Name: prtname, Baud: 1200}
258 | mode := &serial.Mode{
259 | BaudRate: 1200,
260 | Vmin: 0,
261 | Vtimeout: 10,
262 | }
263 | sp, err := serial.OpenPort(prtname, mode)
264 | //log.Println("Just tried to open port", prtname)
265 | if err == nil {
266 | //log.Println("Able to open port", prtname)
267 | list[igood].Name = prtname
268 | sp.Close()
269 | list[igood].FriendlyName = prtname
270 | //list[igood].FriendlyName = getFriendlyName(prtname)
271 | igood++
272 | }
273 | }
274 | for index, element := range list[:igood] {
275 | log.Println("index ", index, " element ", element.Name+
276 | " friendly ", element.FriendlyName)
277 | }
278 | return list[:igood], err
279 | }
280 |
281 | /*
282 | func getListViaRegistry() ([]OsSerialPort, os.SyscallError) {
283 |
284 | log.Println("Doing getListViaRegistry()")
285 | var err os.SyscallError
286 | var root win.HKEY
287 | rootpath, _ := syscall.UTF16PtrFromString("HARDWARE\\DEVICEMAP\\SERIALCOMM")
288 | log.Println(win.RegOpenKeyEx(win.HKEY_LOCAL_MACHINE, rootpath, 0, win.KEY_READ, &root))
289 |
290 | var name_length uint32 = 72
291 | var key_type uint32
292 | var lpDataLength uint32 = 72
293 | var zero_uint uint32 = 0
294 | name := make([]uint16, 72)
295 | lpData := make([]byte, 72)
296 |
297 | var retcode int32
298 | retcode = 0
299 | for retcode == 0 {
300 | retcode = win.RegEnumValue(root, zero_uint, &name[0], &name_length, nil, &key_type, &lpData[0], &lpDataLength)
301 | log.Println("Retcode:", retcode)
302 | log.Println("syscall name: "+syscall.UTF16ToString(name[:name_length-2])+"---- name_length:", name_length)
303 | log.Println("syscall lpdata:"+string(lpData[:lpDataLength-2])+"--- lpDataLength:", lpDataLength)
304 | //log.Println()
305 | zero_uint++
306 | }
307 | win.RegCloseKey(root)
308 | win.RegOpenKeyEx(win.HKEY_LOCAL_MACHINE, rootpath, 0, win.KEY_READ, &root)
309 |
310 | list := make([]OsSerialPort, zero_uint)
311 | var i uint32 = 0
312 | for i = 0; i < zero_uint; i++ {
313 | win.RegEnumValue(root, i-1, &name[0], &name_length, nil, &key_type, &lpData[0], &lpDataLength)
314 | //name := string(lpData[:lpDataLength])
315 | //name = name[:strings.Index(name, '\0')]
316 | //nameb := []byte(strings.TrimSpace(string(lpData[:lpDataLength])))
317 | //list[i].Name = string(nameb)
318 | //list[i].Name = string(name[:strings.Index(name, "\0")])
319 | //list[i].Name = fmt.Sprintf("%s", string(lpData[:lpDataLength-1]))
320 | pname := make([]uint16, (lpDataLength-2)/2)
321 | pname = convertByteArrayToUint16Array(lpData[:lpDataLength-2], lpDataLength-2)
322 | list[i].Name = syscall.UTF16ToString(pname)
323 | log.Println("The length of the name is:", len(list[i].Name))
324 | log.Println("list[i].Name=" + list[i].Name + "---")
325 | //list[i].FriendlyName = getFriendlyName(list[i].Name)
326 | list[i].FriendlyName = getFriendlyName("COM34")
327 | }
328 | win.RegCloseKey(root)
329 | return list, err
330 | }
331 | */
332 |
333 | func convertByteArrayToUint16Array(b []byte, mylen uint32) []uint16 {
334 |
335 | log.Println("converting. len:", mylen)
336 | var i uint32
337 | ret := make([]uint16, mylen/2)
338 | for i = 0; i < mylen; i += 2 {
339 | //ret[i/2] = binary.LittleEndian.Uint16(b[i : i+1])
340 | ret[i/2] = uint16(b[i]) | uint16(b[i+1])<<8
341 | }
342 | return ret
343 | }
344 |
345 | func getFriendlyName(portname string) string {
346 |
347 | // this method panics a lot and i'm not sure why, just catch
348 | // the panic and return empty list
349 | defer func() {
350 | if e := recover(); e != nil {
351 | // e is the interface{} typed-value we passed to panic()
352 | log.Println("Got panic: ", e) // Prints "Whoops: boom!"
353 | }
354 | }()
355 |
356 | var friendlyName string
357 |
358 | // init COM, oh yeah
359 | ole.CoInitialize(0)
360 | defer ole.CoUninitialize()
361 |
362 | unknown, _ := oleutil.CreateObject("WbemScripting.SWbemLocator")
363 | defer unknown.Release()
364 |
365 | wmi, _ := unknown.QueryInterface(ole.IID_IDispatch)
366 | defer wmi.Release()
367 |
368 | // service is a SWbemServices
369 | serviceRaw, _ := oleutil.CallMethod(wmi, "ConnectServer")
370 | service := serviceRaw.ToIDispatch()
371 | defer service.Release()
372 |
373 | // result is a SWBemObjectSet
374 | //pname := syscall.StringToUTF16("SELECT * FROM Win32_PnPEntity where Name like '%" + "COM35" + "%'")
375 | pname := "SELECT * FROM Win32_PnPEntity where Name like '%" + portname + "%'"
376 | resultRaw, _ := oleutil.CallMethod(service, "ExecQuery", pname)
377 | result := resultRaw.ToIDispatch()
378 | defer result.Release()
379 |
380 | countVar, _ := oleutil.GetProperty(result, "Count")
381 | count := int(countVar.Val)
382 |
383 | for i := 0; i < count; i++ {
384 | // item is a SWbemObject, but really a Win32_Process
385 | itemRaw, _ := oleutil.CallMethod(result, "ItemIndex", i)
386 | item := itemRaw.ToIDispatch()
387 | defer item.Release()
388 |
389 | asString, _ := oleutil.GetProperty(item, "Name")
390 |
391 | println(asString.ToString())
392 | friendlyName = asString.ToString()
393 | }
394 |
395 | return friendlyName
396 | }
397 |
--------------------------------------------------------------------------------
/usb.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | // "log"
5 | "encoding/json"
6 | // "runtime"
7 | )
8 |
9 | type UsbItem struct {
10 | Id string
11 | BusNum string
12 | DeviceNum string
13 | VidPid string
14 | Name string
15 | MaxPower string
16 | IsVideo bool
17 | VideoResolutions []VideoResolution
18 | IsAudio bool
19 | SerialNumber string
20 | DeviceClass string
21 | Vendor string
22 | Product string
23 | Pid string
24 | Vid string
25 | }
26 |
27 | type VideoResolution struct {
28 | Width int
29 | Height int
30 | }
31 |
32 | type UsbListCmd struct {
33 | UsbListStatus string
34 | UsbList []UsbItem
35 | }
36 |
37 | func GetUsbList() []UsbItem {
38 | // log.Println("Running main GetUsbList")
39 |
40 | usbList := []UsbItem{}
41 |
42 | // the call to getUsbList() is now handle by the usb_*.go files
43 | //if runtime.GOOS == "linux" && runtime.GOARCH == "arm" {
44 | usbList = getUsbList()
45 | //}
46 |
47 | return usbList
48 | }
49 |
50 | func SendUsbList() {
51 |
52 | finalList := UsbListCmd{}
53 | finalList.UsbListStatus = "Done"
54 | finalList.UsbList = GetUsbList()
55 | mapB, _ := json.Marshal(finalList)
56 | h.broadcastSys <- mapB
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/usb_linux.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "os"
7 | "os/exec"
8 | "regexp"
9 | "strconv"
10 | "strings"
11 | )
12 |
13 | func getUsbList() []UsbItem {
14 | log.Println("Running real linux arm function")
15 |
16 | usbList := []UsbItem{}
17 |
18 | result, err := execShellCmd("lsusb")
19 | if err != nil {
20 | log.Println("err", err)
21 | }
22 | //log.Println("result", result)
23 |
24 | // we will get results like
25 | /*
26 | Bus 001 Device 005: ID 1e4e:0102 Cubeternet GL-UPC822 UVC WebCam
27 | Bus 001 Device 004: ID 090c:037c Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.)
28 | Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
29 | Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
30 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
31 | */
32 |
33 | // parse per line
34 | lineArr := strings.Split(result, "\n")
35 | for _, element := range lineArr {
36 | log.Println("line:", element)
37 | re := regexp.MustCompile("Bus (\\d+) Device (\\d+): ID (.*?):(.*?) (.*)")
38 | matches := re.FindStringSubmatch(element)
39 | //log.Println(matches)
40 | if len(matches) > 4 {
41 | usbitem := UsbItem{}
42 | usbitem.BusNum = matches[1]
43 | usbitem.DeviceNum = matches[2]
44 | usbitem.Vid = matches[3]
45 | usbitem.Pid = matches[4]
46 | usbitem.VidPid = usbitem.Vid + ":" + usbitem.Pid
47 | usbitem.Name = matches[5]
48 | usbList = append(usbList, usbitem)
49 |
50 | }
51 | }
52 |
53 | reMaxPower := regexp.MustCompile("MaxPower\\s+(\\S+)")
54 | reVideo := regexp.MustCompile("Video")
55 | reAudio := regexp.MustCompile("Audio")
56 | reVidDescriptor := regexp.MustCompile("VideoStreaming Interface Descriptor:")
57 | reWidth := regexp.MustCompile("wWidth\\s+(\\S+)")
58 | reHeight := regexp.MustCompile("wHeight\\s+(\\S+)")
59 | reDeviceClass := regexp.MustCompile("bDeviceClass\\s+(.+)$")
60 | reVendor := regexp.MustCompile("idVendor\\s+(.+)$")
61 | reProduct := regexp.MustCompile("idProduct\\s+(.+)$")
62 | reHex := regexp.MustCompile("0x\\S+")
63 |
64 | // loop thru all ports now
65 | // var usbitem UsbItem
66 | for index, item := range usbList {
67 | // get detailed info
68 | cmd := "lsusb -v -s " + item.BusNum + ":" + item.DeviceNum
69 | log.Println("cmd:", cmd)
70 | info, _ := execShellCmd(cmd)
71 | //log.Println("info", info)
72 |
73 | lines := strings.Split(info, "\n")
74 | for _, line := range lines {
75 |
76 | // see amperage data
77 | matches := reMaxPower.FindStringSubmatch(line)
78 | if len(matches) > 1 {
79 | log.Println("found MaxPower", matches[1])
80 | usbList[index].MaxPower = matches[1]
81 | }
82 |
83 | // see if it is video
84 | isvid := reVideo.MatchString(line)
85 | if isvid {
86 | usbList[index].IsVideo = true
87 | }
88 |
89 | // see if it has audio (could be standalone audio or video with audio
90 | isaudio := reAudio.MatchString(line)
91 | if isaudio {
92 | usbList[index].IsAudio = true
93 | }
94 |
95 | // see if we have a video resolution
96 | isviddescriptor := reVidDescriptor.MatchString(line)
97 | if isviddescriptor {
98 | if len(usbList[index].VideoResolutions) == 0 {
99 | usbList[index].VideoResolutions = []VideoResolution{}
100 | }
101 |
102 | // we found a descriptor, so a width/height should be coming
103 | vidRes := VideoResolution{}
104 | usbList[index].VideoResolutions = append(usbList[index].VideoResolutions, vidRes)
105 | }
106 |
107 | // if we hit a width/height, just set it to the last item in the VideoResolutions array
108 | matches = reWidth.FindStringSubmatch(line)
109 | if len(matches) > 1 {
110 | i, _ := strconv.Atoi(matches[1])
111 | usbList[index].VideoResolutions[len(usbList[index].VideoResolutions)-1].Width = i
112 | }
113 | matches = reHeight.FindStringSubmatch(line)
114 | if len(matches) > 1 {
115 | i, _ := strconv.Atoi(matches[1])
116 | usbList[index].VideoResolutions[len(usbList[index].VideoResolutions)-1].Height = i
117 | }
118 |
119 | // get device class
120 | matches = reDeviceClass.FindStringSubmatch(line)
121 | if len(matches) > 1 {
122 | usbList[index].DeviceClass = matches[1]
123 | }
124 |
125 | // get Product
126 | matches = reProduct.FindStringSubmatch(line)
127 | if len(matches) > 1 {
128 | usbList[index].Product = matches[1]
129 | usbList[index].Product = reHex.ReplaceAllString(usbList[index].Product, "")
130 | usbList[index].Product = strings.TrimSpace(usbList[index].Product)
131 | }
132 |
133 | // get Vendor
134 | matches = reVendor.FindStringSubmatch(line)
135 | if len(matches) > 1 {
136 | usbList[index].Vendor = matches[1]
137 | usbList[index].Vendor = reHex.ReplaceAllString(usbList[index].Vendor, "")
138 | usbList[index].Vendor = strings.TrimSpace(usbList[index].Vendor)
139 | }
140 | }
141 |
142 | // override the video class
143 | if usbList[index].IsVideo {
144 | usbList[index].DeviceClass = "Video"
145 | }
146 | }
147 |
148 | bout, _ := json.Marshal(usbList)
149 | log.Println("Final UsbList:", string(bout))
150 | return usbList
151 | }
152 |
153 | func execShellCmd(line string) (string, error) {
154 | shell := os.Getenv("SHELL")
155 | oscmd = exec.Command(shell, "-c", line)
156 | cmdOutput, err := oscmd.CombinedOutput()
157 | if err != nil {
158 | log.Println("err running shell cmd", err)
159 | return string(cmdOutput), err
160 | }
161 | //log.Println("shell success output", string(cmdOutput))
162 | return string(cmdOutput), nil
163 | }
164 |
--------------------------------------------------------------------------------
/usb_other.go:
--------------------------------------------------------------------------------
1 | // +build !linux
2 |
3 | package main
4 |
5 | func getUsbList() []UsbItem {
6 | return nil
7 | }
8 |
--------------------------------------------------------------------------------