├── .gitignore
├── .gitmodules
├── .project
├── LICENSE.md
├── README.md
├── altbuild.sh
├── bufferflow.go
├── bufferflow_default.go
├── bufferflow_dummypause.go
├── bufferflow_grbl.go
├── bufferflow_tinyg.go
├── compile_webidebridge.sh
├── config.ini
├── conn.go
├── discovery.go
├── download.go
├── dummy.go
├── fakecerts
├── cert.pem
└── key.pem
├── home.html
├── hub.go
├── main.go
├── programmer.go
├── queue.go
├── serial.go
├── seriallist.go
├── seriallist_darwin.go
├── seriallist_linux.go
├── seriallist_windows.go
├── serialport.go
├── snapshot
├── README.md
└── downloads.md
├── trayicon.go
├── trayicon_linux_arm.go
├── update.go
└── utilities.go
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | bufferflow_tinyg_old.md
3 |
4 | serial-port-json-server
5 |
6 | snapshot/*
7 | public/
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/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 | import (
4 | //"log"
5 | //"time"
6 | )
7 |
8 | var availableBufferAlgorithms = []string{"default", "tinyg", "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) // 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 | SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool // implement this method
27 | SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool // implement this method
28 | SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool // implement this method
29 | SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool // implement this method
30 | SeeIfSpecificCommandsReturnNoResponse(cmd string) bool // implement this method
31 | ReleaseLock() // implement this method
32 | IsBufferGloballySendingBackIncomingData() bool // implement this method
33 | Close() // implement this method
34 | }
35 |
36 | /*data packets returned to client*/
37 | type DataCmdComplete struct {
38 | Cmd string
39 | Id string
40 | P string
41 | BufSize int `json:"-"`
42 | D string `json:"-"`
43 | }
44 |
45 | type DataPerLine struct {
46 | P string
47 | D string
48 | }
49 |
--------------------------------------------------------------------------------
/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) BlockUntilReady(cmd string, id string) (bool, bool) {
22 | //log.Printf("BlockUntilReady() start\n")
23 | return true, false
24 | }
25 |
26 | func (b *BufferflowDefault) OnIncomingData(data string) {
27 | //log.Printf("OnIncomingData() start. data:%v\n", data)
28 | }
29 |
30 | // Clean out b.sem so it can truly block
31 | func (b *BufferflowDefault) ClearOutSemaphore() {
32 | }
33 |
34 | func (b *BufferflowDefault) BreakApartCommands(cmd string) []string {
35 | return []string{cmd}
36 | }
37 |
38 | func (b *BufferflowDefault) Pause() {
39 | return
40 | }
41 |
42 | func (b *BufferflowDefault) Unpause() {
43 | return
44 | }
45 |
46 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
47 | return false
48 | }
49 |
50 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
51 | return false
52 | }
53 |
54 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
55 | return false
56 | }
57 |
58 | func (b *BufferflowDefault) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
59 | return false
60 | }
61 |
62 | func (b *BufferflowDefault) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
63 | return false
64 | }
65 |
66 | func (b *BufferflowDefault) ReleaseLock() {
67 | }
68 |
69 | func (b *BufferflowDefault) IsBufferGloballySendingBackIncomingData() bool {
70 | return false
71 | }
72 |
73 | func (b *BufferflowDefault) Close() {
74 | }
75 |
--------------------------------------------------------------------------------
/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) BlockUntilReady(cmd string, id string) (bool, bool) {
19 | log.Printf("BlockUntilReady() start. numLines:%v\n", b.NumLines)
20 | log.Printf("buffer:%v\n", b)
21 | //for b.Paused {
22 | log.Println("We are paused for 3 seconds. Yeilding send.")
23 | time.Sleep(3000 * time.Millisecond)
24 | //}
25 | log.Printf("BlockUntilReady() end\n")
26 | return true, false
27 | }
28 |
29 | func (b *BufferflowDummypause) OnIncomingData(data string) {
30 | log.Printf("OnIncomingData() start. data:%v\n", data)
31 | b.NumLines++
32 | //time.Sleep(3000 * time.Millisecond)
33 | log.Printf("OnIncomingData() end. numLines:%v\n", b.NumLines)
34 | }
35 |
36 | // Clean out b.sem so it can truly block
37 | func (b *BufferflowDummypause) ClearOutSemaphore() {
38 | }
39 |
40 | func (b *BufferflowDummypause) BreakApartCommands(cmd string) []string {
41 | return []string{cmd}
42 | }
43 |
44 | func (b *BufferflowDummypause) Pause() {
45 | return
46 | }
47 |
48 | func (b *BufferflowDummypause) Unpause() {
49 | return
50 | }
51 |
52 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
53 | return false
54 | }
55 |
56 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
57 | return false
58 | }
59 |
60 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
61 | return false
62 | }
63 |
64 | func (b *BufferflowDummypause) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
65 | return false
66 | }
67 |
68 | func (b *BufferflowDummypause) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
69 | /*
70 | // remove comments
71 | cmd = b.reComment.ReplaceAllString(cmd, "")
72 | cmd = b.reComment2.ReplaceAllString(cmd, "")
73 | if match := b.reNoResponse.MatchString(cmd); match {
74 | log.Printf("Found cmd that does not get a response from TinyG. cmd:%v\n", cmd)
75 | return true
76 | }
77 | */
78 | return false
79 | }
80 |
81 | func (b *BufferflowDummypause) ReleaseLock() {
82 | }
83 |
84 | func (b *BufferflowDummypause) IsBufferGloballySendingBackIncomingData() bool {
85 | return false
86 | }
87 |
88 | func (b *BufferflowDummypause) Close() {
89 | }
90 |
--------------------------------------------------------------------------------
/bufferflow_grbl.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "regexp"
7 | "strconv"
8 | "strings"
9 | "time"
10 | "sync"
11 | )
12 |
13 | type BufferflowGrbl 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 *BufferflowGrbl) Init() {
39 | b.lock = &sync.Mutex{}
40 | b.SetPaused(false, 1)
41 |
42 | log.Println("Initting GRBL 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("^Grbl")
55 | b.qry, _ = regexp.Compile("\\?")
56 | b.rpt, _ = regexp.Compile("^<")
57 |
58 | //initialize query loop
59 | b.rptQueryLoop(b.parent_serport)
60 | }
61 |
62 | func (b *BufferflowGrbl) BlockUntilReady(cmd string, id string) (bool, bool) {
63 | log.Printf("BlockUntilReady() start\n")
64 |
65 | b.q.Push(cmd, id)
66 |
67 | log.Printf("New line length: %v, buffer size increased to:%v\n", len(cmd), b.q.LenOfCmds())
68 | log.Println(b.q)
69 |
70 | if b.q.LenOfCmds() >= b.BufferMax {
71 | b.SetPaused(true, 0)
72 | log.Printf("Buffer Full - Will send this command when space is available")
73 | }
74 |
75 | if b.GetPaused() {
76 | log.Println("It appears we are being asked to pause, so we will wait on b.sem")
77 | // We are being asked to pause our sending of commands
78 |
79 | // clear all b.sem signals so when we block below, we truly block
80 | b.ClearOutSemaphore()
81 |
82 | log.Println("Blocking on b.sem until told from OnIncomingData to go")
83 | unblockType, ok := <-b.sem // will block until told from OnIncomingData to go
84 |
85 | log.Printf("Done blocking cuz got b.sem semaphore release. ok:%v, unblockType:%v\n", ok, unblockType)
86 |
87 | // we get an unblockType of 1 for normal unblocks
88 | // we get an unblockType of 2 when we're being asked to wipe the buffer, i.e. from a % cmd
89 | if unblockType == 2 {
90 | log.Println("This was an unblock of type 2, which means we're being asked to wipe internal buffer. so return false.")
91 | // returning false asks the calling method to wipe the serial send once
92 | // this function returns
93 | return false, false
94 | }
95 |
96 | log.Printf("BlockUntilReady(cmd:%v, id:%v) end\n", cmd, id)
97 | }
98 | return true, true
99 | }
100 |
101 | func (b *BufferflowGrbl) OnIncomingData(data string) {
102 | log.Printf("OnIncomingData() start. data:%q\n", data)
103 |
104 | b.LatestData += data
105 |
106 | //it was found ok was only received with status responses until the grbl buffer is full.
107 | //b.LatestData = regexp.MustCompile(">\\r\\nok").ReplaceAllString(b.LatestData, ">") //remove oks from status responses
108 |
109 | arrLines := b.reNewLine.Split(b.LatestData, -1)
110 | log.Printf("arrLines:%v\n", arrLines)
111 |
112 | if len(arrLines) > 1 {
113 | // that means we found a newline and have 2 or greater array values
114 | // so we need to analyze our arrLines[] lines but keep last line
115 | // for next trip into OnIncomingData
116 | log.Printf("We have data lines to analyze. numLines:%v\n", len(arrLines))
117 |
118 | } else {
119 | // we don't have a newline yet, so just exit and move on
120 | // we don't have to reset b.LatestData because we ended up
121 | // without any newlines so maybe we will next time into this method
122 | log.Printf("Did not find newline yet, so nothing to analyze\n")
123 | return
124 | }
125 |
126 | // if we made it here we have lines to analyze
127 | // so analyze all of them except the last line
128 | for index, element := range arrLines[:len(arrLines)-1] {
129 | log.Printf("Working on element:%v, index:%v", element, index)
130 |
131 | //check for 'ok' or 'error' response indicating a gcode line has been processed
132 | if b.ok.MatchString(element) || b.err.MatchString(element){
133 | if b.q.Len() > 0 {
134 | doneCmd, id := b.q.Poll()
135 |
136 | if b.ok.MatchString(element){
137 | // Send cmd:"Complete" back
138 | m := DataCmdComplete{"Complete", id, b.Port, b.q.LenOfCmds(), doneCmd}
139 | bm, err := json.Marshal(m)
140 | if err == nil {
141 | h.broadcastSys <- bm
142 | }
143 | } else if b.err.MatchString(element){
144 | // Send cmd:"Error" back
145 | log.Printf("Error Response Received:%v, id:%v", doneCmd, id)
146 | m := DataCmdComplete{"Error", id, b.Port, b.q.LenOfCmds(), doneCmd}
147 | bm, err := json.Marshal(m)
148 | if err == nil {
149 | h.broadcastSys <- bm
150 | }
151 | }
152 |
153 | log.Printf("Buffer decreased to itemCnt:%v, lenOfBuf:%v\n", b.q.Len(), b.q.LenOfCmds())
154 | } else {
155 | 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!!!!")
156 | }
157 |
158 | if b.q.LenOfCmds() < b.BufferMax {
159 |
160 | log.Printf("Grbl just completed a line of gcode\n")
161 |
162 | // if we are paused, tell us to unpause cuz we have clean buffer room now
163 | if b.GetPaused() { b.SetPaused(false, 1) }
164 | }
165 |
166 | //check for the grbl init line indicating the arduino is ready to accept commands
167 | //could also pull version from this string, if we find a need for that later
168 | } else if b.initline.MatchString(element) {
169 | //grbl init line received, clear anything from current buffer and unpause
170 | b.LocalBufferWipe(b.parent_serport)
171 |
172 | //unpause buffer but wipe the command in the queue as grbl has restarted.
173 | if b.GetPaused() { b.SetPaused(false, 2) }
174 |
175 | b.version = element //save element in version
176 |
177 | //Check for report output, compare to last report output, if different return to client to update status; otherwise ignore status.
178 | } else if b.rpt.MatchString(element){
179 | if(element == b.LastStatus){
180 | log.Println("Grbl status has not changed, not reporting to client")
181 | continue //skip this element as the cnc position has not changed, and move on to the next element.
182 | }
183 |
184 | b.LastStatus = element //if we make it here something has changed with the status string and laststatus needs updating
185 | }
186 |
187 | // handle communication back to client
188 | m := DataPerLine{b.Port, element + "\n"}
189 | bm, err := json.Marshal(m)
190 | if err == nil {
191 | h.broadcastSys <- bm
192 | }
193 |
194 | } // for loop
195 |
196 | // now wipe the LatestData to only have the last line that we did not analyze
197 | // because we didn't know/think that was a full command yet
198 | b.LatestData = arrLines[len(arrLines)-1]
199 |
200 | //time.Sleep(3000 * time.Millisecond)
201 | log.Printf("OnIncomingData() end.\n")
202 | }
203 |
204 | // Clean out b.sem so it can truly block
205 | func (b *BufferflowGrbl) ClearOutSemaphore() {
206 | ctr := 0
207 |
208 | keepLooping := true
209 | for keepLooping {
210 | select {
211 | case d, ok := <-b.sem:
212 | log.Printf("Consuming b.sem queue to clear it before we block. ok:%v, d:%v\n", ok, string(d))
213 | ctr++
214 | if ok == false {
215 | keepLooping = false
216 | }
217 | default:
218 | keepLooping = false
219 | log.Println("Hit default in select clause")
220 | }
221 | }
222 | log.Printf("Done consuming b.sem queue so we're good to block on it now. ctr:%v\n", ctr)
223 | // ok, all b.sem signals are now consumed into la-la land
224 | }
225 |
226 | func (b *BufferflowGrbl) BreakApartCommands(cmd string) []string {
227 |
228 | // add newline after !~%
229 | log.Printf("Command Before Break-Apart: %q\n", cmd)
230 |
231 | cmds := strings.Split(cmd, "\n")
232 | finalCmds := []string{}
233 | for _, item := range cmds {
234 | //remove comments and whitespace from item
235 | item = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(item, "")
236 | item = regexp.MustCompile(";.*").ReplaceAllString(item, "")
237 | item = strings.Replace(item," ","",-1)
238 |
239 | if item == "*init*"{ //return init string to update grbl widget when already connected to grbl
240 | m := DataPerLine{b.Port, b.version + "\n"}
241 | bm, err := json.Marshal(m)
242 | if err == nil {
243 | h.broadcastSys <- bm
244 | }
245 | } else if item == "*status*"{ //return status when client first connects to existing open port
246 | m := DataPerLine{b.Port, b.LastStatus + "\n"}
247 | bm, err := json.Marshal(m)
248 | if err == nil {
249 | h.broadcastSys <- bm
250 | }
251 | } else if item == "?"{
252 | log.Printf("Query added without newline: %q\n", item)
253 | finalCmds = append(finalCmds, item) //append query request without newline character
254 | } else if item == "%" {
255 | log.Printf("Wiping Grbl BufferFlow")
256 | b.LocalBufferWipe(b.parent_serport)
257 | //dont add this command to the list of finalCmds
258 | } else if item != "" {
259 | log.Printf("Re-adding newline to item:%v\n", item)
260 | s := item + "\n"
261 | finalCmds = append(finalCmds, s)
262 | log.Printf("New cmd item:%v\n", s)
263 | }
264 |
265 | }
266 | log.Printf("Final array of cmds after BreakApartCommands(). finalCmds:%v\n", finalCmds)
267 |
268 | return finalCmds
269 | //return []string{cmd} //do not process string
270 | }
271 |
272 | func (b *BufferflowGrbl) Pause() {
273 | b.SetPaused(true, 0)
274 | //b.BypassMode = false // turn off bypassmode in case it's on
275 | log.Println("Paused buffer on next BlockUntilReady() call")
276 | }
277 |
278 | func (b *BufferflowGrbl) Unpause() {
279 | //unpause buffer by setting paused to false and passing a 1 to b.sem
280 | b.SetPaused(false, 1)
281 | log.Println("Unpaused buffer inside BlockUntilReady() call")
282 | }
283 |
284 | func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldSkipBuffer(cmd string) bool {
285 | // remove comments
286 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
287 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
288 | if match, _ := regexp.MatchString("[!~\\?]|(\u0018)", cmd); match {
289 | log.Printf("Found cmd that should skip buffer. cmd:%v\n", cmd)
290 | return true
291 | }
292 | return false
293 | }
294 |
295 | func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldPauseBuffer(cmd string) bool {
296 | // remove comments
297 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
298 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
299 | if match, _ := regexp.MatchString("[!]", cmd); match {
300 | log.Printf("Found cmd that should pause buffer. cmd:%v\n", cmd)
301 | return true
302 | }
303 | return false
304 | }
305 |
306 | func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldUnpauseBuffer(cmd string) bool {
307 |
308 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
309 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
310 | if match, _ := regexp.MatchString("[~]", cmd); match {
311 | log.Printf("Found cmd that should unpause buffer. cmd:%v\n", cmd)
312 | return true
313 | }
314 | return false
315 | }
316 |
317 | func (b *BufferflowGrbl) SeeIfSpecificCommandsShouldWipeBuffer(cmd string) bool {
318 |
319 | //cmd = regexp.MustCompile("\\(.*?\\)").ReplaceAllString(cmd, "")
320 | //cmd = regexp.MustCompile(";.*").ReplaceAllString(cmd, "")
321 | if match, _ := regexp.MatchString("(\u0018)", cmd); match {
322 | log.Printf("Found cmd that should wipe out and reset buffer. cmd:%v\n", cmd)
323 |
324 | //b.q.Delete() //delete tracking queue, all buffered commands will be wiped.
325 |
326 | //log.Println("Buffer variables cleared for new input.")
327 | return true
328 | }
329 | return false
330 | }
331 |
332 | func (b *BufferflowGrbl) SeeIfSpecificCommandsReturnNoResponse(cmd string) bool {
333 | /*
334 | // remove comments
335 | cmd = b.reComment.ReplaceAllString(cmd, "")
336 | cmd = b.reComment2.ReplaceAllString(cmd, "")
337 | if match := b.reNoResponse.MatchString(cmd); match {
338 | log.Printf("Found cmd that does not get a response from TinyG. cmd:%v\n", cmd)
339 | return true
340 | }
341 | */
342 | return false
343 | }
344 |
345 | func (b *BufferflowGrbl) ReleaseLock() {
346 | log.Println("Lock being released in GRBL buffer")
347 |
348 | b.q.Delete()
349 |
350 | log.Println("ReleaseLock(), so we will send signal of 2 to b.sem to unpause the BlockUntilReady() thread")
351 |
352 | //release lock, send signal 2 to b.sem
353 | b.SetPaused(false, 2)
354 | }
355 |
356 | func (b *BufferflowGrbl) IsBufferGloballySendingBackIncomingData() bool {
357 | //telling json server that we are handling client responses
358 | return true
359 | }
360 |
361 | //Use this function to open a connection, write directly to serial port and close connection.
362 | //This is used for sending query requests outside of the normal buffered operations that will pause to wait for room in the grbl buffer
363 | //'?' is asynchronous to the normal buffer load and does not need to be paused when buffer full
364 | func (b *BufferflowGrbl) rptQueryLoop(p *serport){
365 | b.parent_serport = p //make note of this port for use in clearing the buffer later, on error.
366 | ticker := time.NewTicker(250 * time.Millisecond)
367 | b.quit = make(chan int)
368 | go func() {
369 | for {
370 | select {
371 | case <- ticker.C:
372 |
373 | n2, err := p.portIo.Write([]byte("?"))
374 |
375 | log.Print("Just wrote ", n2, " bytes to serial: ?")
376 |
377 | if err != nil {
378 | errstr := "Error writing to " + p.portConf.Name + " " + err.Error() + " Closing port."
379 | log.Print(errstr)
380 | h.broadcastSys <- []byte(errstr)
381 | ticker.Stop() //stop query loop if we can't write to the port
382 | break
383 | }
384 | case <- b.quit:
385 | ticker.Stop()
386 | return
387 | }
388 | }
389 | }()
390 | }
391 |
392 | func (b *BufferflowGrbl) Close() {
393 | //stop the status query loop when the serial port is closed off.
394 | log.Println("Stopping the status query loop")
395 | b.quit <- 1
396 | }
397 |
398 | // Gets the paused state of this buffer
399 | // go-routine safe.
400 | func (b *BufferflowGrbl) GetPaused() bool {
401 | b.lock.Lock()
402 | defer b.lock.Unlock()
403 | return b.Paused
404 | }
405 |
406 | // Sets the paused state of this buffer
407 | // go-routine safe.
408 | func (b *BufferflowGrbl) SetPaused(isPaused bool, semRelease int) {
409 | b.lock.Lock()
410 | defer b.lock.Unlock()
411 | b.Paused = isPaused
412 |
413 | //if we are unpausing the buffer, we need to send a signal to release the channel
414 | if isPaused == false{
415 | go func() {
416 | // sending a 2 asks BlockUntilReady() to cancel the send
417 | b.sem <- semRelease
418 | defer func() {
419 | log.Printf("Unpause Semaphore just got consumed by the BlockUntilReady()\n")
420 | }()
421 | }()
422 | }
423 | }
424 |
425 | //local version of buffer wipe loop needed to handle pseudo clear buffer (%) without passing that value on to
426 | func (b *BufferflowGrbl) LocalBufferWipe(p *serport){
427 | log.Printf("Pseudo command received to wipe grbl buffer but *not* send on to grbl controller.")
428 |
429 | // consume all stuff queued
430 | func() {
431 | ctr := 0
432 |
433 | keepLooping := true
434 | for keepLooping {
435 | select {
436 | case d, ok := <-p.sendBuffered:
437 | log.Printf("Consuming sendBuffered queue. ok:%v, d:%v, id:%v\n", ok, string(d.data), string(d.id))
438 | ctr++
439 |
440 | p.itemsInBuffer--
441 | if ok == false {
442 | keepLooping = false
443 | }
444 | default:
445 | keepLooping = false
446 | log.Println("Hit default in select clause")
447 | }
448 | }
449 | log.Printf("Done consuming sendBuffered cmds. ctr:%v\n", ctr)
450 | }()
451 |
452 | b.ReleaseLock()
453 |
454 | // let user know we wiped queue
455 | log.Printf("itemsInBuffer:%v\n", p.itemsInBuffer)
456 | h.broadcastSys <- []byte("{\"Cmd\":\"WipedQueue\",\"QCnt\":" + strconv.Itoa(p.itemsInBuffer) + ",\"Port\":\"" + p.portConf.Name + "\"}")
457 | }
458 |
--------------------------------------------------------------------------------
/compile_webidebridge.sh:
--------------------------------------------------------------------------------
1 | # git submodule init
2 | # git submodule update
3 |
4 | #dependencies
5 | #go install github.com/sanbornm/go-selfupdate
6 |
7 | VERSION=xxx
8 | APP_NAME=Arduino_Create_Bridge
9 |
10 | # OUTPUT-COLORING
11 | red='\e[0;31m'
12 | green='\e[0;32m'
13 | NC='\e[0m' # No Color
14 |
15 | extractVersionFromMain()
16 | {
17 | VERSION=`grep versionFloat main.go | cut -d "(" -f2 | cut -d ")" -f1`
18 | }
19 |
20 | createZipEmbeddableFileArduino()
21 | {
22 | echo 'In createZipEmbeddableFileArduino'
23 | GOOS=$1
24 | GOARCH=$2
25 |
26 | # start clean
27 | rm arduino/arduino.zip
28 | rm -r arduino/arduino
29 | mkdir arduino/arduino
30 | cp -r arduino/hardware arduino/tools\_$GOOS\_$GOARCH arduino/boards.json arduino/arduino
31 | cp config.ini arduino
32 | cp fakecerts/*.pem arduino/
33 | mv arduino/arduino/tools* arduino/arduino/tools
34 | cd arduino
35 | zip -r arduino.zip arduino/* config.ini *.pem > /dev/null
36 | cd ..
37 | cat arduino/arduino.zip >> $3
38 | zip --adjust-sfx $3
39 | mkdir -p snapshot/$GOOS\_$GOARCH
40 | cp $3 snapshot/$GOOS\_$GOARCH/$3
41 | ls -la snapshot/$GOOS\_$GOARCH/$3
42 | }
43 |
44 | bootstrapPlatforms()
45 | {
46 | echo 'In bootstrapPlatforms'
47 | #export PATH=$PATH:/opt/osxcross/target/bin
48 | cd $GOROOT/src
49 | env CC_FOR_TARGET=o64-clang CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 ./make.bash --no-clean
50 | env CC_FOR_TARGET=gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 ./make.bash --no-clean
51 | env CC_FOR_TARGET=gcc CGO_ENABLED=1 GOOS=linux GOARCH=386 ./make.bash --no-clean
52 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm ./make.bash --no-clean
53 | env CC_FOR_TARGET=i686-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=386 ./make.bash --no-clean
54 | }
55 |
56 | set -x
57 | compilePlatform()
58 | {
59 | echo 'In compilePlatform'
60 | GOOS=$1
61 | GOARCH=$2
62 | CC=$3
63 | CGO_ENABLED=$4
64 | NAME=$APP_NAME
65 | if [ $GOOS == "windows" ]
66 | then
67 | NAME=$NAME".exe"
68 | fi
69 | env GOOS=$GOOS GOARCH=$GOARCH CC=$CC CXX=$CC CGO_ENABLED=$CGO_ENABLED go build -o=$NAME
70 | if [ $? != 0 ]
71 | then
72 | echo -e "${red}Target $GOOS, $GOARCH failed${NC}"
73 | exit 1
74 | fi
75 | echo createZipEmbeddableFileArduino $GOOS $GOARCH $NAME
76 | createZipEmbeddableFileArduino $GOOS $GOARCH $NAME
77 | GOOS=$GOOS GOARCH=$GOARCH go-selfupdate $NAME $VERSION
78 | rm -rf $NAME*
79 | }
80 |
81 | extractVersionFromMain
82 | compilePlatform darwin amd64 o64-clang 1
83 | #compilePlatformLinux linux 386 gcc
84 | compilePlatform linux amd64 gcc 1
85 | compilePlatform linux arm 0
86 | compilePlatform windows 386 i686-w64-mingw32-gcc 1
87 |
88 |
89 | exit 0
90 |
--------------------------------------------------------------------------------
/config.ini:
--------------------------------------------------------------------------------
1 | addr = :8989 # http service address
2 | 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
3 | configUpdateInterval = 0 # Update interval for re-reading config file set via -config flag. Zero disables config file re-reading.
4 | 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)
5 | hostname = unknown-hostname # Override the hostname we get from the OS
6 | ls = false # launch self 5 seconds later
7 | regex = usb|acm|com # Regular expression to filter serial port list
8 | v = true # show debug logging
9 | appName = Arduino_Create_Bridge
10 | updateUrl = http://localhost/
11 |
--------------------------------------------------------------------------------
/conn.go:
--------------------------------------------------------------------------------
1 | // Supports Windows, Linux, Mac, and Raspberry Pi
2 |
3 | package main
4 |
5 | import (
6 | "github.com/gin-gonic/gin"
7 | "github.com/googollee/go-socket.io"
8 | "log"
9 | "net/http"
10 | )
11 |
12 | type connection struct {
13 | // The websocket connection.
14 | ws socketio.Socket
15 |
16 | // Buffered channel of outbound messages.
17 | send chan []byte
18 | incoming chan []byte
19 | }
20 |
21 | func (c *connection) writer() {
22 | for message := range c.send {
23 | err := c.ws.Emit("message", string(message))
24 | if err != nil {
25 | break
26 | }
27 | }
28 | }
29 |
30 | // WsServer overrides socket.io server to set the CORS
31 | type WsServer struct {
32 | Server *socketio.Server
33 | }
34 |
35 | func (s *WsServer) ServeHTTP(c *gin.Context) {
36 | s.Server.ServeHTTP(c.Writer, c.Request)
37 | }
38 |
39 | func uploadHandler(c *gin.Context) {
40 | log.Print("Received a upload")
41 | port := c.PostForm("port")
42 | if port == "" {
43 | c.String(http.StatusBadRequest, "port is required")
44 | return
45 | }
46 | board := c.PostForm("board")
47 | if board == "" {
48 | c.String(http.StatusBadRequest, "board is required")
49 | return
50 | }
51 | board_rewrite := c.PostForm("board_rewrite")
52 | sketch, header, err := c.Request.FormFile("sketch_hex")
53 | if err != nil {
54 | c.String(http.StatusBadRequest, err.Error())
55 | }
56 |
57 | if header != nil {
58 | path, err := saveFileonTempDir(header.Filename, sketch)
59 | if err != nil {
60 | c.String(http.StatusBadRequest, err.Error())
61 | }
62 |
63 | go spProgramRW(port, board, board_rewrite, path)
64 | }
65 | }
66 |
67 | func wsHandler() *WsServer {
68 | server, err := socketio.NewServer(nil)
69 | if err != nil {
70 | log.Fatal(err)
71 | }
72 |
73 | server.On("connection", func(so socketio.Socket) {
74 | c := &connection{send: make(chan []byte, 256*10), ws: so}
75 | h.register <- c
76 | so.On("command", func(message string) {
77 | h.broadcast <- []byte(message)
78 | })
79 | so.On("disconnection", func() {
80 | h.unregister <- c
81 | })
82 | go c.writer()
83 | })
84 | server.On("error", func(so socketio.Socket, err error) {
85 | log.Println("error:", err)
86 | })
87 |
88 | wrapper := WsServer{
89 | Server: server,
90 | }
91 |
92 | return &wrapper
93 | }
94 |
--------------------------------------------------------------------------------
/discovery.go:
--------------------------------------------------------------------------------
1 | //
2 | // discovery.go
3 | //
4 | // Created by Martino Facchin
5 | // Copyright (c) 2015 Arduino 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 | "github.com/oleksandr/bonjour"
33 | "log"
34 | "net"
35 | "strings"
36 | "time"
37 | )
38 |
39 | const timeoutConst = 2
40 |
41 | // SavedNetworkPorts contains the ports which we know are already connected
42 | var SavedNetworkPorts []OsSerialPort
43 |
44 | // GetNetworkList returns a list of Network Ports
45 | // The research of network ports is articulated in two phases. First we add new ports coming from
46 | // the bonjour module, then we prune the boards who don't respond to a ping
47 | func GetNetworkList() ([]OsSerialPort, error) {
48 | newPorts, err := getPorts()
49 | if err != nil {
50 | return nil, err
51 | }
52 |
53 | SavedNetworkPorts = Filter(SavedNetworkPorts, func(port OsSerialPort) bool {
54 | any := true
55 | for _, p := range newPorts {
56 | if p.Name == port.Name && p.FriendlyName == port.FriendlyName {
57 | any = false
58 | return any
59 | }
60 | }
61 | return any
62 | })
63 |
64 | SavedNetworkPorts, err = pruneUnreachablePorts(SavedNetworkPorts)
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | SavedNetworkPorts = append(SavedNetworkPorts, newPorts...)
70 |
71 | return SavedNetworkPorts, nil
72 | }
73 |
74 | func checkAvailability(ip string) bool {
75 | timeout := time.Duration(1500 * time.Millisecond)
76 | // Check if the port 80 is open
77 | conn, err := net.DialTimeout("tcp", ip+":80", timeout)
78 | if err != nil {
79 | log.Println(err)
80 | // Check if the port 22 is open
81 | conn, err = net.DialTimeout("tcp", ip+":22", timeout)
82 | if err != nil {
83 | log.Println(err)
84 | return false
85 | }
86 | conn.Close()
87 | return true
88 | }
89 | conn.Close()
90 | return true
91 | }
92 |
93 | func pruneUnreachablePorts(ports []OsSerialPort) ([]OsSerialPort, error) {
94 | times := 2
95 |
96 | ports = Filter(ports, func(port OsSerialPort) bool {
97 | any := false
98 | for i := 0; i < times; i++ {
99 | if checkAvailability(port.Name) {
100 | any = true
101 | }
102 | }
103 | return any
104 | })
105 |
106 | return ports, nil
107 | }
108 |
109 | func getPorts() ([]OsSerialPort, error) {
110 | resolver, err := bonjour.NewResolver(nil)
111 | if err != nil {
112 | log.Println("Failed to initialize resolver:", err.Error())
113 | return nil, err
114 | }
115 |
116 | results := make(chan *bonjour.ServiceEntry)
117 |
118 | timeout := make(chan bool, 1)
119 | go func(exitCh chan<- bool) {
120 | time.Sleep(timeoutConst * time.Second)
121 | exitCh <- true
122 | close(results)
123 | }(resolver.Exit)
124 |
125 | arrPorts := []OsSerialPort{}
126 | go func(results chan *bonjour.ServiceEntry, exitCh chan<- bool) {
127 | for e := range results {
128 | var boardInfosSlice []string
129 | for _, element := range e.Text {
130 | if strings.Contains(element, "board=yun") {
131 | boardInfosSlice = append(boardInfosSlice, "arduino:avr:yun")
132 | }
133 | }
134 | arrPorts = append(arrPorts, OsSerialPort{Name: e.AddrIPv4.String(), FriendlyName: e.Instance, NetworkPort: true, RelatedNames: boardInfosSlice})
135 | }
136 | timeout <- true
137 | }(results, resolver.Exit)
138 |
139 | err = resolver.Browse("_arduino._tcp", "", results)
140 | if err != nil {
141 | log.Println("Failed to browse:", err.Error())
142 | return nil, err
143 | }
144 | // wait for some kind of timeout and return arrPorts
145 | select {
146 | case <-timeout:
147 | return arrPorts, nil
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/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 saveFileonTempDir(filename string, sketch io.Reader) (path string, err error) {
16 | // create tmp dir
17 | tmpdir, err := ioutil.TempDir("", "serial-port-json-server")
18 | if err != nil {
19 | return "", errors.New("Could not create temp directory to store downloaded file. Do you have permissions?")
20 | }
21 |
22 | filename, _ = filepath.Abs(tmpdir + "/" + filename)
23 |
24 | output, err := os.Create(filename)
25 | if err != nil {
26 | log.Println("Error while creating", filename, "-", err)
27 | return filename, err
28 | }
29 | defer output.Close()
30 |
31 | n, err := io.Copy(output, sketch)
32 | if err != nil {
33 | log.Println("Error while copying", err)
34 | return filename, err
35 | }
36 |
37 | log.Println(n, "bytes saved")
38 |
39 | return filename, nil
40 |
41 | }
42 |
43 | func downloadFromUrl(url string) (filename string, err error) {
44 |
45 | // clean up url
46 | // remove newlines and space at end
47 | url = strings.TrimSpace(url)
48 |
49 | // create tmp dir
50 | tmpdir, err := ioutil.TempDir("", "serial-port-json-server")
51 | if err != nil {
52 | return "", errors.New("Could not create temp directory to store downloaded file. Do you have permissions?")
53 | }
54 | tokens := strings.Split(url, "/")
55 | filePrefix := tokens[len(tokens)-1]
56 | log.Println("The filePrefix is", filePrefix)
57 |
58 | fileName, _ := filepath.Abs(tmpdir + "/" + filePrefix)
59 | log.Println("Downloading", url, "to", fileName)
60 |
61 | // TODO: check file existence first with io.IsExist
62 | output, err := os.Create(fileName)
63 | if err != nil {
64 | log.Println("Error while creating", fileName, "-", err)
65 | return fileName, err
66 | }
67 | defer output.Close()
68 |
69 | response, err := http.Get(url)
70 | if err != nil {
71 | log.Println("Error while downloading", url, "-", err)
72 | return fileName, err
73 | }
74 | defer response.Body.Close()
75 |
76 | n, err := io.Copy(output, response.Body)
77 | if err != nil {
78 | log.Println("Error while downloading", url, "-", err)
79 | return fileName, err
80 | }
81 |
82 | log.Println(n, "bytes downloaded.")
83 |
84 | return fileName, nil
85 | }
86 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/fakecerts/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDBDCCAewCCQD9LvUUyrKRXzANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJV
3 | UzEUMBIGA1UEChMLQXJkdWlubyBMTEMxCzAJBgNVBAsTAklUMRIwEAYDVQQDEwls
4 | b2NhbGhvc3QwHhcNMTUwNjExMDcyNTM2WhcNMTYwNjEwMDcyNTM2WjBEMQswCQYD
5 | VQQGEwJVUzEUMBIGA1UEChMLQXJkdWlubyBMTEMxCzAJBgNVBAsTAklUMRIwEAYD
6 | VQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7
7 | 3UTunwOvcw1hgFDN6bdHxssg/H33faLILjUJIBZwyRCVLc8zcExk7D0YLxnMBaxs
8 | t4DoPfMlAXiJ+GT3fiOaYTKHmGT3Qz85WC6Yngz9A/usDQSSYe9rI4k3oLNA4F+b
9 | 6VU5FBj1V3nj79gs3IMEkTMnit+9HZ8PNlJEZSzEuxbrQdIV530H8MvHpUoCkinP
10 | uPur9zGFysIS1euoVybNKmVXcxmy0QHDNfOddH2SBaFD6QriucOIMW6AVHjseL9E
11 | A4IKKum88LLDiKPeu3gr12vNnjydLFGnNg+wlTnXUeeuXgMfNVaTDaIC8NEA+mSQ
12 | M6Ag6CymhWqpe/cqa45hAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBABCXfQZLhYI4
13 | B0VjgwDDRohTAhsSyaQvFEhGOx2Oo28RhKgY2xRtfjDlm7IzTLga4ApsHqIMf85F
14 | z4bpDDciwPhQDJ6a0ueXJv+xb6RGBnGAbnOiHPfgdd/P2vGfsR7mRaTdXJC4r9PI
15 | C3kzjwTlplaulLbHs5XIXImGvAvRX5sPmkqlBAQs8PVG4I71pKXo1M4kl7uzr96+
16 | +DKnVz2oACAPCW6zTlT/MlBH4nSeCQfGiE8iWAqODviONOQjFnaTKLw6d1YnbnLp
17 | 1gokB8sk1TAM3qjb6giZpe945xISSNUDAVRW+3NIKag+qOURnN+QGi9ac3cPepXb
18 | 4Kr4QM+kV+g=
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/fakecerts/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEogIBAAKCAQEAu91E7p8Dr3MNYYBQzem3R8bLIPx9932iyC41CSAWcMkQlS3P
3 | M3BMZOw9GC8ZzAWsbLeA6D3zJQF4ifhk934jmmEyh5hk90M/OVgumJ4M/QP7rA0E
4 | kmHvayOJN6CzQOBfm+lVORQY9Vd54+/YLNyDBJEzJ4rfvR2fDzZSRGUsxLsW60HS
5 | Fed9B/DLx6VKApIpz7j7q/cxhcrCEtXrqFcmzSplV3MZstEBwzXznXR9kgWhQ+kK
6 | 4rnDiDFugFR47Hi/RAOCCirpvPCyw4ij3rt4K9drzZ48nSxRpzYPsJU511Hnrl4D
7 | HzVWkw2iAvDRAPpkkDOgIOgspoVqqXv3KmuOYQIDAQABAoIBADzL4df7W/z2ldj8
8 | 5qmMvkSbP/d/3uAuJ3TzBzTZzolXeGdeuNRjvkVW8nqWG5ocJ+3TAQOnsL1EGZdE
9 | 7J/vkWQPmoOpPNuMRrSvJf08AOYM2PCYEeexjAK3MFvxRLF1K1vQikT7jQww8ABl
10 | CSeTgU8EEnL0jW2tXWFV6g+6Ul+jwfq5IvbUpMsMOPuUEQy85rm04bCw/vUnhZXk
11 | gFSpAp5mKPI6J/v2fkJTjgxi0wURxHKFdH/dFr69k9G7Vv9L8meiZYwA0QsYcmeJ
12 | EAGpZHQXpTCmmmzWM85vz9vg4qUBwF8ypXcWjuqfjAopXvuenyIkfa9paDriRnNM
13 | A3JmUQECgYEA66fdY8cU1cJw5jmhc10mi6lDz0Xswba4kaYnb2fwr7tlkBFzYYVn
14 | GY6f2fNxdx8ZpOCYzembJ67qCXODw4LLle1slgHzOWHQ4RJd2EDw/i9woPHGsT3a
15 | TIk0tX3wOjtJJEXNzQEiqcDqGqrpY3pnm4lFGR4RSE6vIYfPwyO9y7ECgYEAzBUv
16 | hKy19GSFK8tkYa/WcmNMCGwLJB0pEMuT3heDjSEKYNaTh8YcIpeEY3aCWbMKxWdu
17 | O5TIouyNJHtm4Q0umG08ZGekLTZy22it2UJabROvHVHeSnPki9a12Uc3KgB6mBzb
18 | nnHXQ8hR60o0GTPMudVW12aZh9gy+EcGWQEwibECgYAIdQ3M9ugYg9HpcUgKC93G
19 | RVzZo3jLuvMExyHDLcfFhsQuV3H8nf0FPcke2U3JKKSS9jyFp0yaL/zWOf8QlTEZ
20 | QFwVRhykgo4shaw4hpwfgzRXklW/Jqke9g2eNdbZQCdv1dF8+f10eifsrRWTLGFr
21 | g5GnRRz5q1k9qtIZ/r5hAQKBgCegMXLMaiQC5IylWF9TWgsp1piICf3AH7ogHRzx
22 | adycPrygzVJ+l/Xrf4wkdZjfF8dOluvArthbn+gmgcpO2e5Ev4YrTYht2w1ZHPBj
23 | XtVxDf5eaBACwqyYSwTePArOvv8ME2SHbCnAGo/Z/5WpJiYrE0qNpF/pDbSBbe0Y
24 | OwlxAoGAdPEjpeeCpyUcoJMVnIxM7AtsOqomd0lBrRgRq05FYvGMAygoKzz+OBp+
25 | VgptcGJp+6t5MY9R3asRaFp3rOcXvX5R4wBMfijlzoezMEFZN/+xpm7LN2E0domO
26 | xyku5Kcn9G/KTxCduepOtjqoNYkKYAcrdkmRfZ9C9xvuB2lKjk8=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Serial Port Example
4 |
5 |
46 |
83 |
84 |
85 |
86 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/hub.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/kardianos/osext"
6 | "log"
7 | //"os"
8 | "os/exec"
9 | //"path"
10 | //"path/filepath"
11 | //"runtime"
12 | //"debug"
13 | "encoding/json"
14 | "runtime"
15 | "runtime/debug"
16 | "strconv"
17 | "strings"
18 | )
19 |
20 | type hub struct {
21 | // Registered connections.
22 | connections map[*connection]bool
23 |
24 | // Inbound messages from the connections.
25 | broadcast chan []byte
26 |
27 | // Inbound messages from the system
28 | broadcastSys chan []byte
29 |
30 | // Register requests from the connections.
31 | register chan *connection
32 |
33 | // Unregister requests from connections.
34 | unregister chan *connection
35 | }
36 |
37 | var h = hub{
38 | // buffered. go with 1000 cuz should never surpass that
39 | broadcast: make(chan []byte, 1000),
40 | broadcastSys: make(chan []byte, 1000),
41 | // non-buffered
42 | //broadcast: make(chan []byte),
43 | //broadcastSys: make(chan []byte),
44 | register: make(chan *connection),
45 | unregister: make(chan *connection),
46 | connections: make(map[*connection]bool),
47 | }
48 |
49 | func (h *hub) run() {
50 | for {
51 | select {
52 | case c := <-h.register:
53 | h.connections[c] = true
54 | // send supported commands
55 | c.send <- []byte("{\"Version\" : \"" + version + "\"} ")
56 | c.send <- []byte("{\"Commands\" : [\"list\", \"open [portName] [baud] [bufferAlgorithm (optional)]\", \"send [portName] [cmd]\", \"sendnobuf [portName] [cmd]\", \"close [portName]\", \"bufferalgorithms\", \"baudrates\", \"restart\", \"exit\", \"program [portName] [board:name] [$path/to/filename/without/extension]\", \"programfromurl [portName] [board:name] [urlToHexFile]\"]} ")
57 | c.send <- []byte("{\"Hostname\" : \"" + *hostname + "\"} ")
58 | case c := <-h.unregister:
59 | delete(h.connections, c)
60 | // put close in func cuz it was creating panics and want
61 | // to isolate
62 | func() {
63 | // this method can panic if websocket gets disconnected
64 | // from users browser and we see we need to unregister a couple
65 | // of times, i.e. perhaps from incoming data from serial triggering
66 | // an unregister. (NOT 100% sure why seeing c.send be closed twice here)
67 | defer func() {
68 | if e := recover(); e != nil {
69 | log.Println("Got panic: ", e)
70 | }
71 | }()
72 | close(c.send)
73 | }()
74 | case m := <-h.broadcast:
75 | if len(m) > 0 {
76 | //log.Print(string(m))
77 | //log.Print(h.broadcast)
78 | checkCmd(m)
79 | //log.Print("-----")
80 |
81 | for c := range h.connections {
82 | select {
83 | case c.send <- m:
84 | //log.Print("did broadcast to ")
85 | //log.Print(c.ws.RemoteAddr())
86 | //c.send <- []byte("hello world")
87 | default:
88 | delete(h.connections, c)
89 | close(c.send)
90 | }
91 | }
92 | }
93 | case m := <-h.broadcastSys:
94 | for c := range h.connections {
95 | select {
96 | case c.send <- m:
97 | //log.Print("did broadcast to ")
98 | //log.Print(c.ws.RemoteAddr())
99 | //c.send <- []byte("hello world")
100 | default:
101 | delete(h.connections, c)
102 | close(c.send)
103 | }
104 | }
105 | }
106 | }
107 | }
108 |
109 | func checkCmd(m []byte) {
110 | //log.Print("Inside checkCmd")
111 | s := string(m[:])
112 |
113 | sl := strings.ToLower(strings.Trim(s, "\n"))
114 |
115 | if strings.HasPrefix(sl, "open") {
116 |
117 | // check if user wants to open this port as a secondary port
118 | // this doesn't mean much other than allowing the UI to show
119 | // a port as primary and make other ports sort of act less important
120 | isSecondary := false
121 | if strings.HasPrefix(s, "open secondary") {
122 | isSecondary = true
123 | // swap out the word secondary
124 | s = strings.Replace(s, "open secondary", "open", 1)
125 | }
126 |
127 | args := strings.Split(s, " ")
128 | if len(args) < 3 {
129 | go spErr("You did not specify a port and baud rate in your open cmd")
130 | return
131 | }
132 | if len(args[1]) < 1 {
133 | go spErr("You did not specify a serial port")
134 | return
135 | }
136 |
137 | baudStr := strings.Replace(args[2], "\n", "", -1)
138 | baud, err := strconv.Atoi(baudStr)
139 | if err != nil {
140 | go spErr("Problem converting baud rate " + args[2])
141 | return
142 | }
143 | // pass in buffer type now as string. if user does not
144 | // ask for a buffer type pass in empty string
145 | bufferAlgorithm := ""
146 | if len(args) > 3 {
147 | // cool. we got a buffer type request
148 | buftype := strings.Replace(args[3], "\n", "", -1)
149 | bufferAlgorithm = buftype
150 | }
151 | go spHandlerOpen(args[1], baud, bufferAlgorithm, isSecondary)
152 |
153 | } else if strings.HasPrefix(sl, "close") {
154 |
155 | args := strings.Split(s, " ")
156 | if len(args) > 1 {
157 | go spClose(args[1])
158 | } else {
159 | go spErr("You did not specify a port to close")
160 | }
161 |
162 | } else if strings.HasPrefix(sl, "programfromurl") {
163 |
164 | args := strings.Split(s, " ")
165 | if len(args) == 4 {
166 | go spProgramFromUrl(args[1], args[2], args[3])
167 | } else {
168 | go spErr("You did not specify a port, a board to program and/or a URL")
169 | }
170 |
171 | } else if strings.HasPrefix(sl, "program") {
172 |
173 | args := strings.Split(s, " ")
174 | if len(args) > 3 {
175 | var slice []string = args[3:len(args)]
176 | go spProgram(args[1], args[2], strings.Join(slice, " "))
177 | } else {
178 | go spErr("You did not specify a port, a board to program and/or a filename")
179 | }
180 |
181 | } else if strings.HasPrefix(sl, "sendjson") {
182 | // will catch sendjson
183 |
184 | go spWriteJson(s)
185 |
186 | } else if strings.HasPrefix(sl, "send") {
187 | // will catch send and sendnobuf
188 |
189 | //args := strings.Split(s, "send ")
190 | go spWrite(s)
191 |
192 | } else if strings.HasPrefix(sl, "list") {
193 | go spList(false)
194 | go spList(true)
195 | // log.Println("Stack info", runtime.NumGoroutine())
196 | // if runtime.NumGoroutine() > 30 {
197 | // output := make([]byte, 1<<16)
198 | // runtime.Stack(output, true)
199 | // log.Println(output)
200 | // log.Fatal("Crash because something is going wrong")
201 | // }
202 | //go getListViaWmiPnpEntity()
203 | } else if strings.HasPrefix(sl, "bufferalgorithm") {
204 | go spBufferAlgorithms()
205 | } else if strings.HasPrefix(sl, "baudrate") {
206 | go spBaudRates()
207 | } else if strings.HasPrefix(sl, "broadcast") {
208 | go broadcast(s)
209 | } else if strings.HasPrefix(sl, "restart") {
210 | restart()
211 | } else if strings.HasPrefix(sl, "exit") {
212 | exit()
213 | } else if strings.HasPrefix(sl, "memstats") {
214 | memoryStats()
215 | } else if strings.HasPrefix(sl, "gc") {
216 | garbageCollection()
217 | } else if strings.HasPrefix(sl, "bufflowdebug") {
218 | bufflowdebug(sl)
219 | } else if strings.HasPrefix(sl, "hostname") {
220 | getHostname()
221 | } else if strings.HasPrefix(sl, "version") {
222 | getVersion()
223 | } else {
224 | go spErr("Could not understand command.")
225 | }
226 |
227 | //log.Print("Done with checkCmd")
228 | }
229 |
230 | func bufflowdebug(sl string) {
231 | log.Println("bufflowdebug start")
232 | if strings.HasPrefix(sl, "bufflowdebug on") {
233 | *bufFlowDebugType = "on"
234 | } else if strings.HasPrefix(sl, "bufflowdebug off") {
235 | *bufFlowDebugType = "off"
236 | }
237 | h.broadcastSys <- []byte("{\"BufFlowDebug\" : \"" + *bufFlowDebugType + "\"}")
238 | log.Println("bufflowdebug end")
239 | }
240 |
241 | func memoryStats() {
242 | var memStats runtime.MemStats
243 | runtime.ReadMemStats(&memStats)
244 | json, _ := json.Marshal(memStats)
245 | log.Printf("memStats:%v\n", string(json))
246 | h.broadcastSys <- json
247 | }
248 |
249 | func getHostname() {
250 | h.broadcastSys <- []byte("{\"Hostname\" : \"" + *hostname + "\"}")
251 | }
252 |
253 | func getVersion() {
254 | h.broadcastSys <- []byte("{\"Version\" : \"" + version + "\"}")
255 | }
256 |
257 | func garbageCollection() {
258 | log.Printf("Starting garbageCollection()\n")
259 | h.broadcastSys <- []byte("{\"gc\":\"starting\"}")
260 | memoryStats()
261 | debug.SetGCPercent(100)
262 | debug.FreeOSMemory()
263 | debug.SetGCPercent(-1)
264 | log.Printf("Done with garbageCollection()\n")
265 | h.broadcastSys <- []byte("{\"gc\":\"done\"}")
266 | memoryStats()
267 | }
268 |
269 | func exit() {
270 | log.Println("Starting new spjs process")
271 | h.broadcastSys <- []byte("{\"Exiting\" : true}")
272 | log.Fatal("Exited current spjs cuz asked to")
273 |
274 | }
275 |
276 | func restart() {
277 | // relaunch ourself and exit
278 | // the relaunch works because we pass a cmdline in
279 | // that has serial-port-json-server only initialize 5 seconds later
280 | // which gives us time to exit and unbind from serial ports and TCP/IP
281 | // sockets like :8989
282 | log.Println("Starting new spjs process")
283 | h.broadcastSys <- []byte("{\"Restarting\" : true}")
284 |
285 | // figure out current path of executable so we know how to restart
286 | // this process
287 | /*
288 | dir, err2 := filepath.Abs(filepath.Dir(os.Args[0]))
289 | if err2 != nil {
290 | //log.Fatal(err2)
291 | fmt.Printf("Error getting executable file path. err: %v\n", err2)
292 | }
293 | fmt.Printf("The path to this exe is: %v\n", dir)
294 |
295 | // alternate approach
296 | _, filename, _, _ := runtime.Caller(1)
297 | f, _ := os.Open(path.Join(path.Dir(filename), "serial-port-json-server"))
298 | fmt.Println(f)
299 | */
300 |
301 | // using osext
302 | exePath, err3 := osext.Executable()
303 | if err3 != nil {
304 | fmt.Printf("Error getting exe path using osext lib. err: %v\n", err3)
305 | }
306 | fmt.Printf("exePath using osext: %v\n", exePath)
307 |
308 | // figure out garbageCollection flag
309 | //isGcFlag := "false"
310 |
311 | var cmd *exec.Cmd
312 | /*if *isGC {
313 | //isGcFlag = "true"
314 | cmd = exec.Command(exePath, "-ls", "-addr", *addr, "-regex", *regExpFilter, "-gc")
315 | } else {
316 | cmd = exec.Command(exePath, "-ls", "-addr", *addr, "-regex", *regExpFilter)
317 |
318 | }*/
319 | cmd = exec.Command(exePath, "-ls", "-addr", *addr, "-regex", *regExpFilter, "-gc", *gcType)
320 |
321 | //cmd := exec.Command("./serial-port-json-server", "ls")
322 | err := cmd.Start()
323 | if err != nil {
324 | log.Printf("Got err restarting spjs: %v\n", err)
325 | h.broadcastSys <- []byte("{\"Error\" : \"" + fmt.Sprintf("%v", err) + "\"}")
326 | } else {
327 | h.broadcastSys <- []byte("{\"Restarted\" : true}")
328 | }
329 | log.Fatal("Exited current spjs for restart")
330 | //log.Printf("Waiting for command to finish...")
331 | //err = cmd.Wait()
332 | //log.Printf("Command finished with error: %v", err)
333 | }
334 |
335 | type CmdBroadcast struct {
336 | Cmd string
337 | Msg string
338 | }
339 |
340 | func broadcast(arg string) {
341 | // we will get a string of broadcast asdf asdf asdf
342 | log.Println("Inside broadcast arg: " + arg)
343 | arg = strings.TrimPrefix(arg, " ")
344 | //log.Println("arg after trim: " + arg)
345 | args := strings.SplitN(arg, " ", 2)
346 | if len(args) != 2 {
347 | errstr := "Could not parse broadcast command: " + arg
348 | log.Println(errstr)
349 | spErr(errstr)
350 | return
351 | }
352 | broadcastcmd := strings.Trim(args[1], " ")
353 | log.Println("The broadcast cmd is:" + broadcastcmd + "---")
354 |
355 | bcmd := CmdBroadcast{
356 | Cmd: "Broadcast",
357 | Msg: broadcastcmd,
358 | }
359 | json, _ := json.Marshal(bcmd)
360 | log.Printf("bcmd:%v\n", string(json))
361 | h.broadcastSys <- json
362 |
363 | }
364 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Version 1.82
2 | // Supports Windows, Linux, Mac, and Raspberry Pi, Beagle Bone Black
3 |
4 | package main
5 |
6 | import (
7 | "flag"
8 | "fmt"
9 | "go/build"
10 | "log"
11 | "os"
12 | "path/filepath"
13 | //"net/http/pprof"
14 | "github.com/kardianos/osext"
15 | //"github.com/sanbornm/go-selfupdate/selfupdate" #included in update.go to change heavily
16 | //"github.com/sanderhahn/gozip"
17 | "github.com/gin-gonic/gin"
18 | "github.com/itsjamie/gin-cors"
19 | "github.com/kardianos/service"
20 | "github.com/vharitonsky/iniflags"
21 | "runtime/debug"
22 | "text/template"
23 | "time"
24 | )
25 |
26 | var (
27 | version = "1.83"
28 | versionFloat = float32(1.83)
29 | addr = flag.String("addr", ":8989", "http service address")
30 | addrSSL = flag.String("addrSSL", ":8990", "https service address")
31 | //assets = flag.String("assets", defaultAssetPath(), "path to assets")
32 | verbose = flag.Bool("v", true, "show debug logging")
33 | //verbose = flag.Bool("v", false, "show debug logging")
34 | //homeTempl *template.Template
35 | isLaunchSelf = flag.Bool("ls", false, "launch self 5 seconds later")
36 |
37 | configIni = flag.String("configFile", "config.ini", "config file path")
38 | // regular expression to sort the serial port list
39 | // typically this wouldn't be provided, but if the user wants to clean
40 | // up their list with a regexp so it's cleaner inside their end-user interface
41 | // such as ChiliPeppr, this can make the massive list that Linux gives back
42 | // to you be a bit more manageable
43 | regExpFilter = flag.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
44 |
45 | // allow garbageCollection()
46 | //isGC = flag.Bool("gc", false, "Is garbage collection on? Off by default.")
47 | //isGC = flag.Bool("gc", true, "Is garbage collection on? Off by default.")
48 | 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)")
49 |
50 | // whether to do buffer flow debugging
51 | 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")
52 |
53 | // hostname. allow user to override, otherwise we look it up
54 | hostname = flag.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
55 |
56 | updateUrl = flag.String("updateUrl", "", "")
57 | appName = flag.String("appName", "", "")
58 | )
59 |
60 | var globalConfigMap map[string]interface{}
61 |
62 | type NullWriter int
63 |
64 | func (NullWriter) Write([]byte) (int, error) { return 0, nil }
65 |
66 | func defaultAssetPath() string {
67 | //p, err := build.Default.Import("gary.burd.info/go-websocket-chat", "", build.FindOnly)
68 | p, err := build.Default.Import("github.com/johnlauer/serial-port-json-server", "", build.FindOnly)
69 | if err != nil {
70 | return "."
71 | }
72 | return p.Dir
73 | }
74 |
75 | func homeHandler(c *gin.Context) {
76 | homeTemplate.Execute(c.Writer, c.Request.Host)
77 | }
78 |
79 | func launchSelfLater() {
80 | log.Println("Going to launch myself 5 seconds later.")
81 | time.Sleep(2 * 1000 * time.Millisecond)
82 | log.Println("Done waiting 5 secs. Now launching...")
83 | }
84 |
85 | var logger service.Logger
86 |
87 | type program struct{}
88 |
89 | func (p *program) Start(s service.Service) error {
90 | // Start should not block. Do the actual work async.
91 | go p.run()
92 | return nil
93 | }
94 | func (p *program) run() {
95 | startDaemon()
96 | }
97 | func (p *program) Stop(s service.Service) error {
98 | // Stop should not block. Return with a few seconds.
99 | <-time.After(time.Second * 13)
100 | return nil
101 | }
102 |
103 | func main() {
104 | svcConfig := &service.Config{
105 | Name: "ArduinoCreateBridge",
106 | DisplayName: "Arduino Create Bridge",
107 | Description: "A bridge that allows Arduino Create to operate on the boards connected to the computer",
108 | }
109 |
110 | prg := &program{}
111 | s, err := service.New(prg, svcConfig)
112 | if err != nil {
113 | log.Fatal(err)
114 | }
115 | if len(os.Args) > 1 {
116 | err = service.Control(s, os.Args[1])
117 | if err != nil {
118 | log.Fatal(err)
119 | }
120 | return
121 | }
122 |
123 | logger, err = s.Logger(nil)
124 | if err != nil {
125 | log.Fatal(err)
126 | }
127 |
128 | err = s.Install()
129 | if err != nil {
130 | logger.Error(err)
131 | }
132 |
133 | err = s.Run()
134 | if err != nil {
135 | logger.Error(err)
136 | }
137 | }
138 |
139 | func startDaemon() {
140 | // setupSysTray()
141 | go func() {
142 |
143 | // autoextract self
144 | src, _ := osext.Executable()
145 | dest := filepath.Dir(src)
146 |
147 | // save the config.ini (if it exists)
148 | if _, err := os.Stat(dest + "/" + *configIni); os.IsNotExist(err) {
149 | fmt.Println("First run, unzipping self")
150 | err := Unzip(src, dest)
151 | fmt.Println("Self extraction, err:", err)
152 | }
153 |
154 | if _, err := os.Stat(dest + "/" + *configIni); os.IsNotExist(err) {
155 | flag.Parse()
156 | fmt.Println("No config.ini at", *configIni)
157 | } else {
158 | flag.Set("config", dest+"/"+*configIni)
159 | iniflags.Parse()
160 | }
161 |
162 | // setup logging
163 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
164 |
165 | // see if we are supposed to wait 5 seconds
166 | if *isLaunchSelf {
167 | launchSelfLater()
168 | }
169 |
170 | var updater = &Updater{
171 | CurrentVersion: version,
172 | ApiURL: *updateUrl,
173 | BinURL: *updateUrl,
174 | DiffURL: "",
175 | Dir: "update/",
176 | CmdName: *appName,
177 | }
178 |
179 | if updater != nil {
180 | go updater.BackgroundRun()
181 | }
182 |
183 | // data, err := Asset("arduino.zip")
184 | // if err != nil {
185 | // log.Println("arduino tools not found")
186 | // }
187 |
188 | createGlobalConfigMap(&globalConfigMap)
189 |
190 | //getList()
191 | f := flag.Lookup("addr")
192 | log.Println("Version:" + version)
193 |
194 | // hostname
195 | hn, _ := os.Hostname()
196 | if *hostname == "unknown-hostname" {
197 | *hostname = hn
198 | }
199 | log.Println("Hostname:", *hostname)
200 |
201 | // turn off garbage collection
202 | // this is dangerous, as u could overflow memory
203 | //if *isGC {
204 | if *gcType == "std" {
205 | log.Println("Garbage collection is on using Standard mode, meaning we just let Golang determine when to garbage collect.")
206 | } else if *gcType == "max" {
207 | 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.")
208 | } else {
209 | 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.")
210 | debug.SetGCPercent(-1)
211 | }
212 |
213 | ip := "0.0.0.0"
214 | log.Print("Starting server and websocket on " + ip + "" + f.Value.String())
215 | //homeTempl = template.Must(template.ParseFiles(filepath.Join(*assets, "home.html")))
216 |
217 | log.Println("The Serial Port JSON Server is now running.")
218 | log.Println("If you are using ChiliPeppr, you may go back to it and connect to this server.")
219 |
220 | // see if they provided a regex filter
221 | if len(*regExpFilter) > 0 {
222 | log.Printf("You specified a serial port regular expression filter: %v\n", *regExpFilter)
223 | }
224 |
225 | // list serial ports
226 | portList, _ := GetList(false)
227 | /*if errSys != nil {
228 | log.Printf("Got system error trying to retrieve serial port list. Err:%v\n", errSys)
229 | log.Fatal("Exiting")
230 | }*/
231 | log.Println("Your serial ports:")
232 | if len(portList) == 0 {
233 | log.Println("\tThere are no serial ports to list.")
234 | }
235 | for _, element := range portList {
236 | log.Printf("\t%v\n", element)
237 |
238 | }
239 |
240 | if !*verbose {
241 | log.Println("You can enter verbose mode to see all logging by starting with the -v command line switch.")
242 | log.SetOutput(new(NullWriter)) //route all logging to nullwriter
243 | }
244 |
245 | // launch the hub routine which is the singleton for the websocket server
246 | go h.run()
247 | // launch our serial port routine
248 | go sh.run()
249 | // launch our dummy data routine
250 | //go d.run()
251 |
252 | go discoverLoop()
253 |
254 | r := gin.New()
255 |
256 | socketHandler := wsHandler().ServeHTTP
257 |
258 | r.Use(cors.Middleware(cors.Config{
259 | Origins: "https://create.arduino.cc, http://create.arduino.cc, https://create-dev.arduino.cc, http://create-dev.arduino.cc, http://webide.arduino.cc:8080",
260 | Methods: "GET, PUT, POST, DELETE",
261 | RequestHeaders: "Origin, Authorization, Content-Type",
262 | ExposedHeaders: "",
263 | MaxAge: 50 * time.Second,
264 | Credentials: true,
265 | ValidateHeaders: false,
266 | }))
267 |
268 | r.GET("/", homeHandler)
269 | r.POST("/upload", uploadHandler)
270 | r.GET("/socket.io/", socketHandler)
271 | r.POST("/socket.io/", socketHandler)
272 | r.Handle("WS", "/socket.io/", socketHandler)
273 | r.Handle("WSS", "/socket.io/", socketHandler)
274 | go func() {
275 | if err := r.RunTLS(*addrSSL, filepath.Join(dest, "cert.pem"), filepath.Join(dest, "key.pem")); err != nil {
276 | fmt.Printf("Error trying to bind to port: %v, so exiting...", err)
277 | log.Fatal("Error ListenAndServe:", err)
278 | }
279 | }()
280 |
281 | if err := r.Run(*addr); err != nil {
282 | fmt.Printf("Error trying to bind to port: %v, so exiting...", err)
283 | log.Fatal("Error ListenAndServe:", err)
284 | }
285 | }()
286 |
287 | }
288 |
289 | var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHtml))
290 |
291 | // If you navigate to this server's homepage, you'll get this HTML
292 | // so you can directly interact with the serial port server
293 | const homeTemplateHtml = `
294 |
295 |
296 | Serial Port Example
297 |
298 |
299 |
344 |
381 |
382 |
383 |
384 |
388 |
389 |
390 | `
391 |
--------------------------------------------------------------------------------
/programmer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/facchinm/go-serial"
9 | "github.com/kardianos/osext"
10 | "io"
11 | "log"
12 | "mime/multipart"
13 | "net/http"
14 | "os"
15 | "os/exec"
16 | "path/filepath"
17 | "strconv"
18 | "strings"
19 | "time"
20 | )
21 |
22 | var compiling = false
23 |
24 | // Download the file from URL first, store in tmp folder, then pass to spProgram
25 | func spProgramFromUrl(portname string, boardname string, url string) {
26 | mapB, _ := json.Marshal(map[string]string{"ProgrammerStatus": "DownloadStart", "Url": url})
27 | h.broadcastSys <- mapB
28 | filename, err := downloadFromUrl(url)
29 | mapB, _ = json.Marshal(map[string]string{"ProgrammerStatus": "DownloadDone", "Filename": filename, "Url": url})
30 | h.broadcastSys <- mapB
31 |
32 | if err != nil {
33 | spErr(err.Error())
34 | return
35 | } else {
36 | spProgram(portname, boardname, filename)
37 | }
38 |
39 | // delete file
40 |
41 | }
42 |
43 | func colonToUnderscore(input string) string {
44 | output := strings.Replace(input, ":", "_", -1)
45 | return output
46 | }
47 |
48 | func spProgramNetwork(portname string, boardname string, filePath string) error {
49 |
50 | log.Println("Starting network upload")
51 | log.Println("Board Name: " + boardname)
52 |
53 | // Prepare a form that you will submit to that URL.
54 | _url := "http://" + portname + "/data/upload_sketch_silent"
55 | var b bytes.Buffer
56 | w := multipart.NewWriter(&b)
57 | // Add your image file
58 | filePath = strings.Trim(filePath, "\n")
59 | f, err := os.Open(filePath)
60 | if err != nil {
61 | log.Println("Error opening file" + filePath + " err: " + err.Error())
62 | return err
63 | }
64 | fw, err := w.CreateFormFile("sketch_hex", filePath)
65 | if err != nil {
66 | log.Println("Error creating form file")
67 | return err
68 | }
69 | if _, err = io.Copy(fw, f); err != nil {
70 | log.Println("Error copying form file")
71 | return err
72 | }
73 | // Add the other fields
74 | if fw, err = w.CreateFormField("board"); err != nil {
75 | log.Println("Error creating form field")
76 | return err
77 | }
78 | if _, err = fw.Write([]byte(colonToUnderscore(boardname))); err != nil {
79 | log.Println("Error writing form field")
80 | return err
81 | }
82 | // Don't forget to close the multipart writer.
83 | // If you don't close it, your request will be missing the terminating boundary.
84 | w.Close()
85 |
86 | // Now that you have a form, you can submit it to your handler.
87 | req, err := http.NewRequest("POST", _url, &b)
88 | if err != nil {
89 | log.Println("Error creating post request")
90 | return err
91 | }
92 | // Don't forget to set the content type, this will contain the boundary.
93 | req.Header.Set("Content-Type", w.FormDataContentType())
94 | req.SetBasicAuth("root", "arduino")
95 |
96 | //h.broadcastSys <- []byte("Start flashing with command " + cmdString)
97 | log.Printf("Network flashing on " + portname)
98 | mapD := map[string]string{"ProgrammerStatus": "Starting", "Cmd": "POST"}
99 | mapB, _ := json.Marshal(mapD)
100 | h.broadcastSys <- mapB
101 |
102 | // Submit the request
103 | client := &http.Client{}
104 | res, err := client.Do(req)
105 | if err != nil {
106 | log.Println("Error during post request")
107 | return err
108 | }
109 |
110 | // Check the response
111 | if res.StatusCode != http.StatusOK {
112 | err = fmt.Errorf("bad status: %s", res.Status)
113 | }
114 |
115 | if err != nil {
116 | log.Printf("Command finished with error: %v ", err)
117 | mapD := map[string]string{"ProgrammerStatus": "Error " + res.Status, "Msg": "Could not program the board", "Output": "", "Err": "Could not program the board"}
118 | mapB, _ := json.Marshal(mapD)
119 | h.broadcastSys <- mapB
120 | } else {
121 | log.Printf("Finished without error. Good stuff.")
122 | mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok", "Output": ""}
123 | mapB, _ := json.Marshal(mapD)
124 | h.broadcastSys <- mapB
125 | // analyze stdin
126 | }
127 | return err
128 | }
129 |
130 | func spProgramLocal(portname string, boardname string, filePath string) {
131 | isFound, flasher, mycmd := assembleCompilerCommand(boardname, portname, filePath)
132 | mapD := map[string]string{"ProgrammerStatus": "CommandReady", "IsFound": strconv.FormatBool(isFound), "Flasher": flasher, "Cmd": strings.Join(mycmd, " ")}
133 | mapB, _ := json.Marshal(mapD)
134 | h.broadcastSys <- mapB
135 |
136 | if isFound {
137 | spHandlerProgram(flasher, mycmd)
138 | } else {
139 | spErr("Could not find the board " + boardname + " that you were trying to program.")
140 | mapD := map[string]string{"ProgrammerStatus": "Failed", "IsFound": strconv.FormatBool(isFound), "Flasher": flasher, "Cmd": strings.Join(mycmd, " "), "Err": "Could not find the board " + boardname + " that you were trying to program."}
141 | mapB, _ := json.Marshal(mapD)
142 | h.broadcastSys <- mapB
143 | return
144 | }
145 | }
146 |
147 | func spProgram(portname string, boardname string, filePath string) {
148 |
149 | spProgramRW(portname, boardname, "", filePath)
150 | }
151 |
152 | func spProgramRW(portname string, boardname string, boardname_rewrite string, filePath string) {
153 | compiling = true
154 |
155 | defer func() {
156 | time.Sleep(1500 * time.Millisecond)
157 | compiling = false
158 | }()
159 |
160 | // check if the port is physical or network
161 | var networkPort bool
162 | myport, exist := findPortByNameRerun(portname, false)
163 | if !exist {
164 | // it could be a network port that has not been found at the second lap
165 | networkPort = true
166 | } else {
167 | networkPort = myport.NetworkPort
168 | }
169 |
170 | var err error
171 |
172 | if networkPort {
173 | if boardname_rewrite == "" {
174 | err = spProgramNetwork(portname, boardname_rewrite, filePath)
175 | } else {
176 | err = spProgramNetwork(portname, boardname, filePath)
177 | }
178 | if err != nil {
179 | mapD := map[string]string{"ProgrammerStatus": "Error " + err.Error(), "Msg": "Could not program the board", "Output": "", "Err": err.Error()}
180 | mapB, _ := json.Marshal(mapD)
181 | h.broadcastSys <- mapB
182 | }
183 | } else {
184 | spProgramLocal(portname, boardname, filePath)
185 | }
186 | }
187 |
188 | func spHandlerProgram(flasher string, cmdString []string) {
189 |
190 | var oscmd *exec.Cmd
191 | // if runtime.GOOS == "darwin" {
192 | // sh, _ := exec.LookPath("sh")
193 | // // prepend the flasher to run it via sh
194 | // cmdString = append([]string{flasher}, cmdString...)
195 | // oscmd = exec.Command(sh, cmdString...)
196 | // } else {
197 | oscmd = exec.Command(flasher, cmdString...)
198 | // }
199 |
200 | // Stdout buffer
201 | //var cmdOutput []byte
202 |
203 | //h.broadcastSys <- []byte("Start flashing with command " + cmdString)
204 | log.Printf("Flashing with command:" + strings.Join(cmdString, " "))
205 | mapD := map[string]string{"ProgrammerStatus": "Starting", "Cmd": strings.Join(cmdString, " ")}
206 | mapB, _ := json.Marshal(mapD)
207 | h.broadcastSys <- mapB
208 |
209 | cmdOutput, err := oscmd.CombinedOutput()
210 |
211 | if err != nil {
212 | log.Printf("Command finished with error: %v "+string(cmdOutput), err)
213 | mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board", "Output": string(cmdOutput), "Err": string(cmdOutput)}
214 | mapB, _ := json.Marshal(mapD)
215 | h.broadcastSys <- mapB
216 | } else {
217 | log.Printf("Finished without error. Good stuff. stdout: " + string(cmdOutput))
218 | mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok", "Output": string(cmdOutput)}
219 | mapB, _ := json.Marshal(mapD)
220 | h.broadcastSys <- mapB
221 | // analyze stdin
222 |
223 | }
224 | }
225 |
226 | func formatCmdline(cmdline string, boardOptions map[string]string) (string, bool) {
227 |
228 | list := strings.Split(cmdline, "{")
229 | if len(list) == 1 {
230 | return cmdline, false
231 | }
232 | cmdline = ""
233 | for _, item := range list {
234 | item_s := strings.Split(item, "}")
235 | item = boardOptions[item_s[0]]
236 | if len(item_s) == 2 {
237 | cmdline += item + item_s[1]
238 | } else {
239 | if item != "" {
240 | cmdline += item
241 | } else {
242 | cmdline += item_s[0]
243 | }
244 | }
245 | }
246 | log.Println(cmdline)
247 | return cmdline, true
248 | }
249 |
250 | func containsStr(s []string, e string) bool {
251 | for _, a := range s {
252 | if a == e {
253 | return true
254 | }
255 | }
256 | return false
257 | }
258 |
259 | func findNewPortName(slice1 []string, slice2 []string) string {
260 | m := map[string]int{}
261 |
262 | for _, s1Val := range slice1 {
263 | m[s1Val] = 1
264 | }
265 | for _, s2Val := range slice2 {
266 | m[s2Val] = m[s2Val] + 1
267 | }
268 |
269 | for mKey, mVal := range m {
270 | if mVal == 1 {
271 | return mKey
272 | }
273 | }
274 |
275 | return ""
276 | }
277 |
278 | func assembleCompilerCommand(boardname string, portname string, filePath string) (bool, string, []string) {
279 |
280 | // get executable (self)path and use it as base for all other paths
281 | execPath, _ := osext.Executable()
282 |
283 | boardFields := strings.Split(boardname, ":")
284 | if len(boardFields) != 3 {
285 | mapD := map[string]string{"Err": "Board need to be specified in core:architecture:name format"}
286 | mapB, _ := json.Marshal(mapD)
287 | h.broadcastSys <- mapB
288 | return false, "", nil
289 | }
290 | tempPath := (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/boards.txt")
291 | file, err := os.Open(tempPath)
292 | if err != nil {
293 | mapD := map[string]string{"Err": "Could not find board: " + boardname}
294 | mapB, _ := json.Marshal(mapD)
295 | h.broadcastSys <- mapB
296 | log.Println("Error:", err)
297 | return false, "", nil
298 | }
299 | scanner := bufio.NewScanner(file)
300 |
301 | boardOptions := make(map[string]string)
302 | uploadOptions := make(map[string]string)
303 |
304 | for scanner.Scan() {
305 | // map everything matching with boardname
306 | if strings.Contains(scanner.Text(), boardFields[2]) {
307 | arr := strings.Split(scanner.Text(), "=")
308 | arr[0] = strings.Replace(arr[0], boardFields[2]+".", "", 1)
309 | boardOptions[arr[0]] = arr[1]
310 | }
311 | }
312 |
313 | if len(boardOptions) == 0 {
314 | mapD := map[string]string{"Err": "Board " + boardFields[2] + " is not part of " + boardFields[0] + ":" + boardFields[1]}
315 | mapB, _ := json.Marshal(mapD)
316 | h.broadcastSys <- mapB
317 | return false, "", nil
318 | }
319 |
320 | // filepath need special care; the project_name var is the filename minus its extension (hex or bin)
321 | // if we are going to modify standard IDE files we also could pass ALL filename
322 | filePath = strings.Trim(filePath, "\n")
323 | boardOptions["build.path"] = filepath.Dir(filePath)
324 | boardOptions["build.project_name"] = strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filepath.Base(filePath)))
325 |
326 | file.Close()
327 |
328 | // get infos about the programmer
329 | tempPath = (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/platform.txt")
330 | file, err = os.Open(tempPath)
331 | if err != nil {
332 | mapD := map[string]string{"Err": "Could not find board: " + boardname}
333 | mapB, _ := json.Marshal(mapD)
334 | h.broadcastSys <- mapB
335 | log.Println("Error:", err)
336 | return false, "", nil
337 | }
338 | scanner = bufio.NewScanner(file)
339 |
340 | tool := boardOptions["upload.tool"]
341 |
342 | for scanner.Scan() {
343 | // map everything matching with upload
344 | if strings.Contains(scanner.Text(), tool) {
345 | arr := strings.Split(scanner.Text(), "=")
346 | uploadOptions[arr[0]] = arr[1]
347 | arr[0] = strings.Replace(arr[0], "tools."+tool+".", "", 1)
348 | boardOptions[arr[0]] = arr[1]
349 | // we have a "=" in command line
350 | if len(arr) > 2 {
351 | boardOptions[arr[0]] = arr[1] + "=" + arr[2]
352 | }
353 | }
354 | }
355 | file.Close()
356 |
357 | // multiple verisons of the same programmer can be handled if "version" is specified
358 | version := uploadOptions["runtime.tools."+tool+".version"]
359 | path := (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/" + version)
360 | if err != nil {
361 | mapD := map[string]string{"Err": "Could not find board: " + boardname}
362 | mapB, _ := json.Marshal(mapD)
363 | h.broadcastSys <- mapB
364 | log.Println("Error:", err)
365 | return false, "", nil
366 | }
367 |
368 | boardOptions["runtime.tools."+tool+".path"] = path
369 |
370 | cmdline := boardOptions["upload.pattern"]
371 | // remove cmd.path as it is handled differently
372 | cmdline = strings.Replace(cmdline, "\"{cmd.path}\"", " ", 1)
373 | cmdline = strings.Replace(cmdline, "\"{path}/{cmd}\"", " ", 1)
374 | cmdline = strings.Replace(cmdline, "\"", "", -1)
375 |
376 | initialPortName := portname
377 |
378 | // some boards (eg. Leonardo, Yun) need a special procedure to enter bootloader
379 | if boardOptions["upload.use_1200bps_touch"] == "true" {
380 | // triggers bootloader mode
381 | // the portname could change in this occasion (expecially on Windows) so change portname
382 | // with the port which will reappear
383 | log.Println("Restarting in bootloader mode")
384 |
385 | mode := &serial.Mode{
386 | BaudRate: 1200,
387 | Vmin: 1,
388 | Vtimeout: 0,
389 | }
390 | port, err := serial.OpenPort(portname, mode)
391 | if err != nil {
392 | log.Println(err)
393 | return false, "", nil
394 | }
395 | //port.SetDTR(false)
396 | port.Close()
397 | time.Sleep(time.Second / 2.0)
398 |
399 | timeout := false
400 | go func() {
401 | time.Sleep(2 * time.Second)
402 | timeout = true
403 | }()
404 |
405 | // time.Sleep(time.Second / 4)
406 | // wait for port to reappear
407 | if boardOptions["upload.wait_for_upload_port"] == "true" {
408 | after_reset_ports, _ := serial.GetPortsList()
409 | log.Println(after_reset_ports)
410 | var ports []string
411 | for {
412 | ports, _ = serial.GetPortsList()
413 | log.Println(ports)
414 | time.Sleep(time.Millisecond * 200)
415 | portname = findNewPortName(ports, after_reset_ports)
416 | if portname != "" {
417 | break
418 | }
419 | if timeout {
420 | break
421 | }
422 | }
423 | }
424 | }
425 |
426 | if portname == "" {
427 | portname = initialPortName
428 | }
429 |
430 | boardOptions["serial.port"] = portname
431 | boardOptions["serial.port.file"] = filepath.Base(portname)
432 |
433 | // split the commandline in substrings and recursively replace mapped strings
434 | cmdlineSlice := strings.Split(cmdline, " ")
435 | var winded = true
436 | for index, _ := range cmdlineSlice {
437 | winded = true
438 | for winded != false {
439 | cmdlineSlice[index], winded = formatCmdline(cmdlineSlice[index], boardOptions)
440 | }
441 | }
442 |
443 | tool = (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/bin/" + tool)
444 | // the file doesn't exist, we are on windows
445 | if _, err := os.Stat(tool); err != nil {
446 | tool = tool + ".exe"
447 | // convert all "/" to "\"
448 | tool = strings.Replace(tool, "/", "\\", -1)
449 | }
450 |
451 | // remove blanks from cmdlineSlice
452 | var cmdlineSliceOut []string
453 | for _, element := range cmdlineSlice {
454 | if element != "" {
455 | cmdlineSliceOut = append(cmdlineSliceOut, element)
456 | }
457 | }
458 |
459 | return (tool != ""), tool, cmdlineSliceOut
460 | }
461 |
--------------------------------------------------------------------------------
/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 "sync"
32 |
33 | type queuenode struct {
34 | data string
35 | id string
36 | next *queuenode
37 | }
38 |
39 | // A go-routine safe FIFO (first in first out) data stucture.
40 | type Queue struct {
41 | head *queuenode
42 | tail *queuenode
43 | count int
44 | lock *sync.Mutex
45 | lenOfCmds int
46 | }
47 |
48 | // Creates a new pointer to a new queue.
49 | func NewQueue() *Queue {
50 | q := &Queue{}
51 | q.lock = &sync.Mutex{}
52 | return q
53 | }
54 |
55 | // Returns the number of elements in the queue (i.e. size/length)
56 | // go-routine safe.
57 | func (q *Queue) Len() int {
58 | q.lock.Lock()
59 | defer q.lock.Unlock()
60 | return q.count
61 | }
62 |
63 | // Returns the length of the data (gcode cmd) in the queue (i.e. size/length)
64 | // go-routine safe.
65 | func (q *Queue) LenOfCmds() int {
66 | q.lock.Lock()
67 | defer q.lock.Unlock()
68 | return q.lenOfCmds
69 | }
70 |
71 | // Pushes/inserts a value at the end/tail of the queue.
72 | // Note: this function does mutate the queue.
73 | // go-routine safe.
74 | func (q *Queue) Push(item string, id string) {
75 | q.lock.Lock()
76 | defer q.lock.Unlock()
77 |
78 | n := &queuenode{data: item, id: id}
79 |
80 | if q.tail == nil {
81 | q.tail = n
82 | q.head = n
83 | } else {
84 | q.tail.next = n
85 | q.tail = n
86 | }
87 | q.count++
88 | q.lenOfCmds += len(item)
89 | }
90 |
91 | // Returns the value at the front of the queue.
92 | // i.e. the oldest value in the queue.
93 | // Note: this function does mutate the queue.
94 | // go-routine safe.
95 | func (q *Queue) Poll() (string, string) {
96 | q.lock.Lock()
97 | defer q.lock.Unlock()
98 |
99 | if q.head == nil {
100 | return "", ""
101 | }
102 |
103 | n := q.head
104 | q.head = n.next
105 |
106 | if q.head == nil {
107 | q.tail = nil
108 | }
109 | q.count--
110 | q.lenOfCmds -= len(n.data)
111 |
112 | return n.data, n.id
113 | }
114 |
115 | // Returns a read value at the front of the queue.
116 | // i.e. the oldest value in the queue.
117 | // Note: this function does NOT mutate the queue.
118 | // go-routine safe.
119 | func (q *Queue) Peek() (string, string) {
120 | q.lock.Lock()
121 | defer q.lock.Unlock()
122 |
123 | n := q.head
124 | if n == nil || n.data == "" {
125 | return "", ""
126 | }
127 |
128 | return n.data, n.id
129 | }
130 |
131 | // Returns a read value at the front of the queue.
132 | // i.e. the oldest value in the queue.
133 | // Note: this function does NOT mutate the queue.
134 | // go-routine safe.
135 | func (q *Queue) Delete() {
136 | q.lock.Lock()
137 | defer q.lock.Unlock()
138 |
139 | q.head = nil
140 | q.tail = nil
141 | q.count = 0
142 | q.lenOfCmds = 0
143 | }
144 |
--------------------------------------------------------------------------------
/serial.go:
--------------------------------------------------------------------------------
1 | // Supports Windows, Linux, Mac, BeagleBone Black, and Raspberry Pi
2 |
3 | package main
4 |
5 | import (
6 | //"bufio"
7 | "encoding/json"
8 | "fmt"
9 | //"path/filepath"
10 | //"github.com/kballard/go-shellquote"
11 | //"github.com/johnlauer/goserial"
12 | //"github.com/mikepb/go-serial"
13 | //"github.com/facchinm/go-serial"
14 | //"github.com/kardianos/osext"
15 | "log"
16 | //"os"
17 | "regexp"
18 | "runtime/debug"
19 | "strconv"
20 | "strings"
21 | "time"
22 | )
23 |
24 | type writeRequest struct {
25 | p *serport
26 | d string
27 | buffer bool
28 | id string
29 | }
30 |
31 | type writeRequestJson struct {
32 | p *serport
33 | P string
34 | Data []writeRequestJsonData
35 | }
36 |
37 | type writeRequestJsonData struct {
38 | D string
39 | Id string
40 | Buf string
41 | }
42 |
43 | type qReportJson struct {
44 | Cmd string
45 | QCnt int
46 | P string
47 | Data []qReportJsonData
48 | }
49 |
50 | type qReportJsonData struct {
51 | D string
52 | Id string
53 | Buf string `json:"-"`
54 | Parts int `json:"-"`
55 | }
56 |
57 | type qReport struct {
58 | Cmd string
59 | QCnt int
60 | Type []string `json:"-"`
61 | Ids []string
62 | D []string //`json:"-"`
63 | Port string
64 | }
65 |
66 | type serialhub struct {
67 | // Opened serial ports.
68 | ports map[*serport]bool
69 |
70 | //open chan *io.ReadWriteCloser
71 | //write chan *serport, chan []byte
72 | write chan writeRequest
73 | //read chan []byte
74 |
75 | writeJson chan writeRequestJson
76 |
77 | // Register requests from the connections.
78 | register chan *serport
79 |
80 | // Unregister requests from connections.
81 | unregister chan *serport
82 |
83 | // regexp for json trimming
84 | reJsonTrim *regexp.Regexp
85 | }
86 |
87 | type SpPortList struct {
88 | Ports []SpPortItem
89 | Network bool
90 | }
91 |
92 | type SpPortItem struct {
93 | Name string
94 | Friendly string
95 | SerialNumber string
96 | DeviceClass string
97 | IsOpen bool
98 | IsPrimary bool
99 | RelatedNames []string
100 | Baud int
101 | BufferAlgorithm string
102 | AvailableBufferAlgorithms []string
103 | Ver float32
104 | NetworkPort bool
105 | }
106 |
107 | // SerialPorts contains the ports attached to the machine
108 | var SerialPorts SpPortList
109 |
110 | // NetworkPorts contains the ports on the network
111 | var NetworkPorts SpPortList
112 |
113 | var sh = serialhub{
114 | //write: make(chan *serport, chan []byte),
115 | write: make(chan writeRequest),
116 | writeJson: make(chan writeRequestJson),
117 | register: make(chan *serport),
118 | unregister: make(chan *serport),
119 | ports: make(map[*serport]bool),
120 | reJsonTrim: regexp.MustCompile("sendjson"),
121 | }
122 |
123 | func (sh *serialhub) run() {
124 |
125 | log.Print("Inside run of serialhub")
126 | //cmdIdCtr := 0
127 |
128 | //s := ser.open()
129 | //ser.s := s
130 | //ser.write(s, []byte("hello serial data"))
131 | for {
132 | select {
133 | case p := <-sh.register:
134 | log.Print("Registering a port: ", p.portConf.Name)
135 | isPrimary := "false"
136 | if p.IsPrimary {
137 | isPrimary = "true"
138 | }
139 | h.broadcastSys <- []byte("{\"Cmd\":\"Open\",\"Desc\":\"Got register/open on port.\",\"Port\":\"" + p.portConf.Name + "\",\"IsPrimary\":" + isPrimary + ",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + ",\"BufferType\":\"" + p.BufferType + "\"}")
140 | //log.Print(p.portConf.Name)
141 | sh.ports[p] = true
142 | case p := <-sh.unregister:
143 | log.Print("Unregistering a port: ", p.portConf.Name)
144 | h.broadcastSys <- []byte("{\"Cmd\":\"Close\",\"Desc\":\"Got unregister/close on port.\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")
145 | delete(sh.ports, p)
146 | close(p.sendBuffered)
147 | close(p.sendNoBuf)
148 | case wrj := <-sh.writeJson:
149 | // if the user sent in the commands as json
150 | writeJson(wrj)
151 |
152 | case wr := <-sh.write:
153 | // if user sent in the commands as one text mode line
154 | write(wr, "")
155 | }
156 | }
157 | }
158 |
159 | func writeJson(wrj writeRequestJson) {
160 | // we'll parse this json request and then do a write() as if
161 | // the cmd was sent in as text mode
162 |
163 | // create array to hold our qReportJsonData
164 | qReportDataArr := []qReportJsonData{}
165 |
166 | for _, cmdJson := range wrj.Data {
167 | var wr writeRequest
168 | wr.d = cmdJson.D //[]byte(cmdJson.D)
169 | //wr.id = cmdJson.Id
170 | wr.p = wrj.p
171 | if cmdJson.Buf == "Buf" {
172 | wr.buffer = true
173 | } else if cmdJson.Buf == "NoBuf" {
174 | wr.buffer = false
175 | } else {
176 | wr.buffer = true
177 | }
178 | //write(wr, cmdJson.Id, true)
179 |
180 | // we are sending 1 cmd in, but we may get back multiple cmds
181 | // because the BreakApartCommands() can add/modify stuff, so keep
182 | // that in mind
183 | cmds, idArr, bufTypeArr := createCommands(wr, cmdJson.Id)
184 |
185 | for index, _ := range cmds {
186 | // create q report data
187 | cmdId := idArr[index]
188 | // append stuff to the id if this cmd was one line and just got broken up
189 | // the first line will have a normal id like "123"
190 | // the 2nd, 3rd,e tc line will have id like "123-part-2-3", "123-part-3-3"
191 | if index > 0 {
192 | cmdId = fmt.Sprintf("%v-part-%v-%v", cmdId, (index + 1), len(cmds))
193 | }
194 | qrd := qReportJsonData{D: cmds[index], Id: cmdId, Parts: len(cmds)}
195 | // if user forced the buffer type, just use it
196 | if cmdJson.Buf == "Buf" || cmdJson.Buf == "NoBuf" {
197 | qrd.Buf = cmdJson.Buf
198 | } else {
199 | // else use the buffer type figured out in createCommands()
200 | qrd.Buf = bufTypeArr[index]
201 | }
202 | qReportDataArr = append(qReportDataArr, qrd)
203 |
204 | }
205 |
206 | }
207 |
208 | // do our own report
209 | qr := qReportJson{
210 | Cmd: "Queued",
211 | Data: qReportDataArr,
212 | QCnt: wrj.p.itemsInBuffer,
213 | P: wrj.p.portConf.Name,
214 | }
215 | json, _ := json.Marshal(qr)
216 | h.broadcastSys <- json
217 |
218 | // now send off the commands to the appropriate channel
219 | for _, qrd := range qReportDataArr {
220 |
221 | if qrd.Buf == "Buf" {
222 | //log.Println("Json sending to wr.p.sendBuffered")
223 | wrj.p.sendBuffered <- Cmd{qrd.D, qrd.Id, false, false}
224 | } else {
225 | log.Println("Json sending to wr.p.sendNoBuf")
226 | wrj.p.sendNoBuf <- Cmd{qrd.D, qrd.Id, true, false}
227 | }
228 | }
229 |
230 | // garbage collect
231 | if *gcType == "max" {
232 | debug.FreeOSMemory()
233 | }
234 | }
235 |
236 | func write(wr writeRequest, id string) {
237 | cmds, idArr, bufTypeArr := createCommands(wr, id)
238 |
239 | qr := qReport{
240 | Cmd: "Queued",
241 | //Type: bufTypeArr,
242 | Ids: idArr,
243 | D: cmds,
244 | QCnt: wr.p.itemsInBuffer,
245 | Port: wr.p.portConf.Name,
246 | }
247 | json, _ := json.Marshal(qr)
248 | h.broadcastSys <- json
249 |
250 | // now send off the commands to the appropriate channel
251 | for index, cmdToSendToChannel := range cmds {
252 | //cmdIdCtr++
253 | //cmdId := "fakeid-" + strconv.Itoa(cmdIdCtr)
254 | cmdId := idArr[index]
255 | if bufTypeArr[index] == "Buf" {
256 | log.Println("Send was normal send, so sending to wr.p.sendBuffered")
257 | wr.p.sendBuffered <- Cmd{cmdToSendToChannel, cmdId, false, false}
258 | } else {
259 | log.Println("Send was sendnobuf, so sending to wr.p.sendNoBuf")
260 | wr.p.sendNoBuf <- Cmd{cmdToSendToChannel, cmdId, true, false}
261 | }
262 | }
263 |
264 | // garbage collect
265 | if *gcType == "max" {
266 | debug.FreeOSMemory()
267 | }
268 |
269 | }
270 |
271 | func createCommands(wr writeRequest, id string) ([]string, []string, []string) {
272 | //log.Print("Got a write to a port")
273 | //log.Print("Port: ", string(wr.p.portConf.Name))
274 | //log.Print(wr.p)
275 | //log.Print("Data is: ")
276 | //log.Println(wr.d)
277 | //log.Print("Data:" + string(wr.d))
278 | //log.Print("-----")
279 | log.Printf("Got write to id:%v, port:%v, buffer:%v, data:%v", id, wr.p.portConf.Name, wr.buffer, string(wr.d))
280 |
281 | dataCmd := string(wr.d)
282 |
283 | // break the data into individual commands for queuing
284 | // this is important because:
285 | // 1) we could be sent multiple serial commands at once and the
286 | // serial device may want them sent in smaller chunks to give
287 | // better feedback. For example, if we're sent G0 X0\nG0 Y10\n we
288 | // could happily send that to a CNC controller like a TinyG. However,
289 | // on something like TinyG that would chew up 2 buffer planners. So,
290 | // to better match what will happen, we break those into 2 commands
291 | // so we get a better granularity of getting back qr responses or
292 | // other feedback.
293 | // 2) we need to break apart specific commands potentially that do
294 | // not need newlines. For example, on TinyG we need !~% to be different
295 | // commands because we need to pivot off of what they mean. ! means pause
296 | // the sending. So, we need that command as its own command in order of
297 | // how they were sent to us.
298 | cmds := wr.p.bufferwatcher.BreakApartCommands(dataCmd)
299 | dataArr := []string{}
300 | bufTypeArr := []string{}
301 | idArr := []string{}
302 | for _, cmd := range cmds {
303 |
304 | // push this cmd onto dataArr for reporting
305 | dataArr = append(dataArr, cmd)
306 | idArr = append(idArr, id)
307 |
308 | // do extra check to see if certain command should wipe out
309 | // the entire internal serial port buffer we're holding in wr.p.sendBuffered
310 | wipeBuf := wr.p.bufferwatcher.SeeIfSpecificCommandsShouldWipeBuffer(cmd)
311 | if wipeBuf {
312 | log.Printf("We got a command that is asking us to wipe the sendBuffered buf. cmd:%v\n", cmd)
313 | // just wipe out the current channel and create new
314 | // hopefully garbage collection works here
315 |
316 | // close the channel
317 | //close(wr.p.sendBuffered)
318 |
319 | // consume all stuff queued
320 | func() {
321 | ctr := 0
322 | /*
323 | for data := range wr.p.sendBuffered {
324 | log.Printf("Consuming sendBuffered queue. d:%v\n", string(data))
325 | ctr++
326 | }*/
327 |
328 | keepLooping := true
329 | for keepLooping {
330 | select {
331 | case d, ok := <-wr.p.sendBuffered:
332 | log.Printf("Consuming sendBuffered queue. ok:%v, d:%v, id:%v\n", ok, string(d.data), string(d.id))
333 | ctr++
334 | // since we just consumed a buffer item, we need to decrement bufcount
335 | // we are doing this artificially because we artifically threw
336 | // away what was in the bufer
337 | wr.p.itemsInBuffer--
338 | if ok == false {
339 | keepLooping = false
340 | }
341 | default:
342 | keepLooping = false
343 | log.Println("Hit default in select clause")
344 | }
345 | }
346 | log.Printf("Done consuming sendBuffered cmds. ctr:%v\n", ctr)
347 | }()
348 |
349 | // we still will likely have a sendBuffered that is in the BlockUntilReady()
350 | // that we have to deal with so it doesn't send to the serial port
351 | // when we release it
352 | // send semaphore release if there is one on the BlockUntilReady()
353 | // this method will release the BlockUntilReady() but with an unblock
354 | // of type 2 which means cancel the send
355 | wr.p.bufferwatcher.ReleaseLock()
356 |
357 | // let user know we wiped queue
358 | log.Printf("itemsInBuffer:%v\n", wr.p.itemsInBuffer)
359 | h.broadcastSys <- []byte("{\"Cmd\":\"WipedQueue\",\"QCnt\":" + strconv.Itoa(wr.p.itemsInBuffer) + ",\"Port\":\"" + wr.p.portConf.Name + "\"}")
360 |
361 | }
362 |
363 | // do extra check to see if any specific commands should pause
364 | // the buffer. this means we'll trigger a BlockUntilReady() block
365 | pauseBuf := wr.p.bufferwatcher.SeeIfSpecificCommandsShouldPauseBuffer(cmd)
366 | if pauseBuf {
367 | log.Printf("We need to pause our internal buffer.\n")
368 | wr.p.bufferwatcher.Pause()
369 | }
370 |
371 | // do extra check to see if any specific commands should unpause
372 | // the buffer. this means we'll release the BlockUntilReady() block
373 | unpauseBuf := wr.p.bufferwatcher.SeeIfSpecificCommandsShouldUnpauseBuffer(cmd)
374 | if unpauseBuf {
375 | log.Printf("We need to unpause our internal buffer.\n")
376 | wr.p.bufferwatcher.Unpause()
377 | }
378 |
379 | // do extra check to see if certain commands for this buffer type
380 | // should skip the internal serial port buffering
381 | // for example ! on tinyg and grbl should skip
382 | typeBuf := "" // set in if stmt below for reporting afterwards
383 |
384 | if wr.buffer {
385 | bufferSkip := wr.p.bufferwatcher.SeeIfSpecificCommandsShouldSkipBuffer(cmd)
386 | if bufferSkip {
387 | log.Printf("Forcing this cmd to skip buffer. cmd:%v\n", cmd)
388 | //wr.buffer = false
389 | typeBuf = "NoBuf"
390 | } else {
391 | typeBuf = "Buf"
392 | }
393 | } else {
394 | typeBuf = "NoBuf"
395 | }
396 |
397 | /*
398 | if wr.buffer {
399 | //log.Println("Send was normal send, so sending to wr.p.sendBuffered")
400 | //wr.p.sendBuffered <- []byte(cmd)
401 | typeBuf = "Buf"
402 | } else {
403 | //log.Println("Send was sendnobuf, so sending to wr.p.sendNoBuf")
404 | //wr.p.sendNoBuf <- []byte(cmd)
405 | typeBuf = "NoBuf"
406 | }
407 | */
408 | // increment queue counter for reporting
409 | wr.p.itemsInBuffer++
410 | log.Printf("itemsInBuffer:%v\n", wr.p.itemsInBuffer)
411 |
412 | // push the type of this command to bufTypeArr
413 | bufTypeArr = append(bufTypeArr, typeBuf)
414 |
415 | } // for loop on broken apart commands
416 |
417 | return cmds, idArr, bufTypeArr
418 | }
419 |
420 | func writeToChannels(cmds []string, idArr []string, bufTypeArr []string) {
421 |
422 | }
423 |
424 | // spList broadcasts a Json representation of the ports found
425 | func spList(network bool) {
426 | var list SpPortList
427 | if network {
428 | list = NetworkPorts
429 | } else {
430 | list = SerialPorts
431 | }
432 | ls, err := json.MarshalIndent(list, "", "\t")
433 | if err != nil {
434 | log.Println(err)
435 | h.broadcastSys <- []byte("Error creating json on port list " +
436 | err.Error())
437 | } else {
438 | h.broadcastSys <- ls
439 | }
440 | }
441 |
442 | // discoverLoop periodically update the list of ports found
443 | func discoverLoop() {
444 | SerialPorts.Network = false
445 | SerialPorts.Ports = make([]SpPortItem, 0)
446 | NetworkPorts.Network = true
447 | NetworkPorts.Ports = make([]SpPortItem, 0)
448 |
449 | go func() {
450 | for {
451 | if !compiling {
452 | spListDual(false)
453 | }
454 | time.Sleep(2 * time.Second)
455 | }
456 | }()
457 | go func() {
458 | for {
459 | spListDual(true)
460 | time.Sleep(2 * time.Second)
461 | }
462 | }()
463 | }
464 |
465 | func spListDual(network bool) {
466 |
467 | // call our os specific implementation of getting the serial list
468 | list, _ := GetList(network)
469 |
470 | // do a quick loop to see if any of our open ports
471 | // did not end up in the list port list. this can
472 | // happen on windows in a fallback scenario where an
473 | // open port can't be identified because it is locked,
474 | // so just solve that by manually inserting
475 | for port := range sh.ports {
476 |
477 | isFound := false
478 | for _, item := range list {
479 | if strings.ToLower(port.portConf.Name) == strings.ToLower(item.Name) {
480 | isFound = true
481 | }
482 | }
483 |
484 | if !isFound {
485 | // artificially push to front of port list
486 | log.Println(fmt.Sprintf("Did not find an open port in the serial port list. We are going to artificially push it onto the list. port:%v", port.portConf.Name))
487 | var ossp OsSerialPort
488 | ossp.Name = port.portConf.Name
489 | ossp.FriendlyName = port.portConf.Name
490 | list = append([]OsSerialPort{ossp}, list...)
491 | }
492 | }
493 |
494 | // we have a full clean list of ports now. iterate thru them
495 | // to append the open/close state, baud rates, etc to make
496 | // a super clean nice list to send back to browser
497 | n := len(list)
498 | spl := SpPortList{make([]SpPortItem, n, n), network}
499 |
500 | ctr := 0
501 | for _, item := range list {
502 |
503 | /*
504 | Name string
505 | Friendly string
506 | IsOpen bool
507 | IsPrimary bool
508 | RelatedNames []string
509 | Baud int
510 | RtsOn bool
511 | DtrOn bool
512 | BufferAlgorithm string
513 | AvailableBufferAlgorithms []string
514 | Ver float32
515 | */
516 | spl.Ports[ctr] = SpPortItem{
517 | Name: item.Name,
518 | Friendly: item.FriendlyName,
519 | SerialNumber: item.SerialNumber,
520 | DeviceClass: item.DeviceClass,
521 | IsOpen: false,
522 | IsPrimary: false,
523 | RelatedNames: item.RelatedNames,
524 | Baud: 0,
525 | BufferAlgorithm: "",
526 | AvailableBufferAlgorithms: availableBufferAlgorithms,
527 | Ver: versionFloat,
528 | NetworkPort: item.NetworkPort,
529 | }
530 |
531 | // figure out if port is open
532 | //spl.Ports[ctr].IsOpen = false
533 | myport, isFound := findPortByName(item.Name)
534 |
535 | if isFound {
536 | // we found our port
537 | spl.Ports[ctr].IsOpen = true
538 | spl.Ports[ctr].Baud = myport.portConf.Baud
539 | spl.Ports[ctr].BufferAlgorithm = myport.BufferType
540 | spl.Ports[ctr].IsPrimary = myport.IsPrimary
541 | }
542 | //ls += "{ \"name\" : \"" + item.Name + "\", \"friendly\" : \"" + item.FriendlyName + "\" },\n"
543 | ctr++
544 | }
545 |
546 | if network {
547 | NetworkPorts = spl
548 | } else {
549 | SerialPorts = spl
550 | }
551 | }
552 |
553 | func spListOld() {
554 | ls := "{\"serialports\" : [\n"
555 | list, _ := getList()
556 | for _, item := range list {
557 | ls += "{ \"name\" : \"" + item.Name + "\", \"friendly\" : \"" + item.FriendlyName + "\" },\n"
558 | }
559 | ls = strings.TrimSuffix(ls, "},\n")
560 | ls += "}\n"
561 | ls += "]}\n"
562 | h.broadcastSys <- []byte(ls)
563 | }
564 |
565 | func spErr(err string) {
566 | log.Println("Sending err back: ", err)
567 | //h.broadcastSys <- []byte(err)
568 | h.broadcastSys <- []byte("{\"Error\" : \"" + err + "\"}")
569 | }
570 |
571 | func spClose(portname string) {
572 | // look up the registered port by name
573 | // then call the close method inside serialport
574 | // that should cause an unregister channel call back
575 | // to myself
576 |
577 | myport, isFound := findPortByName(portname)
578 |
579 | if isFound {
580 | // we found our port
581 | spHandlerClose(myport)
582 | } else {
583 | // we couldn't find the port, so send err
584 | spErr("We could not find the serial port " + portname + " that you were trying to close.")
585 | }
586 | }
587 |
588 | func spWriteJson(arg string) {
589 |
590 | log.Printf("spWriteJson. arg:%v\n", arg)
591 |
592 | // remove sendjson string
593 | arg = sh.reJsonTrim.ReplaceAllString(arg, "")
594 | //log.Printf("string we're going to parse:%v\n", arg)
595 |
596 | // this is a structured command now for sending in serial commands multiple at a time
597 | // with an ID so we can send back the ID when the command is done
598 | var m writeRequestJson
599 | /*
600 | m.P = "COM22"
601 | var data writeRequestJsonData
602 | data.Id = "234"
603 | str := "yeah yeah"
604 | data.D = str //[]byte(str) //[]byte(string("blah blah"))
605 | m.Data = append(m.Data, data)
606 | //m.Data = append(m.Data, data)
607 | bm, err2 := json.Marshal(m)
608 | if err2 == nil {
609 | log.Printf("Test json serialize:%v\n", string(bm))
610 | }
611 | */
612 |
613 | err := json.Unmarshal([]byte(arg), &m)
614 |
615 | if err != nil {
616 | log.Printf("Problem decoding json. giving up. json:%v, err:%v\n", arg, err)
617 | spErr(fmt.Sprintf("Problem decoding json. giving up. json:%v, err:%v", arg, err))
618 | return
619 | }
620 |
621 | // see if we have this port open
622 | portname := m.P
623 | myport, isFound := findPortByName(portname)
624 |
625 | if !isFound {
626 | // we couldn't find the port, so send err
627 | spErr("We could not find the serial port " + portname + " that you were trying to write to.")
628 | return
629 | }
630 |
631 | // we found our port
632 | m.p = myport
633 |
634 | // send it to the writeJson channel
635 | sh.writeJson <- m
636 | }
637 |
638 | func spWrite(arg string) {
639 | // we will get a string of comXX asdf asdf asdf
640 | log.Println("Inside spWrite arg: " + arg)
641 | arg = strings.TrimPrefix(arg, " ")
642 | //log.Println("arg after trim: " + arg)
643 | args := strings.SplitN(arg, " ", 3)
644 | if len(args) != 3 {
645 | errstr := "Could not parse send command: " + arg
646 | log.Println(errstr)
647 | spErr(errstr)
648 | return
649 | }
650 | portname := strings.Trim(args[1], " ")
651 | //log.Println("The port to write to is:" + portname + "---")
652 | //log.Println("The data is:" + args[2] + "---")
653 |
654 | // see if we have this port open
655 | myport, isFound := findPortByName(portname)
656 |
657 | if !isFound {
658 | // we couldn't find the port, so send err
659 | spErr("We could not find the serial port " + portname + " that you were trying to write to.")
660 | return
661 | }
662 |
663 | // we found our port
664 | // create our write request
665 | var wr writeRequest
666 | wr.p = myport
667 |
668 | // see if args[0] is send or sendnobuf
669 | if args[0] != "sendnobuf" {
670 | // we were just given a "send" so buffer it
671 | wr.buffer = true
672 | } else {
673 | log.Println("sendnobuf specified so wr.buffer is false")
674 | wr.buffer = false
675 | }
676 |
677 | // include newline or not in the write? that is the question.
678 | // for now lets skip the newline
679 | //wr.d = []byte(args[2] + "\n")
680 | wr.d = args[2] //[]byte(args[2])
681 |
682 | // send it to the write channel
683 | sh.write <- wr
684 |
685 | }
686 |
687 | func findPortByName(portname string) (*serport, bool) {
688 | portnamel := strings.ToLower(portname)
689 | for port := range sh.ports {
690 | if strings.ToLower(port.portConf.Name) == portnamel {
691 | // we found our port
692 | //spHandlerClose(port)
693 | return port, true
694 | }
695 | }
696 | return nil, false
697 | }
698 |
699 | func findPortByNameRerun(portname string, network bool) (OsSerialPort, bool) {
700 | portnamel := strings.ToLower(portname)
701 | list, _ := GetList(network)
702 | for _, item := range list {
703 | if strings.ToLower(item.Name) == portnamel {
704 | return item, true
705 | }
706 | }
707 | return OsSerialPort{}, false
708 | }
709 |
710 | func spBufferAlgorithms() {
711 | //arr := []string{"default", "tinyg", "dummypause"}
712 | arr := availableBufferAlgorithms
713 | json := "{\"BufferAlgorithm\" : ["
714 | for _, elem := range arr {
715 | json += "\"" + elem + "\", "
716 | }
717 | json = regexp.MustCompile(", $").ReplaceAllString(json, "]}")
718 | h.broadcastSys <- []byte(json)
719 | }
720 |
721 | func spBaudRates() {
722 | arr := []string{"2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400"}
723 | json := "{\"BaudRate\" : ["
724 | for _, elem := range arr {
725 | json += "" + elem + ", "
726 | }
727 | json = regexp.MustCompile(", $").ReplaceAllString(json, "]}")
728 | h.broadcastSys <- []byte(json)
729 | }
730 |
--------------------------------------------------------------------------------
/seriallist.go:
--------------------------------------------------------------------------------
1 | // Supports Windows, Linux, Mac, and Raspberry Pi
2 |
3 | package main
4 |
5 | import (
6 | "github.com/facchinm/go-serial"
7 | "log"
8 | //"os"
9 | "regexp"
10 | )
11 |
12 | type OsSerialPort struct {
13 | Name string
14 | FriendlyName string
15 | RelatedNames []string // for some devices there are 2 or more ports, i.e. TinyG v9 has 2 serial ports
16 | SerialNumber string
17 | DeviceClass string
18 | Manufacturer string
19 | Product string
20 | IdProduct string
21 | IdVendor string
22 | NetworkPort bool
23 | }
24 |
25 | func GetList(network bool) ([]OsSerialPort, error) {
26 |
27 | //log.Println("Doing GetList()")
28 |
29 | if network {
30 | netportList, err := GetNetworkList()
31 | return netportList, err
32 | } else {
33 |
34 | // will timeout in 2 seconds
35 | ports, err := serial.GetPortsList()
36 |
37 | arrPorts := []OsSerialPort{}
38 | for _, element := range ports {
39 | arrPorts = append(arrPorts, OsSerialPort{Name: element, FriendlyName: element})
40 | }
41 |
42 | // see if we should filter the list
43 | if len(*regExpFilter) > 0 {
44 | // yes, user asked for a filter
45 | reFilter := regexp.MustCompile("(?i)" + *regExpFilter)
46 |
47 | newarrPorts := []OsSerialPort{}
48 | for _, element := range arrPorts {
49 | // if matches regex, include
50 | if reFilter.MatchString(element.Name) {
51 | newarrPorts = append(newarrPorts, element)
52 | } else if reFilter.MatchString(element.FriendlyName) {
53 | newarrPorts = append(newarrPorts, element)
54 | } else {
55 | log.Printf("serial port did not match. port: %v\n", element)
56 | }
57 |
58 | }
59 | arrPorts = newarrPorts
60 | }
61 |
62 | arrPorts = removeNonArduinoBoards(arrPorts)
63 | return arrPorts, err
64 | //log.Printf("Done doing GetList(). arrPorts:%v\n", arrPorts)
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/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 | //"bufio"
14 | "io/ioutil"
15 | "os/exec"
16 | )
17 |
18 | // execute system_profiler SPUSBDataType | grep "Vendor ID: 0x2341" -A5 -B2
19 | // maybe -B2 is not necessary
20 | // trim whitespace and eol
21 | // map everything with :
22 | // get [map][Location ID] first 5 chars
23 | // search all board.txt files for map[Product ID]
24 | // assign it to name
25 |
26 | func removeNonArduinoBoards(ports []OsSerialPort) []OsSerialPort {
27 | usbcmd := exec.Command("system_profiler", "SPUSBDataType")
28 | grepcmd := exec.Command("grep", "0x2341", "-A5", "-B1")
29 |
30 | cmdOutput, _ := pipe_commands(usbcmd, grepcmd)
31 |
32 | //log.Println(string(cmdOutput))
33 | cmdOutSlice := strings.Split(string(cmdOutput), "\n")
34 |
35 | var arduino_ports []OsSerialPort
36 | var other_ports []OsSerialPort
37 |
38 | // how many lines is the output? boards attached = lines/8
39 | for i := 0; i < len(cmdOutSlice)/8; i++ {
40 |
41 | cmdOutSliceN := cmdOutSlice[i*8 : (i+1)*8]
42 |
43 | cmdOutMap := make(map[string]string)
44 |
45 | for _, element := range cmdOutSliceN {
46 | if strings.Contains(element, "ID") {
47 | element = strings.TrimSpace(element)
48 | arr := strings.Split(element, ": ")
49 | cmdOutMap[arr[0]] = arr[1]
50 | }
51 | }
52 |
53 | archBoardName, boardName, _ := getBoardName(cmdOutMap["Product ID"])
54 |
55 | // remove initial 0x and final zeros
56 | ttyHeader := strings.Trim((cmdOutMap["Location ID"]), "0x")
57 | ttyHeader = strings.Split(ttyHeader, " ")[0]
58 | ttyHeader = strings.Trim(ttyHeader, "0")
59 |
60 | for _, port := range ports {
61 | if strings.Contains(port.Name, ttyHeader) {
62 | if !strings.Contains(port.Name, "/cu") {
63 | port.RelatedNames = append(port.RelatedNames, archBoardName)
64 | port.FriendlyName = strings.Trim(boardName, "\n")
65 | arduino_ports = append(arduino_ports, port)
66 | } else {
67 | other_ports = append(other_ports, port)
68 | }
69 | }
70 | }
71 | }
72 |
73 | arduino_ports = append(arduino_ports, other_ports...)
74 |
75 | // additional remove phase
76 | arduino_ports = Filter(arduino_ports, func(port OsSerialPort) bool {
77 | return !strings.Contains(port.Name, "Blue") && !strings.Contains(port.Name, "/cu")
78 | })
79 |
80 | return arduino_ports
81 | }
82 |
83 | func getList() ([]OsSerialPort, os.SyscallError) {
84 | //return getListViaWmiPnpEntity()
85 | return getListViaTtyList()
86 | }
87 |
88 | func getListViaTtyList() ([]OsSerialPort, os.SyscallError) {
89 | var err os.SyscallError
90 |
91 | log.Println("getting serial list on darwin")
92 |
93 | // make buffer of 100 max serial ports
94 | // return a slice
95 | list := make([]OsSerialPort, 100)
96 |
97 | files, _ := ioutil.ReadDir("/dev/")
98 | ctr := 0
99 | for _, f := range files {
100 | if strings.HasPrefix(f.Name(), "tty.") {
101 | // it is a legitimate serial port
102 | list[ctr].Name = "/dev/" + f.Name()
103 | list[ctr].FriendlyName = f.Name()
104 | log.Println("Added serial port to list: ", list[ctr])
105 | ctr++
106 | }
107 | // stop-gap in case going beyond 100 (which should never happen)
108 | // i mean, really, who has more than 100 serial ports?
109 | if ctr > 99 {
110 | ctr = 99
111 | }
112 | //fmt.Println(f.Name())
113 | //fmt.Println(f.)
114 | }
115 | /*
116 | list := make([]OsSerialPort, 3)
117 | list[0].Name = "tty.serial1"
118 | list[0].FriendlyName = "tty.serial1"
119 | list[1].Name = "tty.serial2"
120 | list[1].FriendlyName = "tty.serial2"
121 | list[2].Name = "tty.Bluetooth-Modem"
122 | list[2].FriendlyName = "tty.Bluetooth-Modem"
123 | */
124 |
125 | return list[0:ctr], err
126 | }
127 |
--------------------------------------------------------------------------------
/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 removeNonArduinoBoards(ports []OsSerialPort) []OsSerialPort {
24 | usbcmd := exec.Command("lsusb", "-vvv")
25 | grepcmd := exec.Command("grep", "0x2341", "-A1")
26 | grep2cmd := exec.Command("grep", "idProduct")
27 | //awkcmd := exec.Command("awk", "\'{print $2}\'")
28 | //awkcmd := exec.Command("grep", "-E", "-o", "'0x[[:alnum:]]{4}'")
29 |
30 | cmdOutput, _ := pipe_commands(usbcmd, grepcmd, grep2cmd)
31 |
32 | cmdOutSliceT := strings.Split(string(cmdOutput), "\n")
33 |
34 | re := regexp.MustCompile("0x[[:alnum:]]{4}")
35 |
36 | var cmdOutSlice []string
37 |
38 | for _, element := range cmdOutSliceT {
39 | cmdOutSlice = append(cmdOutSlice, re.FindString(element))
40 | }
41 |
42 | var arduino_ports, other_ports []OsSerialPort
43 |
44 | for _, element := range cmdOutSlice {
45 |
46 | if element == "" {
47 | break
48 | }
49 |
50 | archBoardName, boardName, _ := getBoardName(element)
51 |
52 | for _, port := range ports {
53 | ueventcmd := exec.Command("cat", "/sys/class/tty/"+filepath.Base(port.Name)+"/device/uevent")
54 | grep3cmd := exec.Command("grep", "PRODUCT=")
55 | cutcmd := exec.Command("cut", "-f2", "-d/")
56 |
57 | cmdOutput2, _ := pipe_commands(ueventcmd, grep3cmd, cutcmd)
58 | cmdOutput2S := string(cmdOutput2)
59 |
60 | if strings.Contains(element, strings.Trim(cmdOutput2S, "\n")) && cmdOutput2S != "" {
61 | port.RelatedNames = append(port.RelatedNames, archBoardName)
62 | port.FriendlyName = strings.Trim(boardName, "\n")
63 | arduino_ports = append(arduino_ports, port)
64 | } else {
65 | other_ports = append(other_ports, port)
66 | }
67 | }
68 | }
69 |
70 | arduino_ports = append(arduino_ports, other_ports...)
71 |
72 | return arduino_ports
73 | }
74 |
75 | func getList() ([]OsSerialPort, os.SyscallError) {
76 |
77 | //return getListViaTtyList()
78 | return getAllPortsViaManufacturer()
79 | }
80 |
81 | func getListViaTtyList() ([]OsSerialPort, os.SyscallError) {
82 | var err os.SyscallError
83 |
84 | //log.Println("getting serial list on darwin")
85 |
86 | // make buffer of 1000 max serial ports
87 | // return a slice
88 | list := make([]OsSerialPort, 1000)
89 |
90 | files, _ := ioutil.ReadDir("/dev/")
91 | ctr := 0
92 | for _, f := range files {
93 | if strings.HasPrefix(f.Name(), "tty") {
94 | // it is a legitimate serial port
95 | list[ctr].Name = "/dev/" + f.Name()
96 | list[ctr].FriendlyName = f.Name()
97 |
98 | // see if we can get a better friendly name
99 | //friendly, ferr := getMetaDataForPort(f.Name())
100 | //if ferr == nil {
101 | // list[ctr].FriendlyName = friendly
102 | //}
103 |
104 | //log.Println("Added serial port to list: ", list[ctr])
105 | ctr++
106 | }
107 | // stop-gap in case going beyond 1000 (which should never happen)
108 | // i mean, really, who has more than 1000 serial ports?
109 | if ctr > 999 {
110 | ctr = 999
111 | }
112 | //fmt.Println(f.Name())
113 | //fmt.Println(f.)
114 | }
115 | /*
116 | list := make([]OsSerialPort, 3)
117 | list[0].Name = "tty.serial1"
118 | list[0].FriendlyName = "tty.serial1"
119 | list[1].Name = "tty.serial2"
120 | list[1].FriendlyName = "tty.serial2"
121 | list[2].Name = "tty.Bluetooth-Modem"
122 | list[2].FriendlyName = "tty.Bluetooth-Modem"
123 | */
124 |
125 | return list[0:ctr], err
126 | }
127 |
128 | type deviceClass struct {
129 | BaseClass int
130 | Description string
131 | }
132 |
133 | func getDeviceClassList() {
134 | // TODO: take list from http://www.usb.org/developers/defined_class
135 | // and create mapping.
136 | }
137 |
138 | func getAllPortsViaManufacturer() ([]OsSerialPort, os.SyscallError) {
139 | var err os.SyscallError
140 | var list []OsSerialPort
141 |
142 | // LOOK FOR THE WORD MANUFACTURER
143 | // search /sys folder
144 | oscmd := exec.Command("find", "/sys/", "-name", "manufacturer", "-print") //, "2>", "/dev/null")
145 | // Stdout buffer
146 | cmdOutput := &bytes.Buffer{}
147 | // Attach buffer to command
148 | oscmd.Stdout = cmdOutput
149 |
150 | errstart := oscmd.Start()
151 | if errstart != nil {
152 | log.Printf("Got error running find cmd. Maybe they don't have it installed? %v:", errstart)
153 | return nil, err
154 | }
155 | //log.Printf("Waiting for command to finish... %v", oscmd)
156 |
157 | errwait := oscmd.Wait()
158 |
159 | if errwait != nil {
160 | log.Printf("Command finished with error: %v", errwait)
161 | return nil, err
162 | }
163 |
164 | //log.Printf("Finished without error. Good stuff. stdout:%v", string(cmdOutput.Bytes()))
165 |
166 | // analyze stdout
167 | // we should be able to split on newline to each file
168 | files := strings.Split(string(cmdOutput.Bytes()), "\n")
169 | /*if len(files) == 0 {
170 | return nil, err
171 | }*/
172 |
173 | // LOOK FOR THE WORD PRODUCT
174 | oscmd2 := exec.Command("find", "/sys/", "-name", "product", "-print") //, "2>", "/dev/null")
175 | cmdOutput2 := &bytes.Buffer{}
176 | oscmd2.Stdout = cmdOutput2
177 |
178 | oscmd2.Start()
179 | oscmd2.Wait()
180 |
181 | filesFromProduct := strings.Split(string(cmdOutput2.Bytes()), "\n")
182 |
183 | // append both arrays so we have one (then we'll have to de-dupe)
184 | files = append(files, filesFromProduct...)
185 |
186 | // Now get directories from each file
187 | re := regexp.MustCompile("/(manufacturer|product)$")
188 | var mapfile map[string]int
189 | mapfile = make(map[string]int)
190 | for _, element := range files {
191 | // make this directory be a key so it's unique. increment int so we know
192 | // for debug how many times this directory appeared
193 | mapfile[re.ReplaceAllString(element, "")]++
194 | }
195 |
196 | // sort the directory keys
197 | mapfilekeys := make([]string, len(mapfile))
198 | i := 0
199 | for key, _ := range mapfile {
200 | mapfilekeys[i] = key
201 | i++
202 | }
203 | sort.Strings(mapfilekeys)
204 |
205 | //reRemoveManuf, _ := regexp.Compile("/manufacturer$")
206 | reNewLine, _ := regexp.Compile("\n")
207 |
208 | // loop on unique directories
209 | for _, directory := range mapfilekeys {
210 |
211 | if len(directory) == 0 {
212 | continue
213 | }
214 |
215 | // for each manufacturer or product file, we need to read the val from the file
216 | // but more importantly find the tty ports for this directory
217 |
218 | // for example, for the TinyG v9 which creates 2 ports, the cmd:
219 | // find /sys/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/ -name tty[AU]* -print
220 | // will result in:
221 | /*
222 | /sys/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3:1.0/tty/ttyACM0
223 | /sys/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3:1.2/tty/ttyACM1
224 | */
225 |
226 | // figure out the directory
227 | //directory := reRemoveManuf.ReplaceAllString(element, "")
228 |
229 | // read the device class so we can remove stuff we don't want like hubs
230 | deviceClassBytes, errRead4 := ioutil.ReadFile(directory + "/bDeviceClass")
231 | deviceClass := ""
232 | if errRead4 != nil {
233 | // there must be a permission issue
234 | //log.Printf("Problem reading in serial number text file. Permissions maybe? err:%v", errRead3)
235 | //return nil, err
236 | }
237 | deviceClass = string(deviceClassBytes)
238 | deviceClass = reNewLine.ReplaceAllString(deviceClass, "")
239 |
240 | if deviceClass == "09" || deviceClass == "9" || deviceClass == "09h" {
241 | log.Printf("This is a hub, so skipping. %v", directory)
242 | continue
243 | }
244 |
245 | // read the manufacturer
246 | manufBytes, errRead := ioutil.ReadFile(directory + "/manufacturer")
247 | manuf := ""
248 | if errRead != nil {
249 | // the file could possibly just not exist, which is normal
250 | log.Printf("Problem reading in manufacturer text file. Permissions maybe? err:%v", errRead)
251 | //return nil, err
252 | //continue
253 | }
254 | manuf = string(manufBytes)
255 | manuf = reNewLine.ReplaceAllString(manuf, "")
256 |
257 | // read the product
258 | productBytes, errRead2 := ioutil.ReadFile(directory + "/product")
259 | product := ""
260 | if errRead2 != nil {
261 | // the file could possibly just not exist, which is normal
262 | //log.Printf("Problem reading in product text file. Permissions maybe? err:%v", errRead2)
263 | //return nil, err
264 | }
265 | product = string(productBytes)
266 | product = reNewLine.ReplaceAllString(product, "")
267 |
268 | // read the serial number
269 | serialNumBytes, errRead3 := ioutil.ReadFile(directory + "/serial")
270 | serialNum := ""
271 | if errRead3 != nil {
272 | // the file could possibly just not exist, which is normal
273 | //log.Printf("Problem reading in serial number text file. Permissions maybe? err:%v", errRead3)
274 | //return nil, err
275 | }
276 | serialNum = string(serialNumBytes)
277 | serialNum = reNewLine.ReplaceAllString(serialNum, "")
278 |
279 | // read idvendor
280 | idVendorBytes, _ := ioutil.ReadFile(directory + "/idVendor")
281 | idVendor := ""
282 | idVendor = reNewLine.ReplaceAllString(string(idVendorBytes), "")
283 |
284 | // read idProduct
285 | idProductBytes, _ := ioutil.ReadFile(directory + "/idProduct")
286 | idProduct := ""
287 | idProduct = reNewLine.ReplaceAllString(string(idProductBytes), "")
288 |
289 | log.Printf("%v : %v (%v) DevClass:%v", manuf, product, serialNum, deviceClass)
290 |
291 | // search folder that had manufacturer file in it
292 | log.Printf("\tDirectory searching: %v", directory)
293 |
294 | // -name tty[AU]* -print
295 | oscmd = exec.Command("find", directory, "-name", "tty[AU]*", "-print")
296 |
297 | // Stdout buffer
298 | cmdOutput = &bytes.Buffer{}
299 | // Attach buffer to command
300 | oscmd.Stdout = cmdOutput
301 |
302 | errstart = oscmd.Start()
303 | if errstart != nil {
304 | log.Printf("Got error running find cmd. Maybe they don't have it installed? %v:", errstart)
305 | //return nil, err
306 | continue
307 | }
308 | //log.Printf("Waiting for command to finish... %v", oscmd)
309 |
310 | errwait = oscmd.Wait()
311 |
312 | if errwait != nil {
313 | log.Printf("Command finished with error: %v", errwait)
314 | //return nil, err
315 | continue
316 | }
317 |
318 | //log.Printf("Finished searching manuf directory without error. Good stuff. stdout:%v", string(cmdOutput.Bytes()))
319 | //log.Printf(" \n")
320 |
321 | // we should be able to split on newline to each file
322 | filesTty := strings.Split(string(cmdOutput.Bytes()), "\n")
323 |
324 | // generate a unique list of tty ports below
325 | //var ttyPorts []string
326 | var m map[string]int
327 | m = make(map[string]int)
328 | for _, fileTty := range filesTty {
329 | if len(fileTty) == 0 {
330 | continue
331 | }
332 | log.Printf("\t%v", fileTty)
333 | ttyPort := regexp.MustCompile("^.*/").ReplaceAllString(fileTty, "")
334 | ttyPort = reNewLine.ReplaceAllString(ttyPort, "")
335 | m[ttyPort]++
336 | //ttyPorts = append(ttyPorts, ttyPort)
337 | }
338 | log.Printf("\tlist of ports on this. map:%v\n", m)
339 | log.Printf("\t.")
340 | //sort.Strings(ttyPorts)
341 |
342 | // create order array of ttyPorts so they're in order when
343 | // we send back via json. this makes for more human friendly reading
344 | // cuz anytime you do a hash map you can get out of order
345 | ttyPorts := []string{}
346 | for key, _ := range m {
347 | ttyPorts = append(ttyPorts, key)
348 | }
349 | sort.Strings(ttyPorts)
350 |
351 | // we now have a very nice list of ttyports for this device. many are just 1 port
352 | // however, for some advanced devices there are 2 or more ports associated and
353 | // we have this data correct now, so build out the final OsSerialPort list
354 | for _, key := range ttyPorts {
355 | listitem := OsSerialPort{
356 | Name: "/dev/" + key,
357 | FriendlyName: manuf, // + " " + product,
358 | SerialNumber: serialNum,
359 | DeviceClass: deviceClass,
360 | Manufacturer: manuf,
361 | Product: product,
362 | IdVendor: idVendor,
363 | IdProduct: idProduct,
364 | }
365 | if len(product) > 0 {
366 | listitem.FriendlyName += " " + product
367 | }
368 | listitem.FriendlyName += " (" + key + ")"
369 | listitem.FriendlyName = friendlyNameCleanup(listitem.FriendlyName)
370 |
371 | // append related tty ports
372 | for _, keyRelated := range ttyPorts {
373 | if key == keyRelated {
374 | continue
375 | }
376 | listitem.RelatedNames = append(listitem.RelatedNames, "/dev/"+keyRelated)
377 | }
378 | list = append(list, listitem)
379 | }
380 |
381 | }
382 |
383 | // sort ports by item.Name
384 | sort.Sort(ByName(list))
385 |
386 | log.Printf("Final port list: %v", list)
387 | return list, err
388 | }
389 |
390 | // ByAge implements sort.Interface for []Person based on
391 | // the Age field.
392 | type ByName []OsSerialPort
393 |
394 | func (a ByName) Len() int { return len(a) }
395 | func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
396 | func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
397 |
398 | func friendlyNameCleanup(fnin string) (fnout string) {
399 | // This is an industry intelligence method to just cleanup common names
400 | // out there so we don't get ugly friendly names back
401 | fnout = regexp.MustCompile("\\(www.arduino.cc\\)").ReplaceAllString(fnin, "")
402 | fnout = regexp.MustCompile("Arduino\\s+Arduino").ReplaceAllString(fnout, "Arduino")
403 | fnout = regexp.MustCompile("\\s+").ReplaceAllString(fnout, " ") // multi space to single space
404 | fnout = regexp.MustCompile("^\\s+|\\s+$").ReplaceAllString(fnout, "") // trim
405 | return fnout
406 | }
407 |
408 | func getMetaDataForPort(port string) (string, error) {
409 | // search the folder structure on linux for this port name
410 |
411 | // search /sys folder
412 | oscmd := exec.Command("find", "/sys/devices", "-name", port, "-print") //, "2>", "/dev/null")
413 |
414 | // Stdout buffer
415 | cmdOutput := &bytes.Buffer{}
416 | // Attach buffer to command
417 | oscmd.Stdout = cmdOutput
418 |
419 | err := oscmd.Start()
420 | if err != nil {
421 | log.Fatal(err)
422 | }
423 | log.Printf("Waiting for command to finish... %v", oscmd)
424 |
425 | err = oscmd.Wait()
426 |
427 | if err != nil {
428 | log.Printf("Command finished with error: %v", err)
429 | } else {
430 | log.Printf("Finished without error. Good stuff. stdout:%v", string(cmdOutput.Bytes()))
431 | // analyze stdin
432 |
433 | }
434 |
435 | return port + "coolio", nil
436 | }
437 |
438 | func getMetaDataForPortOld(port string) (string, error) {
439 | // search the folder structure on linux for this port name
440 |
441 | // search /sys folder
442 | oscmd := exec.Command("find", "/sys/devices", "-name", port, "-print") //, "2>", "/dev/null")
443 |
444 | // Stdout buffer
445 | cmdOutput := &bytes.Buffer{}
446 | // Attach buffer to command
447 | oscmd.Stdout = cmdOutput
448 |
449 | err := oscmd.Start()
450 | if err != nil {
451 | log.Fatal(err)
452 | }
453 | log.Printf("Waiting for command to finish... %v", oscmd)
454 |
455 | err = oscmd.Wait()
456 |
457 | if err != nil {
458 | log.Printf("Command finished with error: %v", err)
459 | } else {
460 | log.Printf("Finished without error. Good stuff. stdout:%v", string(cmdOutput.Bytes()))
461 | // analyze stdin
462 |
463 | }
464 |
465 | return port + "coolio", nil
466 | }
467 |
--------------------------------------------------------------------------------
/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/tarm/goserial"
9 | "github.com/johnlauer/goserial"
10 | "log"
11 | "os"
12 | "strings"
13 | //"encoding/binary"
14 | "strconv"
15 | "sync"
16 | //"syscall"
17 | )
18 |
19 | var (
20 | serialListWindowsWg sync.WaitGroup
21 | )
22 |
23 | func removeNonArduinoBoards(ports []OsSerialPort) []OsSerialPort {
24 | return ports
25 | }
26 |
27 | func getList() ([]OsSerialPort, os.SyscallError) {
28 | // use a queue to do this to avoid conflicts
29 | // we've been getting crashes when this getList is requested
30 | // too many times too fast. i think it's something to do with
31 | // the unsafe syscalls overwriting memory
32 |
33 | // this will only block if waitgroupctr > 0. so first time
34 | // in shouldn't block
35 | serialListWindowsWg.Wait()
36 |
37 | serialListWindowsWg.Add(1)
38 | arr, sysCallErr := getListViaWmiPnpEntity()
39 | serialListWindowsWg.Done()
40 | //arr = make([]OsSerialPort, 0)
41 |
42 | // see if array has any data, if not fallback to the traditional
43 | // com port list model
44 | if len(arr) == 0 {
45 | // assume it failed
46 | arr, sysCallErr = getListViaOpen()
47 | }
48 |
49 | // see if array has any data, if not fallback to looking at
50 | // the registry list
51 | /*
52 | arr = make([]OsSerialPort, 0)
53 | if len(arr) == 0 {
54 | // assume it failed
55 | arr, sysCallErr = getListViaRegistry()
56 | }
57 | */
58 |
59 | return arr, sysCallErr
60 | }
61 |
62 | func getListSynchronously() {
63 |
64 | }
65 |
66 | func getListViaWmiPnpEntity() ([]OsSerialPort, os.SyscallError) {
67 |
68 | //log.Println("Doing getListViaWmiPnpEntity()")
69 |
70 | // this method panics a lot and i'm not sure why, just catch
71 | // the panic and return empty list
72 | defer func() {
73 | if e := recover(); e != nil {
74 | // e is the interface{} typed-value we passed to panic()
75 | log.Println("Got panic: ", e) // Prints "Whoops: boom!"
76 | }
77 | }()
78 |
79 | var err os.SyscallError
80 |
81 | //var friendlyName string
82 |
83 | // init COM, oh yeah
84 | ole.CoInitialize(0)
85 | defer ole.CoUninitialize()
86 |
87 | unknown, _ := oleutil.CreateObject("WbemScripting.SWbemLocator")
88 | defer unknown.Release()
89 |
90 | wmi, _ := unknown.QueryInterface(ole.IID_IDispatch)
91 | defer wmi.Release()
92 |
93 | // service is a SWbemServices
94 | serviceRaw, _ := oleutil.CallMethod(wmi, "ConnectServer")
95 | service := serviceRaw.ToIDispatch()
96 | defer service.Release()
97 |
98 | // result is a SWBemObjectSet
99 | //pname := syscall.StringToUTF16("SELECT * FROM Win32_PnPEntity where Name like '%" + "COM35" + "%'")
100 | pname := "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0 and Name like '%(COM%'"
101 | //pname := "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0"
102 | resultRaw, err2 := oleutil.CallMethod(service, "ExecQuery", pname)
103 | //log.Println("Got result from oleutil.CallMethod")
104 | if err2 != nil {
105 | // we got back an error or empty list
106 | log.Printf("Got an error back from oleutil.CallMethod. err:%v", err2)
107 | return nil, err
108 | }
109 |
110 | result := resultRaw.ToIDispatch()
111 | defer result.Release()
112 |
113 | countVar, _ := oleutil.GetProperty(result, "Count")
114 | count := int(countVar.Val)
115 |
116 | list := make([]OsSerialPort, count)
117 |
118 | for i := 0; i < count; i++ {
119 | // item is a SWbemObject, but really a Win32_Process
120 | itemRaw, _ := oleutil.CallMethod(result, "ItemIndex", i)
121 | item := itemRaw.ToIDispatch()
122 | defer item.Release()
123 |
124 | asString, _ := oleutil.GetProperty(item, "Name")
125 |
126 | //log.Println(asString.ToString())
127 |
128 | // get the com port
129 | //if false {
130 | s := strings.Split(asString.ToString(), "(COM")[1]
131 | s = "COM" + s
132 | s = strings.Split(s, ")")[0]
133 | list[i].Name = s
134 | list[i].FriendlyName = asString.ToString()
135 | //}
136 | }
137 |
138 | /*
139 | for index, element := range list {
140 | log.Println("index ", index, " element ", element.Name+
141 | " friendly ", element.FriendlyName)
142 | }
143 | */
144 |
145 | return list, err
146 | }
147 |
148 | func getListViaOpen() ([]OsSerialPort, os.SyscallError) {
149 |
150 | log.Println("Doing getListViaOpen(). Will try to open COM1 to COM99.")
151 | var err os.SyscallError
152 | list := make([]OsSerialPort, 100)
153 | var igood int = 0
154 | for i := 0; i < 100; i++ {
155 | prtname := "COM" + strconv.Itoa(i)
156 | conf := &serial.Config{Name: prtname, Baud: 9600}
157 | sp, err := serial.OpenPort(conf)
158 | //log.Println("Just tried to open port", prtname)
159 | if err == nil {
160 | //log.Println("Able to open port", prtname)
161 | list[igood].Name = prtname
162 | sp.Close()
163 | list[igood].FriendlyName = prtname
164 | //list[igood].FriendlyName = getFriendlyName(prtname)
165 | igood++
166 | }
167 | }
168 | for index, element := range list[:igood] {
169 | log.Println("index ", index, " element ", element.Name+
170 | " friendly ", element.FriendlyName)
171 | }
172 | return list[:igood], err
173 | }
174 |
175 | /*
176 | func getListViaRegistry() ([]OsSerialPort, os.SyscallError) {
177 |
178 | log.Println("Doing getListViaRegistry()")
179 | var err os.SyscallError
180 | var root win.HKEY
181 | rootpath, _ := syscall.UTF16PtrFromString("HARDWARE\\DEVICEMAP\\SERIALCOMM")
182 | log.Println(win.RegOpenKeyEx(win.HKEY_LOCAL_MACHINE, rootpath, 0, win.KEY_READ, &root))
183 |
184 | var name_length uint32 = 72
185 | var key_type uint32
186 | var lpDataLength uint32 = 72
187 | var zero_uint uint32 = 0
188 | name := make([]uint16, 72)
189 | lpData := make([]byte, 72)
190 |
191 | var retcode int32
192 | retcode = 0
193 | for retcode == 0 {
194 | retcode = win.RegEnumValue(root, zero_uint, &name[0], &name_length, nil, &key_type, &lpData[0], &lpDataLength)
195 | log.Println("Retcode:", retcode)
196 | log.Println("syscall name: "+syscall.UTF16ToString(name[:name_length-2])+"---- name_length:", name_length)
197 | log.Println("syscall lpdata:"+string(lpData[:lpDataLength-2])+"--- lpDataLength:", lpDataLength)
198 | //log.Println()
199 | zero_uint++
200 | }
201 | win.RegCloseKey(root)
202 | win.RegOpenKeyEx(win.HKEY_LOCAL_MACHINE, rootpath, 0, win.KEY_READ, &root)
203 |
204 | list := make([]OsSerialPort, zero_uint)
205 | var i uint32 = 0
206 | for i = 0; i < zero_uint; i++ {
207 | win.RegEnumValue(root, i-1, &name[0], &name_length, nil, &key_type, &lpData[0], &lpDataLength)
208 | //name := string(lpData[:lpDataLength])
209 | //name = name[:strings.Index(name, '\0')]
210 | //nameb := []byte(strings.TrimSpace(string(lpData[:lpDataLength])))
211 | //list[i].Name = string(nameb)
212 | //list[i].Name = string(name[:strings.Index(name, "\0")])
213 | //list[i].Name = fmt.Sprintf("%s", string(lpData[:lpDataLength-1]))
214 | pname := make([]uint16, (lpDataLength-2)/2)
215 | pname = convertByteArrayToUint16Array(lpData[:lpDataLength-2], lpDataLength-2)
216 | list[i].Name = syscall.UTF16ToString(pname)
217 | log.Println("The length of the name is:", len(list[i].Name))
218 | log.Println("list[i].Name=" + list[i].Name + "---")
219 | //list[i].FriendlyName = getFriendlyName(list[i].Name)
220 | list[i].FriendlyName = getFriendlyName("COM34")
221 | }
222 | win.RegCloseKey(root)
223 | return list, err
224 | }
225 | */
226 |
227 | func convertByteArrayToUint16Array(b []byte, mylen uint32) []uint16 {
228 |
229 | log.Println("converting. len:", mylen)
230 | var i uint32
231 | ret := make([]uint16, mylen/2)
232 | for i = 0; i < mylen; i += 2 {
233 | //ret[i/2] = binary.LittleEndian.Uint16(b[i : i+1])
234 | ret[i/2] = uint16(b[i]) | uint16(b[i+1])<<8
235 | }
236 | return ret
237 | }
238 |
239 | func getFriendlyName(portname string) string {
240 |
241 | // this method panics a lot and i'm not sure why, just catch
242 | // the panic and return empty list
243 | defer func() {
244 | if e := recover(); e != nil {
245 | // e is the interface{} typed-value we passed to panic()
246 | log.Println("Got panic: ", e) // Prints "Whoops: boom!"
247 | }
248 | }()
249 |
250 | var friendlyName string
251 |
252 | // init COM, oh yeah
253 | ole.CoInitialize(0)
254 | defer ole.CoUninitialize()
255 |
256 | unknown, _ := oleutil.CreateObject("WbemScripting.SWbemLocator")
257 | defer unknown.Release()
258 |
259 | wmi, _ := unknown.QueryInterface(ole.IID_IDispatch)
260 | defer wmi.Release()
261 |
262 | // service is a SWbemServices
263 | serviceRaw, _ := oleutil.CallMethod(wmi, "ConnectServer")
264 | service := serviceRaw.ToIDispatch()
265 | defer service.Release()
266 |
267 | // result is a SWBemObjectSet
268 | //pname := syscall.StringToUTF16("SELECT * FROM Win32_PnPEntity where Name like '%" + "COM35" + "%'")
269 | pname := "SELECT * FROM Win32_PnPEntity where Name like '%" + portname + "%'"
270 | resultRaw, _ := oleutil.CallMethod(service, "ExecQuery", pname)
271 | result := resultRaw.ToIDispatch()
272 | defer result.Release()
273 |
274 | countVar, _ := oleutil.GetProperty(result, "Count")
275 | count := int(countVar.Val)
276 |
277 | for i := 0; i < count; i++ {
278 | // item is a SWbemObject, but really a Win32_Process
279 | itemRaw, _ := oleutil.CallMethod(result, "ItemIndex", i)
280 | item := itemRaw.ToIDispatch()
281 | defer item.Release()
282 |
283 | asString, _ := oleutil.GetProperty(item, "Name")
284 |
285 | println(asString.ToString())
286 | friendlyName = asString.ToString()
287 | }
288 |
289 | return friendlyName
290 | }
291 |
--------------------------------------------------------------------------------
/serialport.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | //"github.com/johnlauer/goserial"
7 | "github.com/facchinm/go-serial"
8 | "io"
9 | "log"
10 | "strconv"
11 | "time"
12 | )
13 |
14 | type SerialConfig struct {
15 | Name string
16 | Baud int
17 |
18 | // Size int // 0 get translated to 8
19 | // Parity SomeNewTypeToGetCorrectDefaultOf_None
20 | // StopBits SomeNewTypeToGetCorrectDefaultOf_1
21 |
22 | // RTSFlowControl bool
23 | // DTRFlowControl bool
24 | // XONFlowControl bool
25 |
26 | // CRLFTranslate bool
27 | // TimeoutStuff int
28 | RtsOn bool
29 | DtrOn bool
30 | }
31 |
32 | type serport struct {
33 | // The serial port connection.
34 | portConf *SerialConfig
35 | portIo io.ReadWriteCloser
36 |
37 | done chan bool // signals the end of this request
38 |
39 | // Keep track of whether we're being actively closed
40 | // just so we don't show scary error messages
41 | isClosing bool
42 |
43 | // counter incremented on queue, decremented on write
44 | itemsInBuffer int
45 |
46 | // buffered channel containing up to 25600 outbound messages.
47 | sendBuffered chan Cmd
48 |
49 | // unbuffered channel of outbound messages that bypass internal serial port buffer
50 | sendNoBuf chan Cmd
51 |
52 | // Do we have an extra channel/thread to watch our buffer?
53 | BufferType string
54 | //bufferwatcher *BufferflowDummypause
55 | bufferwatcher Bufferflow
56 |
57 | // Keep track of whether this is the primary serial port, i.e. cnc controller
58 | // or if its secondary, i.e. a backup port or arduino or something tertiary
59 | IsPrimary bool
60 | IsSecondary bool
61 | }
62 |
63 | type Cmd struct {
64 | data string
65 | id string
66 | skippedBuffer bool
67 | willHandleCompleteResponse bool
68 | }
69 |
70 | type CmdComplete struct {
71 | Cmd string
72 | Id string
73 | P string
74 | BufSize int `json:"-"`
75 | D string `json:"-"`
76 | }
77 |
78 | type qwReport struct {
79 | Cmd string
80 | QCnt int
81 | Id string
82 | D string `json:"-"`
83 | Buf string `json:"-"`
84 | P string
85 | }
86 |
87 | type SpPortMessage struct {
88 | P string // the port, i.e. com22
89 | D string // the data, i.e. G0 X0 Y0
90 | }
91 |
92 | func (p *serport) reader() {
93 |
94 | //var buf bytes.Buffer
95 | ch := make([]byte, 1024)
96 | timeCheckOpen := time.Now()
97 |
98 | for {
99 |
100 | n, err := p.portIo.Read(ch)
101 |
102 | //if we detect that port is closing, break out o this for{} loop.
103 | if p.isClosing {
104 | strmsg := "Shutting down reader on " + p.portConf.Name
105 | log.Println(strmsg)
106 | h.broadcastSys <- []byte(strmsg)
107 | break
108 | }
109 |
110 | // read can return legitimate bytes as well as an error
111 | // so process the bytes if n > 0
112 | if n > 0 {
113 | //log.Print("Read " + strconv.Itoa(n) + " bytes ch: " + string(ch))
114 | data := string(ch[:n])
115 | //log.Print("The data i will convert to json is:")
116 | //log.Print(data)
117 |
118 | // give the data to our bufferflow so it can do it's work
119 | // to read/translate the data to see if it wants to block
120 | // writes to the serialport. each bufferflow type will decide
121 | // this on its own based on its logic, i.e. tinyg vs grbl vs others
122 | //p.b.bufferwatcher..OnIncomingData(data)
123 | p.bufferwatcher.OnIncomingData(data)
124 |
125 | // see if the OnIncomingData handled the broadcast back
126 | // to the user. this option was added in case the OnIncomingData wanted
127 | // to do something fancier or implementation specific, i.e. TinyG Buffer
128 | // actually sends back data on a perline basis rather than our method
129 | // where we just send the moment we get it. the reason for this is that
130 | // the browser was sometimes getting back packets out of order which
131 | // of course would screw things up when parsing
132 |
133 | if p.bufferwatcher.IsBufferGloballySendingBackIncomingData() == false {
134 | //m := SpPortMessage{"Alice", "Hello"}
135 | m := SpPortMessage{p.portConf.Name, data}
136 | //log.Print("The m obj struct is:")
137 | //log.Print(m)
138 |
139 | //b, err := json.MarshalIndent(m, "", "\t")
140 | b, err := json.Marshal(m)
141 | if err != nil {
142 | log.Println(err)
143 | h.broadcastSys <- []byte("Error creating json on " + p.portConf.Name + " " +
144 | err.Error() + " The data we were trying to convert is: " + string(ch[:n]))
145 | break
146 | }
147 | //log.Print("Printing out json byte data...")
148 | //log.Print(string(b))
149 | h.broadcastSys <- b
150 | //h.broadcastSys <- []byte("{ \"p\" : \"" + p.portConf.Name + "\", \"d\": \"" + string(ch[:n]) + "\" }\n")
151 | }
152 | }
153 |
154 | // double check that we got characters in the buffer
155 | // before deciding if an EOF is legitimately a reason
156 | // to close the port because we're seeing that on some
157 | // os's like Linux/Ubuntu you get an EOF when you open
158 | // the port. Perhaps the EOF was buffered from a previous
159 | // close and the OS doesn't clear out that buffer on a new
160 | // connect. This means we'll only catch EOF's when there are
161 | // other characters with it, but that seems to work ok
162 | if n <= 0 {
163 | if err == io.EOF || err == io.ErrUnexpectedEOF {
164 | // hit end of file
165 | log.Println("Hit end of file on serial port")
166 | h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got EOF (End of File) on port which usually means another app other than Serial Port JSON Server is locking your port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")
167 |
168 | }
169 |
170 | if err != nil {
171 | log.Println(err)
172 | h.broadcastSys <- []byte("Error reading on " + p.portConf.Name + " " +
173 | err.Error() + " Closing port.")
174 | h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Got error reading on port. " + err.Error() + "\",\"Port\":\"" + p.portConf.Name + "\",\"Baud\":" + strconv.Itoa(p.portConf.Baud) + "}")
175 | break
176 | }
177 |
178 | // Keep track of time difference between two consecutive read with n == 0 and err == nil
179 | // we get here if the port has been disconnected while open (cpu usage will jump to 100%)
180 | // let's close the port only if the events are extremely fast (<1ms)
181 | if err == nil {
182 | diff := time.Since(timeCheckOpen)
183 | if diff.Nanoseconds() < 1000000 {
184 | p.isClosing = true
185 | }
186 | timeCheckOpen = time.Now()
187 | }
188 | }
189 | }
190 | p.portIo.Close()
191 | }
192 |
193 | // this method runs as its own thread because it's instantiated
194 | // as a "go" method. so if it blocks inside, it is ok
195 | func (p *serport) writerBuffered() {
196 |
197 | // this method can panic if user closes serial port and something is
198 | // in BlockUntilReady() and then a send occurs on p.sendNoBuf
199 |
200 | defer func() {
201 | if e := recover(); e != nil {
202 | // e is the interface{} typed-value we passed to panic()
203 | log.Println("Got panic: ", e) // Prints "Whoops: boom!"
204 | }
205 | }()
206 |
207 | // this for loop blocks on p.sendBuffered until that channel
208 | // sees something come in
209 | for data := range p.sendBuffered {
210 |
211 | //log.Printf("Got p.sendBuffered. data:%v, id:%v\n", string(data.data), string(data.id))
212 |
213 | // we want to block here if we are being asked
214 | // to pause.
215 | goodToGo, willHandleCompleteResponse := p.bufferwatcher.BlockUntilReady(string(data.data), data.id)
216 |
217 | if goodToGo == false {
218 | log.Println("We got back from BlockUntilReady() but apparently we must cancel this cmd")
219 | // since we won't get a buffer decrement in p.sendNoBuf, we must do it here
220 | p.itemsInBuffer--
221 | } else {
222 | // send to the non-buffered serial port writer
223 | //log.Println("About to send to p.sendNoBuf channel")
224 | data.willHandleCompleteResponse = willHandleCompleteResponse
225 | p.sendNoBuf <- data
226 | }
227 | }
228 | msgstr := "writerBuffered just got closed. make sure you make a new one. port:" + p.portConf.Name
229 | log.Println(msgstr)
230 | h.broadcastSys <- []byte(msgstr)
231 | }
232 |
233 | // this method runs as its own thread because it's instantiated
234 | // as a "go" method. so if it blocks inside, it is ok
235 | func (p *serport) writerNoBuf() {
236 | // this for loop blocks on p.send until that channel
237 | // sees something come in
238 | for data := range p.sendNoBuf {
239 |
240 | //log.Printf("Got p.sendNoBuf. data:%v, id:%v\n", string(data.data), string(data.id))
241 |
242 | // if we get here, we were able to write successfully
243 | // to the serial port because it blocks until it can write
244 |
245 | // decrement counter
246 | p.itemsInBuffer--
247 | log.Printf("itemsInBuffer:%v\n", p.itemsInBuffer)
248 | //h.broadcastSys <- []byte("{\"Cmd\":\"Write\",\"QCnt\":" + strconv.Itoa(p.itemsInBuffer) + ",\"Byte\":" + strconv.Itoa(n2) + ",\"Port\":\"" + p.portConf.Name + "\"}")
249 |
250 | // For reducing load on websocket, stop transmitting write data
251 | buf := "Buf"
252 | if data.skippedBuffer {
253 | buf = "NoBuf"
254 | }
255 | qwr := qwReport{
256 | Cmd: "Write",
257 | QCnt: p.itemsInBuffer,
258 | Id: string(data.id),
259 | D: string(data.data),
260 | Buf: buf,
261 | P: p.portConf.Name,
262 | }
263 | qwrJson, _ := json.Marshal(qwr)
264 | h.broadcastSys <- qwrJson
265 |
266 | // FINALLY, OF ALL THE CODE IN THIS PROJECT
267 | // WE TRULY/FINALLY GET TO WRITE TO THE SERIAL PORT!
268 | n2, err := p.portIo.Write([]byte(data.data))
269 |
270 | // see if we need to send back the completeResponse
271 | if data.willHandleCompleteResponse == false {
272 | // we need to send back complete response
273 | // Send fake cmd:"Complete" back
274 | //strCmd := data.data
275 | m := CmdComplete{"CompleteFake", data.id, p.portConf.Name, -1, data.data}
276 | msgJson, err := json.Marshal(m)
277 | if err == nil {
278 | h.broadcastSys <- msgJson
279 | }
280 |
281 | }
282 |
283 | log.Print("Just wrote ", n2, " bytes to serial: ", string(data.data))
284 | //log.Print(n2)
285 | //log.Print(" bytes to serial: ")
286 | //log.Print(data)
287 | if err != nil {
288 | errstr := "Error writing to " + p.portConf.Name + " " + err.Error() + " Closing port."
289 | log.Print(errstr)
290 | h.broadcastSys <- []byte(errstr)
291 | break
292 | }
293 | }
294 | msgstr := "Shutting down writer on " + p.portConf.Name
295 | log.Println(msgstr)
296 | h.broadcastSys <- []byte(msgstr)
297 | p.portIo.Close()
298 | }
299 |
300 | func spHandlerOpen(portname string, baud int, buftype string, isSecondary bool) {
301 |
302 | log.Print("Inside spHandler")
303 |
304 | var out bytes.Buffer
305 |
306 | out.WriteString("Opening serial port ")
307 | out.WriteString(portname)
308 | out.WriteString(" at ")
309 | out.WriteString(strconv.Itoa(baud))
310 | out.WriteString(" baud")
311 | log.Print(out.String())
312 |
313 | //h.broadcast <- []byte("Opened a serial port ")
314 | //h.broadcastSys <- out.Bytes()
315 |
316 | isPrimary := true
317 | if isSecondary {
318 | isPrimary = false
319 | }
320 |
321 | conf := &SerialConfig{Name: portname, Baud: baud, RtsOn: true}
322 |
323 | mode := &serial.Mode{
324 | BaudRate: baud,
325 | Vmin: 0,
326 | Vtimeout: 10,
327 | }
328 |
329 | sp, err := serial.OpenPort(portname, mode)
330 | log.Print("Just tried to open port")
331 | if err != nil {
332 | //log.Fatal(err)
333 | log.Print("Error opening port " + err.Error())
334 | //h.broadcastSys <- []byte("Error opening port. " + err.Error())
335 | h.broadcastSys <- []byte("{\"Cmd\":\"OpenFail\",\"Desc\":\"Error opening port. " + err.Error() + "\",\"Port\":\"" + conf.Name + "\",\"Baud\":" + strconv.Itoa(conf.Baud) + "}")
336 |
337 | return
338 | }
339 | log.Print("Opened port successfully")
340 | //p := &serport{send: make(chan []byte, 256), portConf: conf, portIo: sp}
341 | // we can go up to 256,000 lines of gcode in the buffer
342 | p := &serport{sendBuffered: make(chan Cmd, 256000), sendNoBuf: make(chan Cmd), portConf: conf, portIo: sp, BufferType: buftype, IsPrimary: isPrimary, IsSecondary: isSecondary}
343 |
344 | // if user asked for a buffer watcher, i.e. tinyg/grbl then attach here
345 | if buftype == "tinyg" {
346 |
347 | bw := &BufferflowTinyg{Name: "tinyg", parent_serport: p}
348 | bw.Init()
349 | bw.Port = portname
350 | p.bufferwatcher = bw
351 | } else if buftype == "dummypause" {
352 |
353 | // this is a dummy pause type bufferflow object
354 | // to test artificially a delay on the serial port write
355 | // it just pauses 3 seconds on each serial port write
356 | bw := &BufferflowDummypause{}
357 | bw.Init()
358 | bw.Port = portname
359 | p.bufferwatcher = bw
360 | } else if buftype == "grbl" {
361 | // grbl bufferflow
362 | // store port as parent_serport for use in intializing a status query loop for '?'
363 | bw := &BufferflowGrbl{Name: "grbl", parent_serport: p}
364 | bw.Init()
365 | bw.Port = portname
366 | p.bufferwatcher = bw
367 | } else {
368 | bw := &BufferflowDefault{}
369 | bw.Init()
370 | bw.Port = portname
371 | p.bufferwatcher = bw
372 | }
373 |
374 | sh.register <- p
375 | defer func() { sh.unregister <- p }()
376 | // this is internally buffered thread to not send to serial port if blocked
377 | go p.writerBuffered()
378 | // this is thread to send to serial port regardless of block
379 | go p.writerNoBuf()
380 | p.reader()
381 | //go p.reader()
382 | //p.done = make(chan bool)
383 | //<-p.done
384 | }
385 |
386 | func spHandlerCloseExperimental(p *serport) {
387 | h.broadcastSys <- []byte("Pre-closing serial port " + p.portConf.Name)
388 | p.isClosing = true
389 | //close the port
390 |
391 | p.bufferwatcher.Close()
392 | p.portIo.Close()
393 | h.broadcastSys <- []byte("Bufferwatcher closed")
394 | p.portIo.Close()
395 | //elicit response from hardware to close out p.reader()
396 | //_, _ = p.portIo.Write([]byte("?"))
397 | //p.portIo.Read(nil)
398 |
399 | //close(p.portIo)
400 | h.broadcastSys <- []byte("portIo closed")
401 | close(p.sendBuffered)
402 | h.broadcastSys <- []byte("p.sendBuffered closed")
403 | close(p.sendNoBuf)
404 | h.broadcastSys <- []byte("p.sendNoBuf closed")
405 |
406 | //p.done <- true
407 |
408 | // unregister myself
409 | // we already have a deferred unregister in place from when
410 | // we opened. the only thing holding up that thread is the p.reader()
411 | // so if we close the reader we should get an exit
412 | h.broadcastSys <- []byte("Closing serial port " + p.portConf.Name)
413 | }
414 |
415 | func spHandlerClose(p *serport) {
416 | p.isClosing = true
417 | //close the port
418 | //elicit response from hardware to close out p.reader()
419 | _, _ = p.portIo.Write([]byte("?"))
420 |
421 | p.bufferwatcher.Close()
422 | p.portIo.Close()
423 | // unregister myself
424 | // we already have a deferred unregister in place from when
425 | // we opened. the only thing holding up that thread is the p.reader()
426 | // so if we close the reader we should get an exit
427 | h.broadcastSys <- []byte("Closing serial port " + p.portConf.Name)
428 | }
429 |
--------------------------------------------------------------------------------
/snapshot/README.md:
--------------------------------------------------------------------------------
1 | serial-port-json-server
2 | =======================
3 | Version 1.75
4 |
5 | A serial port JSON websocket & web server that runs from the command line on Windows, Mac, Linux, Raspberry Pi, or Beagle Bone that lets you communicate with your serial port from a web application. This enables web apps to be written that can communicate with your local serial device such as an Arduino, CNC controller, or any device that communicates over the serial port.
6 |
7 | The app is written in Go. It has an embedded web server and websocket server. The server runs on the standard port of localhost:8989. You can connect to it locally with your browser to interact by visiting http://localhost:8989. The websocket is technically running at ws://localhost/ws. You can of course connect to your websocket from any other computer to bind in remotely. For example, just connect to ws://192.168.1.10/ws if you are on a remote host where 192.168.1.10 is your devices actual IP address.
8 |
9 | The app is one executable with everything you need and is available ready-to-go for every major platform. It is a multi-threaded app that uses all of the cool techniques available in Go including extensive use of channels (threads) to create a super-responsive app.
10 |
11 | If you are a web developer and want to write a web application that connects to somebody's local or remote serial port server, then you simply need to create a websocket connection to the localhost or remote host and you will be directly interacting with that user's serial port.
12 |
13 | For example, if you wanted to create a Gcode Sender web app to enable people to send 3D print or milling commands from your site, this would be a perfect use case. Or if you've created an oscilloscope web app that connects to an Arduino, it would be another great use case. Finally you can write web apps that interact with a user's local hardware.
14 |
15 | Thanks go to gary.burd.info for the websocket example in Go. Thanks also go to tarm/goserial for the serial port base implementation. Thanks go to Jarret Luft at well for building the Grbl buffer and helping on global code changes to make everything better.
16 |
17 | Example Use Case
18 | ---------
19 | Here is a screenshot of the Serial Port JSON Server being used inside the ChiliPeppr Serial Port web console app.
20 | http://chilipeppr.com/serialport
21 |
22 |
23 | This is the Serial Port JSON Server being used inside the TinyG workspace in ChiliPeppr.
24 | http://chilipeppr.com/tinyg
25 |
26 |
27 | There is also a JSFiddle you can fork to create your own interface to the Serial Port JSON Server for your own project.
28 | http://jsfiddle.net/chilipeppr/vetj5fvx/
29 |
30 |
31 |
32 | Running
33 | ---------
34 | From the command line issue the following command:
35 | - Mac/Linux
36 | `./serial-port-json-server`
37 | - Windows
38 | `serial-port-json-server.exe`
39 |
40 | Verbose logging mode:
41 | - Mac/Linux
42 | `./serial-port-json-server -v`
43 | - Windows
44 | `serial-port-json-server.exe -v`
45 |
46 | Running on alternate port:
47 | - Mac/Linux
48 | `./serial-port-json-server -addr :8000`
49 | - Windows
50 | `serial-port-json-server.exe -addr :8000`
51 |
52 | Here's a screenshot of a successful run on Windows x64. Make sure you allow the firewall to give access to Serial Port JSON Server or you'll wonder why it's not working.
53 |
54 |
55 |
56 | How to Build
57 | ---------
58 | Video tutorial of building SPJS on a Mac: https://www.youtube.com/watch?v=4Hou06bOuHc
59 |
60 | 1. Install Go (http://golang.org/doc/install)
61 | 2. If you're on a Mac, install Xcode from the Apple Store because you'll need gcc to compile the native code for a Mac. If you're on Windows, Linux, Raspberry Pi, or Beagle Bone you are all set.
62 | 3. Get go into your path so you can run "go" from any directory:
63 | On Linux, Mac, Raspberry Pi, Beagle Bone Black
64 | export PATH=$PATH:/usr/local/go/bin
65 | On Windows, use the Environment Variables dialog by right-click My Computer
66 | 4. Define your GOPATH variable and create the folder to match. This is your personal working folder for all yourGo code. This is important because you will be retrieving several projects from Github and Go needs to know where to download all the files and where to build the directory structure. On my Windows computer I created a folder called C:\Users\John\go and set GOPATH=C:\Users\John\go
67 | On Mac
68 | export GOPATH=/Users/john/go
69 | On Linux, Raspberry Pi, Beagle Bone Black, Intel Edison
70 | export GOPATH=/home/john/go
71 | On Windows, use the Environment Variables dialog by right-click My Computer to create GOPATH
72 | 5. Change directory into your GOPATH
73 | 6. Type "go get github.com/johnlauer/serial-port-json-server". This will retrieve this Github project and all dependent projects. It takes some time to run this.
74 | 7. Then change direcory into github.com\johnlauer\serial-port-json-server.
75 | 8. Type "go build" when you're inside that directory and it will create a binary called serial-port-json-server
76 | 9. Run it by typing ./serial-port-json-server or on Windows run serial-port-json-server.exe
77 | 10. If you have a firewall on the computer running the serial-port-json-server you must allow port 8989 in the firewall.
78 |
79 | Supported Commands
80 | -------
81 |
82 | Command | Example | Description
83 | ------- | ------- | -------
84 | list | | Lists all available serial ports on your device
85 | open portName baudRate [bufferAlgorithm] | open /dev/ttyACM0 115200 tinyg | Opens a serial port. The comPort should be the Name of the port inside the list response such as COM2 or /dev/ttyACM0. The baudrate should be a rate from the baudrates command or a typical baudrate such as 9600 or 115200. A bufferAlgorithm can be optionally specified such as "tinyg" (or in the future "grbl" if somebody writes it) or write your own.
86 | sendjson {} | {"P":"COM22","Data":[{"D":"!~\n","Id":"234"},{"D":"{\"sr\":\"\"}\n","Id":"235"}]} | See Wiki page at https://github.com/johnlauer/serial-port-json-server/wiki
87 | send portName data | send /dev/ttyACM0 G1 X10.5 Y2 F100\n | Send your data to the serial port. Remember to send a newline in your data if your serial port expects it.
88 | sendnobuf portName data | send COM22 {"qv":0}\n | Send your data and bypass the bufferFlowAlgorithm if you specified one.
89 | close portName | close COM1 | Close out your serial port
90 | bufferalgorithms | | List the available bufferAlgorithms on the server. You will get a list such as "default, tinyg"
91 | baudrates | | List common baudrates such as 2400, 9600, 115200
92 |
93 | FAQ
94 | -------
95 | - Q: There are several Node.js serial port servers. Why not write this in Node.js instead of Go?
96 |
97 | - A: Because Go is a better solution for several reasons.
98 | - Easier to install on your computer. Just download and run binary. (Node requires big install)
99 | - It is multi-threaded which is key for a serial port websocket server (Node is single-threaded)
100 | - It has a tiny memory footprint using about 3MB of RAM
101 | - It is one clean compiled executable with no dependencies
102 | - It makes very efficient use of RAM with amazing garbage collection
103 | - It is super fast when running
104 | - It launches super quick
105 | - It is essentially C code without the pain of C code. Go has insanely amazing threading support called Channels. Node.js is single-threaded, so you can't take full advantage of the CPU's threading capabilities. Go lets you do this easily. A serial port server needs several threads. 1) Websocket thread for each connection. 2) Serial port thread for each serial device. Serial Port JSON Server allows you to bind as many serial port devices in parallel as you want. 3) A writer and reader thread for each serial port. 4) A buffering thread for each incoming message from the browser into the websocket 5) A buffering thread for messages back out from the server to the websocket to the browser. To achieve this in Node requires lots of callbacks. You also end up talking natively anyway to the serial port on each specific platform you're on, so you have to deal with the native code glued to Node.
106 |
107 | Revisions
108 | -------
109 | Changes in 1.75
110 | - Tweaked the order of operations for pausing/unpausing the buffer in Grbl and TinyG to account for rare cases where a deadlock could occur. This should guarantee no dead-locking.
111 | - Jarret Luft added an artificial % buffer wipe to Grbl buffer to mimic to some degree the buffer wiping available on TinyG.
112 |
113 | Changes in 1.7
114 | - sendjson now supported. Will give back onQueue, onWrite, onComplete
115 | - Moved TinyG buffer to serial byte counting.
116 |
117 | Changes in 1.6
118 | - Logging is now off by default so Raspberry Pi runs cleaner. The immense amount of logging was dragging the Raspi down. Should help on BeagleBone Black as well. Makes SPJS run more efficient on powerful systems too like Windows, Mac, and Linux. You can turn on logging by issuing a -v on the command line. This fix by Jarret Luft.
119 | - Added EOF extra checking for Linux serial ports that seem to return an EOF on a new connect and thus the port was prematurely closing. Thanks to Yiannis Mandravellos for finding the bug and fixing it.
120 | - Added a really nice Grbl bufferAlgorithm which was written by Jarret Luft who is the creator of the Grbl workspace in ChiliPeppr.
121 | - The buffer counts each line of gcode being sent to Grbl up to 127 bytes and then doesn't send anymore data to Grbl until it sees an OK or ERROR response from Grbl indicating the command was processed. For each OK|ERROR the buffer decrements the counter to see how much more room is avaialble. If the next Gcode command can fit it is sent immediately in.
122 | - This new Grbl buffer should mirror the stream.py example code from Sonny Jeon who maintains Grbl. This Serial Port JSON Server should now be able to execute the commands faster than anything out there since it's written in Go (which is C) and is compiled and super-fast.
123 | - Position requests occur inside this buffer where a ? is sent every 250ms to Grbl such that you should see a position just come back on demand non-stop from Grbl. It could be possible in a future version to only queue these position reports up during actual Gcode commands being sent so that when idle there are not a ton of position updates being sent back that aren't necessary.
124 | - Soft resets (Ctrl-x) now wipe the buffer.
125 | - !~? will skip ahead of all other commands now. This is important for jogging or using ! as a quick stop of your controller since you can have 25,000 lines of gcode queued to SPJS now and of course you would want these commands to skip in front of that queue.
126 | - Feedhold pauses the buffer inside SPJS now.
127 | - Cycle resume ~ unpauses the buffer inside SPJS now.
128 | - When using this buffer data is sent back in a per line mode rather than as characters are received so there is more efficiency on the websocket.
129 | - Checks for the grbl init line indicating the arduino is ready to accept commands
130 |
131 | Changes in 1.5
132 | - For TinyG buffer, moved to slot counter approach. The buffer planner approach was causing G2/G3 commands to overflow the buffer because the round-trip time was too off with reading QR responses. So, moved to a 4 slot buffer approach. Jogging is still a bit rough in this approach, but that can get tweaked. The new slot approach is more like counting serial buffer queue items. SPJS sends up to 4 commands and then waits for a r:{} json response. It has intelligence to know if certain commands won't get a response like !~% or newlines, so it doesn't look for slot responses and just blindly sends. The only danger is if there are 4 really long lines of Gcode that surpass the 254 bytes in the serial buffer then we could overflow. Could add trapping for that.
133 |
134 | Changes in 1.4
135 | - Added reporting on Queuing so you know what the state of the Serial Port JSON Server Queue is doing. The reason for this is to ensure your serial port commands don't get out of order you will want to make sure you write to the websocket and then wait for the {"Cmd":"Queued"} response. Then write your next command. This is necessary because when sending different frames across a websocket over the Internet, you can get packet retransmissions, and although you'll never lose your data, your serial commands could arrive at the server out of order. By watching that your command is queued, you are safe to send the next command. However, this can also slow things down, so now you can simply gang up multiple commands into one send and the Serial Port JSON Server will split them into separate sub-commands and tell you that it did in the queue and write reports.
136 | - For example, a typical queue report looks like {"Cmd":"Queued","QCnt":61,"Type":["Buf"],"D":["{\"sr\":\"\"}\n"],"Port":"COM22"}.
137 | - If you send something like: send COM22 {"sr":""}\n{"qr":""}\n{"sr":""}\n{"qr":""}\n. You will get back a queue report like {"Cmd":"Queued","QCnt":4,"Type":["Buf","Buf","Buf","Buf"],"D":["{\"sr\":\"\"}\n","{\"qr\":\"\"}\n","{\"sr\":\"\"}\n","{\"qr\":\"\"}\n"],"Port":"COM22"}
138 | - When two queue items are written to the serial port you will get back something like {"Cmd":"Write","QCnt":1,"D":"{\"qr\":\"\"}\n","Port":"COM22"}{"Cmd":"Write","QCnt":0,"D":"{\"sr\":\"\"}\n","Port":"COM22"}
139 | - Fixed analysis of incoming serial data due to some serial ports sending fragmented data.
140 | - Added bufferalgorithms and baudrates commands
141 | - A new command called sendnobuf was added so you can bypass the bufferflow algorithm. This command only is worth using if you specified a bufflerFlowAlgorithm when you opened the serial port. You use it by sending "sendnobuf com4 G0 X0 Y0" and it will jump ahead of the queue and go diretly to the serial port without hesitation.
142 | - TinyG Bufferflow algorithm.
143 | - Looks for qr responses and if they are too low on the planner buffer will trigger a pause on send.
144 | - Looks for qr responses and if they are high enough to send again the bufferflow is unblocked.
145 | - If you pause with ! then the bufferflow also pauses.
146 | - If you resume with ~ then the bufferflow also resumes.
147 | - If you wipe the buffer with % then the bufferflow also wipes.
148 | - When you send !~% it automatically is sent to TinyG without buffering so it essentially skips ahead of all other buffered commands. This mimics what TinyG does internally.
149 | - If you ask qr reports to be turned off with a $qv=0 or {"qv":0} then bypassmode is entered whereby no blocking occurs on sending serial port commands.
150 | - If you ask qr reports to be turned back on with $qv=1 (or 2 or 3) or {"qv":1} (or 2 or 3) then bypassmode is turned off.
151 | - If a qr reponse is seen from TinyG then BypassMode is turned off automatically.
152 |
153 | Changes in 1.3
154 | - Added ability for buffer flow plugins. There is a new buffer flow plugin
155 | for TinyG that watches the {"qr":NN} response. When it sees the qr value
156 | go below 12 it pauses its own sending and queues up whatever is still coming
157 | in on the Websocket. This is fine because we've got plenty of RAM on the
158 | websocket server. The {"qr":NN} value is still sent back on the websocket as
159 | soon as it was before, so the host application should see no real difference
160 | as to how it worked before. The difference now though is that the serial sending
161 | knows to check if sending is paused to the serial port and queue. This makes
162 | sure no buffer overflows ever occur. The reason this was becoming important is
163 | that the lag time between the qr response and the sending of Gcode was too distant
164 | and this buffer flow needs resolution around 5ms. Normal latency on the Internet
165 | is like 20ms to 200ms, so it just wasn't fast enough. If the Javascript hosting
166 | the websocket was busy processing other events, then this lag time became even
167 | worse. So, now the Serial Port JSON Server simply helps out by lots of extra
168 | buffering. Go ahead and pound it even harder with more serial commands and see
169 | it fly.
170 |
171 | Changes in 1.2
172 | - Added better error handling
173 | - Removed forcibly adding a newline to the serial data being sent to the port. This
174 | means apps must send in a newline if the serial port expects it.
175 | - Embedded the home.html file inside the binary so there is no longer a dependency
176 | on an external file.
177 | - TODO: Closing a port on Beagle Bone seems to hang. Only solution now is to kill
178 | the process and restart.
179 | - TODO: Mac implementation seems to have trouble on writing data after a while. Mac
180 | gray screen of death can appear. Mac version uses CGO, so it is in unsafe mode.
181 | May have to rework Mac serial port to use pure golang code.
182 |
--------------------------------------------------------------------------------
/snapshot/downloads.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Downloads
4 | ---
5 |
6 | serial-port-json-server downloads (version snapshot)
7 |
8 | ### Linux
9 |
10 | * [serial-port-json-server\_linux\_386.tar.gz](serial-port-json-server_linux_386.tar.gz)
11 | * [serial-port-json-server\_linux\_amd64.tar.gz](serial-port-json-server_linux_amd64.tar.gz)
12 | * [serial-port-json-server\_linux\_arm.tar.gz](serial-port-json-server_linux_arm.tar.gz)
13 | * [serial-port-json-server\_snapshot\_amd64.deb](serial-port-json-server_snapshot_amd64.deb)
14 | * [serial-port-json-server\_snapshot\_armhf.deb](serial-port-json-server_snapshot_armhf.deb)
15 | * [serial-port-json-server\_snapshot\_i386.deb](serial-port-json-server_snapshot_i386.deb)
16 |
17 | ### MS Windows
18 |
19 | * [serial-port-json-server\_windows\_386.zip](serial-port-json-server_windows_386.zip)
20 | * [serial-port-json-server\_windows\_amd64.zip](serial-port-json-server_windows_amd64.zip)
21 |
22 | ### Other files
23 |
24 | * [README.md](README.md)
25 |
26 |
27 |
28 | Generated by goxc
--------------------------------------------------------------------------------
/trayicon.go:
--------------------------------------------------------------------------------
1 | //
2 | // trayicon.go
3 | //
4 | // Created by Martino Facchin
5 | // Copyright (c) 2015 Arduino 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 | // +build !arm
30 |
31 | package main
32 |
33 | import (
34 | "fmt"
35 | "github.com/facchinm/systray"
36 | "github.com/facchinm/systray/example/icon"
37 | "github.com/skratchdot/open-golang/open"
38 | "runtime"
39 | )
40 |
41 | func setupSysTray() {
42 | runtime.LockOSThread()
43 | systray.Run(setupSysTrayReal)
44 | }
45 |
46 | func setupSysTrayReal() {
47 |
48 | systray.SetIcon(icon.Data)
49 | mUrl := systray.AddMenuItem("Go to create.arduino.cc", "Arduino Create")
50 | mQuit := systray.AddMenuItem("Quit", "Quit the bridge")
51 |
52 | go func() {
53 | <-mQuit.ClickedCh
54 | systray.Quit()
55 | fmt.Println("Quit now...")
56 | exit()
57 | }()
58 |
59 | // We can manipulate the systray in other goroutines
60 | go func() {
61 | <-mUrl.ClickedCh
62 | open.Run("http://create.arduino.cc")
63 | }()
64 | }
65 |
--------------------------------------------------------------------------------
/trayicon_linux_arm.go:
--------------------------------------------------------------------------------
1 | //
2 | // trayicon.go
3 | //
4 | // Created by Martino Facchin
5 | // Copyright (c) 2015 Arduino 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 | func setupSysTray() {
32 | //no systray support for arm yet
33 | select {}
34 | }
35 |
--------------------------------------------------------------------------------
/update.go:
--------------------------------------------------------------------------------
1 | //
2 | // update.go
3 | //
4 | // Created by Martino Facchin
5 | // Code from github.com/sanbornm and github.com/sanderhahn
6 | // Copyright (c) 2015 Arduino LLC
7 | //
8 | // Permission is hereby granted, free of charge, to any person
9 | // obtaining a copy of this software and associated documentation
10 | // files (the "Software"), to deal in the Software without
11 | // restriction, including without limitation the rights to use,
12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the
14 | // Software is furnished to do so, subject to the following
15 | // conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be
18 | // included in all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 | // OTHER DEALINGS IN THE SOFTWARE.
28 | //
29 |
30 | package main
31 |
32 | import (
33 | "archive/zip"
34 | "bytes"
35 | "compress/gzip"
36 | "crypto/sha256"
37 | "encoding/json"
38 | "errors"
39 | "fmt"
40 | "io"
41 | "io/ioutil"
42 | "log"
43 | "math/rand"
44 | "net/http"
45 | "os"
46 | "path"
47 | "path/filepath"
48 | "runtime"
49 | "strings"
50 | "time"
51 |
52 | "github.com/inconshreveable/go-update"
53 | "github.com/kardianos/osext"
54 | "github.com/kr/binarydist"
55 | "github.com/termie/go-shutil"
56 |
57 | patch "github.com/sanderhahn/gozip/patchzip"
58 | )
59 |
60 | func IsZip(path string) bool {
61 | r, err := zip.OpenReader(path)
62 | if err == nil {
63 | r.Close()
64 | return true
65 | }
66 | return false
67 | }
68 |
69 | func Zip(path string, dirs []string) (err error) {
70 | if IsZip(path) {
71 | return errors.New(path + " is already a zip file")
72 | }
73 |
74 | f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
75 | if err != nil {
76 | return
77 | }
78 | defer f.Close()
79 |
80 | startoffset, err := f.Seek(0, os.SEEK_END)
81 | if err != nil {
82 | return
83 | }
84 |
85 | w := patch.NewWriterAt(f, startoffset)
86 |
87 | for _, dir := range dirs {
88 | err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
89 | if err != nil {
90 | return err
91 | }
92 |
93 | fh, err := patch.FileInfoHeader(info)
94 | if err != nil {
95 | return err
96 | }
97 | fh.Name = path
98 |
99 | p, err := w.CreateHeader(fh)
100 | if err != nil {
101 | return err
102 | }
103 | if !info.IsDir() {
104 | content, err := ioutil.ReadFile(path)
105 | if err != nil {
106 | return err
107 | }
108 | _, err = p.Write(content)
109 | if err != nil {
110 | return err
111 | }
112 | }
113 | return err
114 | })
115 | }
116 | err = w.Close()
117 | return
118 | }
119 |
120 | func Unzip(zippath string, destination string) (err error) {
121 | r, err := zip.OpenReader(zippath)
122 | if err != nil {
123 | return err
124 | }
125 | for _, f := range r.File {
126 | fullname := path.Join(destination, f.Name)
127 | if f.FileInfo().IsDir() {
128 | os.MkdirAll(fullname, f.FileInfo().Mode().Perm())
129 | } else {
130 | os.MkdirAll(filepath.Dir(fullname), 0755)
131 | perms := f.FileInfo().Mode().Perm()
132 | out, err := os.OpenFile(fullname, os.O_CREATE|os.O_RDWR, perms)
133 | if err != nil {
134 | return err
135 | }
136 | rc, err := f.Open()
137 | if err != nil {
138 | return err
139 | }
140 | _, err = io.CopyN(out, rc, f.FileInfo().Size())
141 | if err != nil {
142 | return err
143 | }
144 | rc.Close()
145 | out.Close()
146 |
147 | mtime := f.FileInfo().ModTime()
148 | err = os.Chtimes(fullname, mtime, mtime)
149 | if err != nil {
150 | return err
151 | }
152 | }
153 | }
154 | return
155 | }
156 |
157 | func UnzipList(path string) (list []string, err error) {
158 | r, err := zip.OpenReader(path)
159 | if err != nil {
160 | return
161 | }
162 | for _, f := range r.File {
163 | list = append(list, f.Name)
164 | }
165 | return
166 | }
167 |
168 | // Update protocol:
169 | //
170 | // GET hk.heroku.com/hk/linux-amd64.json
171 | //
172 | // 200 ok
173 | // {
174 | // "Version": "2",
175 | // "Sha256": "..." // base64
176 | // }
177 | //
178 | // then
179 | //
180 | // GET hkpatch.s3.amazonaws.com/hk/1/2/linux-amd64
181 | //
182 | // 200 ok
183 | // [bsdiff data]
184 | //
185 | // or
186 | //
187 | // GET hkdist.s3.amazonaws.com/hk/2/linux-amd64.gz
188 | //
189 | // 200 ok
190 | // [gzipped executable data]
191 | //
192 | //
193 |
194 | const (
195 | plat = runtime.GOOS + "-" + runtime.GOARCH
196 | )
197 |
198 | const devValidTime = 7 * 24 * time.Hour
199 |
200 | var ErrHashMismatch = errors.New("new file hash mismatch after patch")
201 | var up = update.New()
202 |
203 | // Updater is the configuration and runtime data for doing an update.
204 | //
205 | // Note that ApiURL, BinURL and DiffURL should have the same value if all files are available at the same location.
206 | //
207 | // Example:
208 | //
209 | // updater := &selfupdate.Updater{
210 | // CurrentVersion: version,
211 | // ApiURL: "http://updates.yourdomain.com/",
212 | // BinURL: "http://updates.yourdownmain.com/",
213 | // DiffURL: "http://updates.yourdomain.com/",
214 | // Dir: "update/",
215 | // CmdName: "myapp", // app name
216 | // }
217 | // if updater != nil {
218 | // go updater.BackgroundRun()
219 | // }
220 | type Updater struct {
221 | CurrentVersion string // Currently running version.
222 | ApiURL string // Base URL for API requests (json files).
223 | CmdName string // Command name is appended to the ApiURL like http://apiurl/CmdName/. This represents one binary.
224 | BinURL string // Base URL for full binary downloads.
225 | DiffURL string // Base URL for diff downloads.
226 | Dir string // Directory to store selfupdate state.
227 | Info struct {
228 | Version string
229 | Sha256 []byte
230 | }
231 | }
232 |
233 | func (u *Updater) getExecRelativeDir(dir string) string {
234 | filename, _ := osext.Executable()
235 | path := filepath.Join(filepath.Dir(filename), dir)
236 | return path
237 | }
238 |
239 | // BackgroundRun starts the update check and apply cycle.
240 | func (u *Updater) BackgroundRun() error {
241 | os.MkdirAll(u.getExecRelativeDir(u.Dir), 0777)
242 | if u.wantUpdate() {
243 | if err := up.CanUpdate(); err != nil {
244 | // fail
245 | return err
246 | }
247 | //self, err := osext.Executable()
248 | //if err != nil {
249 | // fail update, couldn't figure out path to self
250 | //return
251 | //}
252 | // TODO(bgentry): logger isn't on Windows. Replace w/ proper error reports.
253 | if err := u.update(); err != nil {
254 | return err
255 | }
256 | }
257 | return nil
258 | }
259 |
260 | func (u *Updater) wantUpdate() bool {
261 | if strings.Contains(u.CurrentVersion, "dev") {
262 | return false
263 | } else {
264 | return true
265 | }
266 | }
267 |
268 | func (u *Updater) update() error {
269 | path, err := osext.Executable()
270 | if err != nil {
271 | return err
272 | }
273 | old, err := os.Open(path)
274 | if err != nil {
275 | return err
276 | }
277 | defer old.Close()
278 |
279 | err = u.fetchInfo()
280 | if err != nil {
281 | return err
282 | }
283 | if u.Info.Version == u.CurrentVersion {
284 | return nil
285 | }
286 | bin, err := u.fetchAndVerifyPatch(old)
287 | if err != nil {
288 | if err == ErrHashMismatch {
289 | log.Println("update: hash mismatch from patched binary")
290 | } else {
291 | if u.DiffURL != "" {
292 | log.Println("update: patching binary,", err)
293 | }
294 | }
295 |
296 | bin, err = u.fetchAndVerifyFullBin()
297 | if err != nil {
298 | if err == ErrHashMismatch {
299 | log.Println("update: hash mismatch from full binary")
300 | } else {
301 | log.Println("update: fetching full binary,", err)
302 | }
303 | return err
304 | }
305 | }
306 |
307 | // close the old binary before installing because on windows
308 | // it can't be renamed if a handle to the file is still open
309 | old.Close()
310 |
311 | err, errRecover := up.FromStream(bytes.NewBuffer(bin))
312 | if errRecover != nil {
313 | return fmt.Errorf("update and recovery errors: %q %q", err, errRecover)
314 | }
315 | if err != nil {
316 | return err
317 | }
318 |
319 | // remove config.ini so at restart the package will extract again
320 | shutil.CopyFile(*configIni, *configIni+".bak", false)
321 | os.Remove(*configIni)
322 |
323 | // update done, we should decide if we need to restart ASAP (maybe a field in update json?)
324 | // BIG issue: the file has been renamed in the meantime
325 |
326 | return nil
327 | }
328 |
329 | func (u *Updater) fetchInfo() error {
330 | r, err := fetch(u.ApiURL + u.CmdName + "/" + plat + ".json")
331 | if err != nil {
332 | return err
333 | }
334 | defer r.Close()
335 | err = json.NewDecoder(r).Decode(&u.Info)
336 | if err != nil {
337 | return err
338 | }
339 | if len(u.Info.Sha256) != sha256.Size {
340 | return errors.New("bad cmd hash in info")
341 | }
342 | return nil
343 | }
344 |
345 | func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) {
346 | bin, err := u.fetchAndApplyPatch(old)
347 | if err != nil {
348 | return nil, err
349 | }
350 | if !verifySha(bin, u.Info.Sha256) {
351 | return nil, ErrHashMismatch
352 | }
353 | return bin, nil
354 | }
355 |
356 | func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) {
357 | r, err := fetch(u.DiffURL + u.CmdName + "/" + u.CurrentVersion + "/" + u.Info.Version + "/" + plat)
358 | if err != nil {
359 | return nil, err
360 | }
361 | defer r.Close()
362 | var buf bytes.Buffer
363 | err = binarydist.Patch(old, &buf, r)
364 | return buf.Bytes(), err
365 | }
366 |
367 | func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) {
368 | bin, err := u.fetchBin()
369 | if err != nil {
370 | return nil, err
371 | }
372 | verified := verifySha(bin, u.Info.Sha256)
373 | if !verified {
374 | return nil, ErrHashMismatch
375 | }
376 | return bin, nil
377 | }
378 |
379 | func (u *Updater) fetchBin() ([]byte, error) {
380 | r, err := fetch(u.BinURL + u.CmdName + "/" + u.Info.Version + "/" + plat + ".gz")
381 | if err != nil {
382 | return nil, err
383 | }
384 | defer r.Close()
385 | buf := new(bytes.Buffer)
386 | gz, err := gzip.NewReader(r)
387 | if err != nil {
388 | return nil, err
389 | }
390 | if _, err = io.Copy(buf, gz); err != nil {
391 | return nil, err
392 | }
393 |
394 | return buf.Bytes(), nil
395 | }
396 |
397 | // returns a random duration in [0,n).
398 | func randDuration(n time.Duration) time.Duration {
399 | return time.Duration(rand.Int63n(int64(n)))
400 | }
401 |
402 | func fetch(url string) (io.ReadCloser, error) {
403 | resp, err := http.Get(url)
404 | if err != nil {
405 | return nil, err
406 | }
407 | if resp.StatusCode != 200 {
408 | return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
409 | }
410 | return resp.Body, nil
411 | }
412 |
413 | func readTime(path string) time.Time {
414 | p, err := ioutil.ReadFile(path)
415 | if os.IsNotExist(err) {
416 | return time.Time{}
417 | }
418 | if err != nil {
419 | return time.Now().Add(1000 * time.Hour)
420 | }
421 | t, err := time.Parse(time.RFC3339, string(p))
422 | if err != nil {
423 | return time.Now().Add(1000 * time.Hour)
424 | }
425 | return t
426 | }
427 |
428 | func verifySha(bin []byte, sha []byte) bool {
429 | h := sha256.New()
430 | h.Write(bin)
431 | return bytes.Equal(h.Sum(nil), sha)
432 | }
433 |
434 | func writeTime(path string, t time.Time) bool {
435 | return ioutil.WriteFile(path, []byte(t.Format(time.RFC3339)), 0644) == nil
436 | }
437 |
--------------------------------------------------------------------------------
/utilities.go:
--------------------------------------------------------------------------------
1 | // utilities.go
2 |
3 | package main
4 |
5 | import (
6 | "bytes"
7 | "crypto/md5"
8 | "encoding/json"
9 | "fmt"
10 | "github.com/kardianos/osext"
11 | "io"
12 | "io/ioutil"
13 | "os"
14 | "os/exec"
15 | "path/filepath"
16 | "strings"
17 | )
18 |
19 | type Combo struct {
20 | vendors map[string]Vendor
21 | }
22 |
23 | type Vendor struct {
24 | architectures map[string]Architecture
25 | }
26 |
27 | type Architecture struct {
28 | boards map[string]Board
29 | platform map[string]Platform
30 | }
31 |
32 | type Platform struct {
33 | }
34 |
35 | type Board struct {
36 | name string
37 | vid map[int]int
38 | pid map[int]int
39 | upload Upload
40 | build Build
41 | bootloader Bootloader
42 | }
43 |
44 | type Upload struct {
45 | protocol string
46 | disable_flushing bool
47 | maximum_data_size int
48 | tool string
49 | use_1200bps_touch bool
50 | speed int
51 | maximum_size int
52 | wait_for_upload_port int
53 | }
54 |
55 | type Build struct {
56 | core string
57 | f_cpu int
58 | board string
59 | vid int
60 | pid int
61 | usb_product string
62 | mcu string
63 | extra_flags string
64 | variant string
65 | }
66 |
67 | type Bootloader struct {
68 | extended_fuses int
69 | high_fuses int
70 | file string
71 | low_fuses int
72 | lock_bits int
73 | tool string
74 | unlock_bits int
75 | }
76 |
77 | type ConfigProperty struct {
78 | value string
79 | path string
80 | }
81 |
82 | func computeMd5(filePath string) ([]byte, error) {
83 | var result []byte
84 | file, err := os.Open(filePath)
85 | if err != nil {
86 | return result, err
87 | }
88 | defer file.Close()
89 |
90 | hash := md5.New()
91 | if _, err := io.Copy(hash, file); err != nil {
92 | return result, err
93 | }
94 |
95 | return hash.Sum(result), nil
96 | }
97 |
98 | func call(stack []*exec.Cmd, pipes []*io.PipeWriter) (err error) {
99 | if stack[0].Process == nil {
100 | if err = stack[0].Start(); err != nil {
101 | return err
102 | }
103 | }
104 | if len(stack) > 1 {
105 | if err = stack[1].Start(); err != nil {
106 | return err
107 | }
108 | defer func() {
109 | pipes[0].Close()
110 | err = call(stack[1:], pipes[1:])
111 | }()
112 | }
113 | return stack[0].Wait()
114 | }
115 |
116 | // code inspired by https://gist.github.com/tyndyll/89fbb2c2273f83a074dc
117 | func pipe_commands(commands ...*exec.Cmd) ([]byte, error) {
118 | var errorBuffer, outputBuffer bytes.Buffer
119 | pipeStack := make([]*io.PipeWriter, len(commands)-1)
120 | i := 0
121 | for ; i < len(commands)-1; i++ {
122 | stdinPipe, stdoutPipe := io.Pipe()
123 | commands[i].Stdout = stdoutPipe
124 | commands[i].Stderr = &errorBuffer
125 | commands[i+1].Stdin = stdinPipe
126 | pipeStack[i] = stdoutPipe
127 | }
128 | commands[i].Stdout = &outputBuffer
129 | commands[i].Stderr = &errorBuffer
130 |
131 | if err := call(commands, pipeStack); err != nil {
132 | logger.Errorf(string(errorBuffer.Bytes()), err)
133 | return nil, err
134 | }
135 |
136 | return outputBuffer.Bytes(), nil
137 | }
138 |
139 | func getBoardName(pid string) (string, string, error) {
140 | //execPath, _ := osext.Executable()
141 |
142 | //avr := (m["arduino"].(map[string]interface{})["avr"].(map[string]interface{})["boards"])
143 | //var uno Board
144 | //uno = avr["uno"]
145 |
146 | findAllPIDs(globalConfigMap)
147 |
148 | list, _ := searchFor(globalConfigMap, []string{"pid"}, pid)
149 |
150 | var archBoardNameSlice []string
151 | archBoardName := ""
152 |
153 | if len(list) > 0 {
154 | archBoardNameSlice = strings.Split(list[0].path, ":")[:5]
155 | archBoardName = archBoardNameSlice[1] + ":" + archBoardNameSlice[2] + ":" + archBoardNameSlice[4]
156 | } else {
157 | return "", "", nil
158 | }
159 |
160 | boardPath := append(archBoardNameSlice, "name")
161 |
162 | boardName := getElementFromMapWithList(globalConfigMap, boardPath).(string)
163 |
164 | return archBoardName, boardName, nil
165 | }
166 |
167 | func matchStringWithSlice(str string, match []string) bool {
168 | for _, elem := range match {
169 | if !strings.Contains(str, elem) {
170 | return false
171 | }
172 | }
173 | return true
174 | }
175 |
176 | func recursivelyIterateConfig(m map[string]interface{}, fullpath string, match []string, mapOut *[]ConfigProperty) {
177 |
178 | for k, v := range m {
179 | switch vv := v.(type) {
180 | case string:
181 | if matchStringWithSlice(fullpath+":"+k, match) {
182 | //fmt.Println(k, "is string", vv, "path", fullpath)
183 | if mapOut != nil {
184 | *mapOut = append(*mapOut, ConfigProperty{path: fullpath, value: vv})
185 | //fmt.Println(getElementFromMapWithList(globalConfigMap, strings.Split(fullpath, ":")))
186 | }
187 | }
188 | case map[string]interface{}:
189 | //fmt.Println(k, "is a map:", fullpath)
190 | recursivelyIterateConfig(m[k].(map[string]interface{}), fullpath+":"+k, match, mapOut)
191 | default:
192 | //fmt.Println(k, "is of a type I don't know how to handle ", vv)
193 | }
194 | }
195 | }
196 |
197 | func RemoveDuplicates(xs *[]string) {
198 | found := make(map[string]bool)
199 | j := 0
200 | for i, x := range *xs {
201 | if !found[x] {
202 | found[x] = true
203 | (*xs)[j] = (*xs)[i]
204 | j++
205 | }
206 | }
207 | *xs = (*xs)[:j]
208 | }
209 |
210 | func findAllVIDs(m map[string]interface{}) []ConfigProperty {
211 | var vidList []ConfigProperty
212 | recursivelyIterateConfig(m, "", []string{"vid"}, &vidList)
213 | //fmt.Println(vidList)
214 | return vidList
215 | }
216 |
217 | func findAllPIDs(m map[string]interface{}) []ConfigProperty {
218 | var pidList []ConfigProperty
219 | recursivelyIterateConfig(m, "", []string{"pid"}, &pidList)
220 | //fmt.Println(pidList)
221 | return pidList
222 | }
223 |
224 | func searchFor(m map[string]interface{}, args []string, element string) ([]ConfigProperty, bool) {
225 | var uList []ConfigProperty
226 | var results []ConfigProperty
227 | recursivelyIterateConfig(m, "", args, &uList)
228 | //fmt.Println(uList)
229 | for _, elm := range uList {
230 | if elm.value == element {
231 | results = append(results, elm)
232 | }
233 | }
234 | return results, len(results) != 0
235 | }
236 |
237 | func getElementFromMapWithList(m map[string]interface{}, listStr []string) interface{} {
238 | var k map[string]interface{}
239 | k = m
240 | for _, element := range listStr {
241 | switch k[element].(type) {
242 | case string:
243 | return k[element]
244 | default:
245 | if element != "" {
246 | k = k[element].(map[string]interface{})
247 | }
248 | }
249 | }
250 | return k
251 | }
252 |
253 | func createGlobalConfigMap(m *map[string]interface{}) {
254 | execPath, _ := osext.Executable()
255 |
256 | file, e := ioutil.ReadFile(filepath.Dir(execPath) + "/arduino/boards.json")
257 |
258 | if e != nil {
259 | fmt.Printf("File error: %v\n", e)
260 | os.Exit(1)
261 | }
262 |
263 | //var config Combo
264 | json.Unmarshal(file, m)
265 | }
266 |
267 | // Filter returns a new slice containing all OsSerialPort in the slice that satisfy the predicate f.
268 | func Filter(vs []OsSerialPort, f func(OsSerialPort) bool) []OsSerialPort {
269 | var vsf []OsSerialPort
270 | for _, v := range vs {
271 | if f(v) {
272 | vsf = append(vsf, v)
273 | }
274 | }
275 | return vsf
276 | }
277 |
--------------------------------------------------------------------------------