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