├── .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 |
87 | 88 | 89 |
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 |
403 | 404 | 405 |
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 | --------------------------------------------------------------------------------