├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── docs
├── screenshots
│ ├── serial.gif
│ ├── tool-bar.png
│ └── verify.gif
└── toolbar.md
├── lib
├── arduino-upload.coffee
├── boards.coffee
├── output-view.coffee
├── serial-view.coffee
└── util.coffee
├── menus
└── arduino-upload.cson
├── package.json
├── spec
├── arduino-upload-spec.coffee
└── arduino-upload-view-spec.coffee
└── styles
└── arduino-upload.less
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 | package-lock.json
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.3.2
2 | * Derp, forgot to update the import
3 |
4 | ## 1.3.1
5 | * Depend on `serialport-builds-electron` now so that I can upload custom electron builds to windows users
6 |
7 | ## 1.3.0
8 | * Windows support should be working
9 |
10 | ## 1.2.0
11 | * Board can be set now via status bar
12 | * Better arduino default path detection for macos
13 |
14 | ## 1.1.1
15 | * Actual error output when uploading
16 |
17 | ## 1.1.0
18 | * multiple bugfixes
19 | * cleaner code
20 | * quicker compiling
21 | * add support to programmers
22 |
23 | ## 1.0.3
24 | * fix [#10](https://github.com/Sorunome/arduino-upload/issues/6)
25 |
26 | ## 1.0.2
27 | * add support for arduino.org boards
28 |
29 | ## 1.0.1
30 | * fix [#6](https://github.com/Sorunome/arduino-upload/issues/6)
31 |
32 | ## 1.0.0
33 | * First major release
34 | * Added screenshots to readme
35 | * Made serialport a hard dependency, works with atom 1.10
36 |
37 | ## 0.7.1
38 | * SerialPort is now an optional dependency, please figure out how to get it working...
39 | * Fixed windows support (I hope)
40 |
41 | ## 0.7.0
42 | * commented out SerialPort until it is working via atom.... (node-pre-gyp issue....)
43 |
44 | ## 0.6.0
45 | * Added serial sending
46 |
47 | ## 0.5.0
48 | * Added verification
49 | * Building now copies .hex, .elf and .eep to sketch directory
50 |
51 | ## 0.4.0
52 | * Port to upload to is now determined by this package instead of the IDE
53 | * Made boards configurable
54 |
55 | ## 0.3.1
56 | * files when clicked on in error output are now activated even in other pane
57 |
58 | ## 0.3.0
59 | * Filenames in error output are now clickable, also jumps to line
60 |
61 | ## 0.2.0
62 | * Added Serial monitor
63 |
64 | ## 0.1.0 - First Release
65 | * Allowing building the sketch
66 | * Allowing uploading the sketch
67 | * Nice error output
68 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Sorunome
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Arduino Upload Package
2 |
3 | Did you not like the look of the Arduino IDE but were stuck with it nevertheless? Not anymore, this package provides some essential features of the Arduino IDE.
4 |
5 | * Verify a sketch
6 | * Build a sketch
7 | * Upload a sketch
8 | * Serial Monitor
9 | * Error output when compiling
10 | * Files are clickable in build output --> cursor jumps to that line in that file
11 | * Switch board directly in the status bar
12 |
13 | ## Installation
14 | `apm install arduino-upload`
15 | (Requires atom 1.10 or later)
16 |
17 | ## Troubleshooting
18 | * The verify/build output does not appear? Make sure your Arduino official IDE language is set to English.
19 | * Windows
20 | * Getting `Uncaught Error: spawn EPERM`? Be sure you don't have the Microsoft Store version of the Arduino IDE. - [#65](https://github.com/Sorunome/arduino-upload/issues/65)
21 | * Can't install because of the serialport package? Try installing the [Visual C++ 2015 building tools](https://github.com/felixrieseberg/windows-build-tools) by running `.\npm install --global --production windows-build-tools` from the directory `%LocalAppData%\atom\app-[VERSION]\resources\app\apm\bin` in an adminastrative PowerShell (this uses atoms integrated npm instead of requiring an external instance). Then update apm config with `apm config set msvs_version [VERSION]` (the version installed by the tool should be 2015) and `apm config set python [PATH]\python.exe` (where PATH is the path to the python executable). - [#46](https://github.com/Sorunome/arduino-upload/issues/46)
22 |
23 |
24 | ## Available commands
25 | * `arduino-upload:verify` - Verifies the sketches (checking for error output), deletes all sources, though
26 | * `arduino-upload:build` - Builds the current sketch, the .hex, .elf, .eep and .bin are copied to the sketch directory
27 | * `arduino-upload:upload` - Uploads the current sketch to a connected arduino
28 | * `arduino-upload:serial-monitor` - Opens the serial monitor of a connected arduino
29 |
30 | You can place these commands in a toolbar using the [**Flex Tool Bar**](https://atom.io/packages/flex-tool-bar) package. [Here's how](https://github.com/Sorunome/arduino-upload/blob/master/docs/toolbar.md).
31 |
32 | ## Screenshots
33 | Verifying a program:
34 |
35 | 
36 |
37 | Serial monitor:
38 |
39 | 
40 |
--------------------------------------------------------------------------------
/docs/screenshots/serial.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sorunome/arduino-upload/151cce0a1db218dbed7866db62d37052f367dcee/docs/screenshots/serial.gif
--------------------------------------------------------------------------------
/docs/screenshots/tool-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sorunome/arduino-upload/151cce0a1db218dbed7866db62d37052f367dcee/docs/screenshots/tool-bar.png
--------------------------------------------------------------------------------
/docs/screenshots/verify.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sorunome/arduino-upload/151cce0a1db218dbed7866db62d37052f367dcee/docs/screenshots/verify.gif
--------------------------------------------------------------------------------
/docs/toolbar.md:
--------------------------------------------------------------------------------
1 | # Arduino Upload Package
2 |
3 | ## Add a toolbar
4 |
5 | You can install `tool-bar` and `flex-tool-bar` Atom packages and use them to add a toolbar for Arduino commands, similar to the one in the official IDE.
6 |
7 | apm install tool-bar
8 | apm install flex-tool-bar
9 |
10 | The following is an example for the Flex Tool Bar's default configuration file, `toolbar.cson`. It adds the buttons for Arduino **Verify** and **Upload** commands and another to open the **Serial Monitor**.
11 |
12 | Please note that Flex Tool Bar can use many different configuration files and each different type of configuration file has it's own formatting and syntax rules.
13 |
14 | ```coffeescript
15 | [
16 | {
17 | type: "button"
18 | icon: "check"
19 | callback: ["arduino-upload:verify"]
20 | tooltip: "Arduino: Verify"
21 | enable: { grammar: "arduino" }
22 | }
23 | {
24 | type: "button"
25 | icon: "arrow-right"
26 | callback: ["arduino-upload:upload"]
27 | tooltip: "Arduino: Upload"
28 | enable: { grammar: "arduino" }
29 | }
30 | {
31 | type: "button"
32 | icon: "terminal"
33 | callback: "arduino-upload:serial-monitor"
34 | tooltip: "Arduino: Serial Monitor"
35 | enable: { grammar: "arduino" }
36 | }
37 | {
38 | type: "spacer"
39 | }
40 | {
41 | type: "button"
42 | icon: "gear"
43 | callback: "flex-tool-bar:edit-config-file"
44 | tooltip: "Edit Tool Bar"
45 | }
46 | ]
47 | ```
48 |
49 | Here's the result:
50 |
51 | 
52 |
--------------------------------------------------------------------------------
/lib/arduino-upload.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable} = require 'atom'
2 | {spawn} = require 'child_process'
3 | String::strip = -> if String::trim? then @trim() else @replace /^\s+|\s+$/g, ""
4 |
5 | fs = require 'fs'
6 | path = require 'path'
7 | OutputView = require './output-view'
8 | SerialView = require './serial-view'
9 | tmp = require 'tmp'
10 | { separator, getArduinoPath } = require './util'
11 | Boards = require './boards'
12 |
13 | try
14 | serialport = require 'serialport'
15 | catch e
16 | serialport = null
17 | try
18 | usbDetect = require 'usb-detection'
19 | catch e
20 | usbDetect = null
21 |
22 |
23 | boards = new Boards
24 | output = null
25 | serial = null
26 | serialeditor = null
27 |
28 | removeDir = (dir) ->
29 | if fs.existsSync dir
30 | for file in fs.readdirSync dir
31 | path = dir + '/' + file
32 | if fs.lstatSync(path).isDirectory()
33 | removeDir path
34 | else
35 | fs.unlinkSync path
36 |
37 | fs.rmdirSync dir
38 | module.exports = ArduinoUpload =
39 | config:
40 | arduinoExecutablePath:
41 | title: 'Arduino Executable Path'
42 | description: '''The path to the Arduino IDE executable or debug executable
43 | . This checks the default install location automatically.'''
44 | type: 'string'
45 | default: 'arduino'
46 | autosave:
47 | title: 'AutoSave'
48 | description: '''Enables auto-saving before building, uploading, or
49 | verifying. The serial monitor will be closed for this operation, but can
50 | be made to reopen automatically.'''
51 | type: 'string'
52 | default: 'disabled'
53 | enum: ['disabled', 'save', 'save+reopen']
54 | baudRate:
55 | title: 'BAUD Rate'
56 | description: '''Sets the BAUD rate for the serial connection, if this is
57 | changed the serial monitor will need to be reopened.'''
58 | type: 'number'
59 | default: '9600'
60 | board:
61 | title: 'Arduino board'
62 | description: '''If kept blank, this will take the settings from the
63 | Arduino IDE. The board uses the pattern as described
64 | here'''
65 | type: 'string'
66 | default: ''
67 | lineEnding:
68 | title: 'Default line ending in serial monitor'
69 | description: '''
70 | 0 - No line ending
71 | 1 - Newline
72 | 2 - Carriage return
73 | 3 - Both NL & CR'''
74 | type: 'integer'
75 | default: 1
76 | minimum: 0
77 | maximum: 3
78 | vendorsArduino: {
79 | 0x2341: true # Arduino
80 | 0x2a03: true # Arduino M0 Pro (perhaps other devices?)
81 | 0x03eb: true # Atmel
82 | # knockoff producers
83 | 0x0403: [ 0x6001 ] # FTDI
84 | 0x1a86: [ 0x7523 ] # QuinHeng
85 | 0x0403: [ 0x6001 ] # Future Technology Devices International, Ltd
86 | 0x10c4: [ 0xea60 ] # Silicon Labs CP210x USB to UART Bridge
87 | }
88 | vendorsProgrammer: {
89 | 0x03eb: [ 0x2141 ] # Atmel ICE debugger
90 | 0x1781: [ 0x0c9f ] # USBtinyISP Clones
91 | }
92 | buildFolders: []
93 | activate: (state) ->
94 | # Setup to use the new composite disposables API for registering commands
95 | @subscriptions = new CompositeDisposable
96 | @subscriptions.add atom.commands.add "atom-workspace",
97 | 'arduino-upload:verify': => @build false
98 | @subscriptions.add atom.commands.add "atom-workspace",
99 | 'arduino-upload:build': => @build true
100 | @subscriptions.add atom.commands.add "atom-workspace",
101 | 'arduino-upload:upload': => @upload()
102 | @subscriptions.add atom.commands.add "atom-workspace",
103 | 'arduino-upload:serial-monitor': => @openserial()
104 |
105 | output = new OutputView
106 | atom.workspace.addBottomPanel(item:output)
107 | output.hide()
108 |
109 | boards.init()
110 | boards.load()
111 | atom.config.onDidChange 'arduino-upload.arduinoExecutablePath', ({newValue, oldValue}) =>
112 | boards.load()
113 | atom.config.onDidChange 'arduino-upload.board', ({newValue, oldValue}) =>
114 | boards.set newValue
115 |
116 | atom.workspace.observeActivePaneItem (editor) =>
117 | if @isArduinoProject().isArduino
118 | boards.show()
119 | else
120 | boards.hide()
121 |
122 | deactivate: ->
123 | for own s, f of @buildFolders
124 | removeDir f
125 | @subscriptions.dispose()
126 | output?.remove()
127 | boards.destroy()
128 | @closeserial()
129 |
130 | consumeStatusBar: (statusBar) ->
131 | boards.init()
132 | boards.setStatusBar statusBar
133 |
134 | additionalArduinoOptions: (path, port = false) ->
135 | options = ['-v']
136 | if atom.config.get('arduino-upload.board') != ''
137 | options = options.concat ['--board', atom.config.get('arduino-upload.board')]
138 | if typeof port != 'boolean'
139 | if port == 'PROGRAMMER'
140 | options.push '--useprogrammer'
141 | else if port != 'ARDUINO'
142 | options = options.concat ['--port', port]
143 | if not @buildFolders[path]
144 | @buildFolders[path] = tmp.dirSync().name
145 | options = options.concat ['--pref', 'build.path='+@buildFolders[path]]
146 | return options
147 | isArduinoProject: () ->
148 | editor = atom.workspace.getActivePaneItem()
149 | file = editor?.buffer?.file?.getPath()?.split separator
150 | file?.pop()
151 | name = file?.pop()
152 | file?.push name
153 | workpath = file?.join separator
154 | name += '.ino'
155 | file?.push name
156 | file = file?.join separator
157 | isArduino = fs.existsSync file
158 | return {isArduino, workpath, file, name}
159 | _build: (options, callback, onerror, port = false) ->
160 | serialWasOpen = false
161 | autosaveConf = atom.config.get('arduino-upload.autosave')
162 | if (autosaveConf == 'save') || (autosaveConf == 'save+reopen')
163 | if (autosaveConf == 'save+reopen') && (serial != null)
164 | serialWasOpen = true
165 | @closeserial()
166 | atom.commands.dispatch(atom.views.getView(atom.workspace.getActiveTextEditor()), 'window:save-all')
167 | {isArduino, workpath, file, name} = @isArduinoProject()
168 | if not isArduino
169 | atom.notifications.addError "The file isn't part of an Arduino sketch!"
170 | callback false
171 | return
172 |
173 | dispError = false
174 | output.reset()
175 | atom.notifications.addInfo 'Building...'
176 |
177 | options = [file].concat(options).concat @additionalArduinoOptions file, port
178 | stdoutput = spawn getArduinoPath(), options
179 |
180 | error = false
181 |
182 | stdoutput.on 'error', (err) =>
183 | atom.notifications.addError "Can't find the Arduino IDE, please install it and set Arduino Executable Path in the settings! (" + err + ")"
184 | callback false
185 | error = true
186 | stdoutput.stdout.on 'data', (data) =>
187 | if data.toString().strip().indexOf('Sketch') == 0 || data.toString().strip().indexOf('Global') == 0
188 | atom.notifications.addInfo data.toString()
189 |
190 | stdoutput.stderr.on 'data', (data) =>
191 | console.log data.toString()
192 | overrideError = false
193 | if onerror
194 | overrideError = onerror(data)
195 | if data.toString().strip() == "exit status 1"
196 | console.log "ERROR OUTPUT OFF"
197 | dispError = false
198 | if dispError && !overrideError
199 | console.log data.toString()
200 | output.addLine data.toString(), @buildFolders[file], workpath
201 | if -1 != data.toString().toLowerCase().indexOf "verifying"
202 | console.log "ERROR OUTPUT ACTIVATED"
203 | dispError = true
204 |
205 | stdoutput.on 'close', (code) =>
206 | if error
207 | return
208 | info = {
209 | 'buildFolder': @buildFolders[file]
210 | 'name': name
211 | 'workpath': workpath
212 | }
213 | callback code, info
214 | output.finish()
215 | if (autosaveConf == 'save+reopen') && (serialWasOpen)
216 | @openserial()
217 | build: (keep) ->
218 | @_build ['--verify'], (code, info) =>
219 | if code != false
220 | if code != 0
221 | atom.notifications.addError 'Build failed!'
222 | else if keep
223 | for ending in ['.eep', '.elf', '.hex', '.bin']
224 | fs.createReadStream(info.buildFolder + separator + info.name + ending).pipe(fs.createWriteStream(info.workpath + separator + info.name + ending))
225 |
226 | upload: ->
227 | @getPort (port) =>
228 | if port == ''
229 | atom.notifications.addError 'No Arduino found!'
230 | return
231 | callback = (code, info) =>
232 | if code != false
233 | if code == 0
234 | atom.notifications.addInfo 'Successfully uploaded sketch'
235 | else
236 | if uploading
237 | atom.notifications.addError "Couldn't upload the sketch to the board, is it connected?"
238 | else
239 | atom.notifications.addError 'Build failed!'
240 | if serial != null
241 | @_openserialport port, false
242 | uploading = false
243 | onerror = (data) =>
244 | s = data.toString().toLowerCase()
245 | if (s.indexOf("avrdude:") != -1 || s.indexOf("uploading") == 0) && !uploading
246 | uploading = true
247 | atom.notifications.addInfo 'Uploading...'
248 | return uploading
249 | if serial == null
250 | # no serial connection open to halt
251 | @_build ['--upload'], callback, onerror, port
252 | return
253 | @serialNormalClose = false
254 | serial.close (err) =>
255 | @_build ['--upload'], callback, onerror, port
256 | isArduino: (vid, pid, vendors = false) ->
257 | if typeof vid == 'string'
258 | vid = parseInt vid, 16
259 | if typeof pid == 'string'
260 | pid = parseInt pid, 16
261 | if !vendors
262 | vendors = @vendorsArduino
263 | for own v, p of vendors
264 | if vid == parseInt v
265 | if p && typeof p == 'boolean'
266 | return true
267 | if -1 != p.indexOf pid
268 | return true
269 | return false
270 | _getPort: (callback) ->
271 | serialport.list().then (ports) =>
272 | console.log ports
273 | p = ''
274 | for port in ports
275 | if @isArduino(port.vendorId, port.productId)
276 | p = port.path;
277 | break
278 | callback p
279 | getPort: (callback) ->
280 | if serialport == null and usbDetect == null
281 | console.log 'NOTHING TO CHECK'
282 | callback 'ARDUINO'
283 | return
284 | if usbDetect == null
285 | console.log 'ONLY SERIALPORT'
286 | @_getPort callback
287 | return
288 | usbDetect.find (err, ports) =>
289 | for port in ports
290 | if @isArduino port.vendorId, port.productId, @vendorsProgrammer
291 | callback 'PROGRAMMER'
292 | return
293 | if serialport == null
294 | callback 'ARDUINO'
295 | return
296 | @_getPort callback
297 | serialNormalClose: true
298 | _openserialport: (port, start = true)->
299 | try
300 | serialport serial = new serialport(port, {
301 | baudRate: atom.config.get('arduino-upload.baudRate')
302 | parser: serialport.parsers.raw
303 | });
304 | @serialNormalClose = true
305 | serial.on 'open', (data) =>
306 | if start
307 | atom.notifications.addInfo 'Serial port opened'
308 | serial.on 'data', (data) =>
309 | serialeditor?.insertText data
310 | serial.on 'close', (data) =>
311 | if @serialNormalClose
312 | @closeserial()
313 | atom.notifications.addInfo 'Serial port closed'
314 | serial.on 'error', (data) =>
315 | console.log data
316 | @closeserial()
317 | atom.notifications.addInfo "Can't open serial port"
318 | catch e
319 | @closeserial()
320 | atom.notifications.addError e.toString()
321 | openserialport: ->
322 | if serial!=null
323 | atom.notifications.addInfo 'Serial port already open'
324 | return
325 | p = ''
326 | @getPort (port) =>
327 | if port == 'PROGRAMMER'
328 | atom.notifications.addError "Can't use a programmer as serial monitor!"
329 | @closeserial()
330 | return
331 | if port == '' or port == 'ARDUINO'
332 | atom.notifications.addError 'No Arduino found!'
333 | @closeserial()
334 | return
335 |
336 | @_openserialport(port)
337 | openserial: ->
338 | if serialport == null
339 | atom.notifications.addInfo 'Serialport dependency not present!'
340 | return
341 | if serial!=null
342 | return
343 |
344 | serialeditor = new SerialView
345 | serialeditor.open =>
346 | serialeditor.onDidDestroy =>
347 | @closeserial()
348 | serialeditor.onSend (s) =>
349 | serial?.write s
350 | @openserialport()
351 | closeserial: ->
352 | serial?.close (err) ->
353 | return
354 | serial = null
355 |
356 | serialeditor?.destroy()
357 | serialeditor = null
358 |
--------------------------------------------------------------------------------
/lib/boards.coffee:
--------------------------------------------------------------------------------
1 | {SelectListView} = require 'atom-space-pen-views'
2 | {spawn} = require 'child_process'
3 | Promise = require 'bluebird'
4 | String::strip = -> if String::trim? then @trim() else @replace /^\s+|\s+$/g, ""
5 | fs = require 'fs'
6 |
7 | readdir = Promise.promisify(fs.readdir)
8 | readFile = Promise.promisify(fs.readFile)
9 | { separator, getArduinoPath } = require './util'
10 | module.exports =
11 | class Boards extends SelectListView
12 | inited: false
13 | loaded: false
14 | boards: {}
15 | statusBar: null
16 | selectNode: null
17 | init: =>
18 | if @inited
19 | return
20 | @div = document.createElement 'div'
21 | @div.className = 'inline-block arduino-upload'
22 | @div.style.display = 'none'
23 | @inited = true
24 | addBoards: (pkg, arch, file) ->
25 | return readFile(file).then((data) =>
26 | re = /(\w+)\.name=([^\r\n]+)/g
27 | data = data.toString()
28 | while matches = re.exec data
29 | @boards[pkg+':'+arch+':'+matches[1]] = matches[2]
30 | ).catch((err) =>
31 | # do nothing
32 | )
33 | destroy: (partial = false) ->
34 | if @loaded
35 | boards = {}
36 | loaded = false
37 | if not partial
38 | @statusBar?.destroy()
39 | parseNewPath: (path) ->
40 | return readdir(path).then((files) =>
41 | Promise.each(files, (pkg) =>
42 | path2 = path + separator + pkg + separator + 'hardware'
43 | readdir(path2).then((files) =>
44 | Promise.each(files, (arch) =>
45 | path3 = path2 + separator + arch
46 | readdir(path3).then((files) =>
47 | Promise.each(files, (version) =>
48 | path4 = path3 + separator + version
49 | return @addBoards pkg, arch, path4 + separator + 'boards.txt'
50 | )
51 | ).catch((err) =>
52 | # do nothing
53 | )
54 | )
55 | ).catch((err) =>
56 | # do nothing
57 | )
58 | )
59 | ).catch((err) =>
60 | # do nothing
61 | )
62 | parseOldPath: (path) ->
63 | return readdir(path).then((files) =>
64 | Promise.each(files, (pkg) =>
65 | path2 = path + separator + pkg
66 | readdir(path2).then((files) =>
67 | Promise.each(files, (arch) =>
68 | return @addBoards pkg, arch, path2 + separator + arch + separator + 'boards.txt'
69 | )
70 | ).catch((err) =>
71 | # do nothing
72 | )
73 | )
74 | ).catch((err) =>
75 | # do nothing
76 | )
77 | load: ->
78 | @div.innerHTML = 'Loading arduino boards...'
79 | if @loaded
80 | @destroy true
81 | # first we parse the arduino15 file structure
82 | path = ''
83 | if /^win/.test process.platform
84 | path = process.env.LOCALAPPDATA + separator + 'Arduino15'
85 | else if process.platform == 'darwin'
86 | path = process.env.HOME + separator + 'Arduino15'
87 | else
88 | path = process.env.HOME + separator + '.arduino15'
89 | path += separator + 'packages'
90 | @parseNewPath(path).then( =>
91 | if /^win/.test process.platform
92 | path = getArduinoPath().split(separator)
93 | path.pop()
94 | path = path.join(separator) + separator + 'hardware'
95 | return @parseOldPath(path)
96 | ).then( =>
97 | # parse the pre-arduino 1.5 boards
98 | stdoutput = spawn getArduinoPath(), ['--get-pref', 'sketchbook.path']
99 | return new Promise((resolve, reject) =>
100 | res = ''
101 | stdoutput.stdout.on 'data', (data) =>
102 | res += data.toString()
103 | stdoutput.on 'close', (code) =>
104 | if code
105 | reject code
106 | else
107 | res = res.split('\r').join('').split('\n')
108 | while !res[res.length - 1] # remove the empty lines
109 | res.pop()
110 | res = res[res.length - 1]
111 | resolve res
112 | ).then((path) =>
113 | path = path.strip() + separator + 'hardware'
114 | @parseOldPath(path)
115 | )
116 | ).finally( =>
117 | console.log "Boards found:", @boards
118 | @loaded = true
119 | @populateStatusBar()
120 | )
121 | set: (val) ->
122 | if !@loaded || !@selectNode
123 | return # trash
124 | if @boards[val] || val == ''
125 | @selectNode.value = val
126 | else
127 | @selectNode.value = 'ignore'
128 | populateStatusBar: ->
129 | @div.innerHTML = ''
130 | @selectNode = document.createElement 'select'
131 |
132 | op = new Option()
133 | op.value = ''
134 | op.text = '== Arduino Setting =='
135 | @selectNode.options.add op
136 | op = new Option()
137 | op.value = 'ignore'
138 | op.text = '== Custom Setting =='
139 | @selectNode.options.add op
140 | for board of @boards
141 | op = new Option()
142 | op.value = board
143 | op.text = @boards[board]
144 | @selectNode.options.add op
145 | @div.appendChild @selectNode
146 |
147 | cfg = atom.config.get 'arduino-upload.board'
148 | if @boards[cfg] || cfg == ''
149 | @selectNode.value = cfg
150 | else
151 | @selectNode.value = 'ignore'
152 | @selectNode.onchange = ->
153 | if @value != 'ignore'
154 | atom.config.set 'arduino-upload.board', @value
155 | setStatusBar: (sb) ->
156 | @statusBar = sb.addRightTile item: @div, priority: 5
157 | hide: ->
158 | @div.style.display = 'none'
159 | show: ->
160 | @div.style.display = ''
161 |
--------------------------------------------------------------------------------
/lib/output-view.coffee:
--------------------------------------------------------------------------------
1 | {View} = require 'atom-space-pen-views'
2 | escape = (s) ->
3 | s.replace(/&/g,'&' ).replace(/
12 | @div class: 'arduino-upload info-view', =>
13 | @button click: 'close', class: 'btn', 'close'
14 | @pre class: 'output', @message
15 | @initialize: ->
16 | super
17 | addLine: (line, tmppath = '', path = '') ->
18 | if tmppath && path
19 | line = line.replace /((?:\/|\n|^)[\w\-\/\.]+:)(\d)*/gi, (match) =>
20 | # ok here we parse whcih filenames are clickable
21 |
22 | # extra holds the additional path in front, whch isn't clickable (such as the sketch path, or the tmp path)
23 | extra = ''
24 | # line holds the line number to jump to
25 | line = -1
26 | # match is the current thing to parse
27 | if !match.endsWith ':'
28 | line = match.substring match.lastIndexOf(':')+1
29 | match = match.substring 0, match.lastIndexOf(':') # we need this anyways to strip the last char
30 |
31 | # check if it is in the tmp directory. stripping that from the link
32 | if match.strip().startsWith tmppath
33 | extra += tmppath
34 | match = match.substring tmppath.length
35 | # strip optional trailing separator
36 | if match[0] == separator
37 | extra += separator
38 | match = match.substring 1
39 | # make sure that we are in the sketch folder
40 | if match.strip().substring(0, 7) == 'sketch' + separator
41 | extra += 'sketch' + separator
42 | match = match.substring 7
43 |
44 | # generate nice output
45 | file = match
46 | match += ':'
47 | if line != -1
48 | match += line
49 | file = file.strip()
50 | if separator != file.substring 0,1
51 | file = path + separator + file
52 | if file.lastIndexOf('.')==-1 || file.lastIndexOf('.')+20 < file.length
53 | file += '.ino'
54 | extra+''+match+''
55 | @message += line
56 | this
57 | reset: ->
58 | @message = ''
59 | finish: ->
60 | if @message.trim() == ''
61 | @message = ''
62 | @hide()
63 | return
64 | @find('pre').html @message
65 |
66 | for elem in @find('pre a')
67 | elem.addEventListener 'click', ->
68 | for pane in atom.workspace.getPanes()
69 | for editor in pane.getItems()
70 | if editor.getPath && editor.getPath() == @dataset.file
71 | pane.activateItem editor
72 | if @dataset.line != -1
73 | editor.setCursorBufferPosition [@dataset.line - 1,0]
74 | editor.scrollToCursorPosition() # center the cursor
75 | pane.activate()
76 | return
77 | atom.workspace.open(@dataset.file).then (editor) =>
78 | if @dataset.line != -1
79 | editor.setCursorBufferPosition [@dataset.line - 1,0]
80 | editor.scrollToCursorPosition() # center the cursor
81 | @show()
82 | close: ->
83 | @hide()
84 |
--------------------------------------------------------------------------------
/lib/serial-view.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | class SerialView
3 | editor: null
4 | editorView:null
5 | onDidDestroy: (callback) ->
6 | @editor.onDidDestroy =>
7 | callback()
8 | sendCallback: null
9 | sendText: ->
10 | input = @editorView?.querySelector '#arduino-upload-serial-input'
11 | if input && @sendCallback
12 | add = ['','\n','\r','\r\n']
13 | add = add[@editorView?.querySelector('#arduino-upload-serial-lineending').value]
14 | console.log @editorView?.querySelector('#arduino-upload-serial-lineending').value
15 | console.log add
16 | @sendCallback input.value+add
17 | input.value = ''
18 | onSend: (@sendCallback) ->
19 | open: (callback) ->
20 | atom.workspace.open('Serial Monitor').then (editor) =>
21 | @editor = editor
22 | @editor.setText ''
23 | @editorView = @editor.element
24 |
25 | div = document.createElement 'div'
26 | div.className = 'arduino-upload-serial'
27 | div.innerHTML = ''
28 | @editorView.childNodes[0].style.height = 'calc(100% - 1.8em)'
29 | @editorView.appendChild div
30 |
31 | input = @editorView.querySelector '#arduino-upload-serial-input'
32 | input.addEventListener 'focus', (e) =>
33 | e.preventDefault()
34 | e.stopPropagation()
35 | input.addEventListener 'keydown', (e) =>
36 | e.stopPropagation()
37 | if e.keyCode == 13 # we hit enter
38 | @sendText()
39 | select = @editorView.querySelector '#arduino-upload-serial-lineending'
40 | select.value = atom.config.get 'arduino-upload.lineEnding'
41 | select.addEventListener 'change', ->
42 | atom.config.set 'arduino-upload.lineEnding',@value
43 | @editorView.querySelector('#arduino-upload-serial-send').addEventListener 'click', (e) =>
44 | e.preventDefault()
45 | @sendText()
46 | callback()
47 | destroy: ->
48 | @editor?.destroy()
49 | sendCallback = null
50 | @editorView = null
51 | insertText: (s) ->
52 | if s instanceof Uint8Array
53 | s = new TextDecoder('utf-8').decode s
54 | console.log s
55 | @editor?.insertText s
56 |
--------------------------------------------------------------------------------
/lib/util.coffee:
--------------------------------------------------------------------------------
1 | @separator = '/'
2 | if /^win/.test process.platform
3 | @separator = '\\'
4 |
5 | @getArduinoPath = () =>
6 | execPath = atom.config.get 'arduino-upload.arduinoExecutablePath'
7 | if execPath == 'arduino'
8 | # we want the default path which is actually dependent on our os
9 | if process.platform == 'darwin'
10 | execPath = '/Applications/Arduino.app/Contents/MacOS/Arduino'
11 | if /^win/.test process.platform
12 | execPath = 'C:\\Program Files (x86)\\Arduino\\arduino_debug.exe' # arduino_debug.exe to not launch any GUI stuff
13 | return execPath
14 |
--------------------------------------------------------------------------------
/menus/arduino-upload.cson:
--------------------------------------------------------------------------------
1 | # See https://atom.io/docs/latest/hacking-atom-package-word-count#menus for more details
2 | 'menu': [
3 | {
4 | 'label': 'Packages'
5 | 'submenu': [
6 | 'label': 'Arduino Upload'
7 | 'submenu': [
8 | {
9 | 'label': 'Verify'
10 | 'command': 'arduino-upload:verify'
11 | },
12 | {
13 | 'label': 'Build'
14 | 'command': 'arduino-upload:build'
15 | },
16 | {
17 | 'label': 'Upload'
18 | 'command': 'arduino-upload:upload'
19 | },
20 | {
21 | 'label': 'Serial Monitor'
22 | 'command': 'arduino-upload:serial-monitor'
23 | }
24 | ]
25 | ]
26 | }
27 | ]
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "arduino-upload",
3 | "main": "./lib/arduino-upload",
4 | "version": "1.8.0",
5 | "description": "Upload your arduino sketches without using the IDE",
6 | "keywords": [
7 | "arduino",
8 | "upload",
9 | "sketch",
10 | "serial",
11 | "compile"
12 | ],
13 | "repository": "https://github.com/Sorunome/arduino-upload",
14 | "license": "MIT",
15 | "engines": {
16 | "atom": ">=1.0.0"
17 | },
18 | "consumedServices": {
19 | "status-bar": {
20 | "versions": {
21 | ">=1.0.0": "consumeStatusBar"
22 | }
23 | }
24 | },
25 | "dependencies": {
26 | "atom-space-pen-views": ">=2.0.3",
27 | "bluebird": ">=3.5.0",
28 | "serialport": ">=8.0.6",
29 | "tmp": ">=0.0.31",
30 | "usb-detection": ">=4.8.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/spec/arduino-upload-spec.coffee:
--------------------------------------------------------------------------------
1 | ArduinoUpload = require '../lib/arduino-upload'
2 |
3 | # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
4 | #
5 | # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit`
6 | # or `fdescribe`). Remove the `f` to unfocus the block.
7 |
8 | describe "ArduinoUpload", ->
9 | [workspaceElement, activationPromise] = []
10 |
11 | beforeEach ->
12 | workspaceElement = atom.views.getView(atom.workspace)
13 | activationPromise = atom.packages.activatePackage('arduino-upload')
14 |
15 | describe "when the arduino-upload:toggle event is triggered", ->
16 | it "hides and shows the modal panel", ->
17 | # Before the activation event the view is not on the DOM, and no panel
18 | # has been created
19 | expect(workspaceElement.querySelector('.arduino-upload')).not.toExist()
20 |
21 | # This is an activation event, triggering it will cause the package to be
22 | # activated.
23 | atom.commands.dispatch workspaceElement, 'arduino-upload:toggle'
24 |
25 | waitsForPromise ->
26 | activationPromise
27 |
28 | runs ->
29 | expect(workspaceElement.querySelector('.arduino-upload')).toExist()
30 |
31 | arduinoUploadElement = workspaceElement.querySelector('.arduino-upload')
32 | expect(arduinoUploadElement).toExist()
33 |
34 | arduinoUploadPanel = atom.workspace.panelForItem(arduinoUploadElement)
35 | expect(arduinoUploadPanel.isVisible()).toBe true
36 | atom.commands.dispatch workspaceElement, 'arduino-upload:toggle'
37 | expect(arduinoUploadPanel.isVisible()).toBe false
38 |
39 | it "hides and shows the view", ->
40 | # This test shows you an integration test testing at the view level.
41 |
42 | # Attaching the workspaceElement to the DOM is required to allow the
43 | # `toBeVisible()` matchers to work. Anything testing visibility or focus
44 | # requires that the workspaceElement is on the DOM. Tests that attach the
45 | # workspaceElement to the DOM are generally slower than those off DOM.
46 | jasmine.attachToDOM(workspaceElement)
47 |
48 | expect(workspaceElement.querySelector('.arduino-upload')).not.toExist()
49 |
50 | # This is an activation event, triggering it causes the package to be
51 | # activated.
52 | atom.commands.dispatch workspaceElement, 'arduino-upload:toggle'
53 |
54 | waitsForPromise ->
55 | activationPromise
56 |
57 | runs ->
58 | # Now we can test for view visibility
59 | arduinoUploadElement = workspaceElement.querySelector('.arduino-upload')
60 | expect(arduinoUploadElement).toBeVisible()
61 | atom.commands.dispatch workspaceElement, 'arduino-upload:toggle'
62 | expect(arduinoUploadElement).not.toBeVisible()
63 |
--------------------------------------------------------------------------------
/spec/arduino-upload-view-spec.coffee:
--------------------------------------------------------------------------------
1 | ArduinoUploadView = require '../lib/arduino-upload-view'
2 |
3 | describe "ArduinoUploadView", ->
4 | it "has one valid test", ->
5 | expect("life").toBe "easy"
6 |
--------------------------------------------------------------------------------
/styles/arduino-upload.less:
--------------------------------------------------------------------------------
1 | // The ui-variables file is provided by base themes provided by Atom.
2 | //
3 | // See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less
4 | // for a full listing of what's available.
5 | @import "ui-variables";
6 |
7 | .arduino-upload > pre {
8 | max-height:200pt;
9 | }
10 |
11 | .editor .arduino-upload-serial {
12 | position: absolute;
13 | bottom: 0;
14 | left: 0;
15 | width: 100%;
16 | }
17 | /* copied from https://github.com/atom/atom/blob/master/static/buttons.less */
18 | .editor .arduino-upload-serial button {
19 | color: @text-color;
20 | border-radius: @component-border-radius;
21 | border: none;
22 | text-shadow: none;
23 |
24 | height: @component-line-height + 2px;
25 | line-height: @component-line-height;
26 |
27 | padding: 0 @component-padding;
28 | font-size: @font-size;
29 | z-index: 0;
30 |
31 | background-color: @button-background-color;
32 | &:hover {
33 | background-color: @button-background-color-hover;
34 | }
35 | &.selected,
36 | &.selected:hover {
37 | // we want the selected button to behave like the :hover button; it's on top of the other buttons.
38 | z-index: 1;
39 | background-color: @button-background-color-selected;
40 | }
41 | }
42 | .editor .arduino-upload-serial select {
43 | color: @text-color;
44 | border-radius: @component-border-radius;
45 | border: none;
46 | text-shadow: none;
47 |
48 | height: @component-line-height + 2px;
49 | line-height: @component-line-height;
50 |
51 | padding: 0 @component-padding;
52 | font-size: @font-size;
53 | z-index: 0;
54 |
55 | background-color: @button-background-color;
56 | &:hover {
57 | background-color: @button-background-color-hover;
58 | }
59 | &.selected,
60 | &.selected:hover {
61 | // we want the selected button to behave like the :hover button; it's on top of the other buttons.
62 | z-index: 1;
63 | background-color: @button-background-color-selected;
64 | }
65 | }
66 | .editor .arduino-upload-serial #arduino-upload-serial-input {
67 | color: @text-color;
68 | border-color:@input-border-color;
69 | border-radius: @component-border-radius;
70 | border: none;
71 | text-shadow: none;
72 |
73 | height: @component-line-height + 2px;
74 | line-height: @component-line-height;
75 |
76 | padding: 0 @component-padding;
77 | font-size: @font-size;
78 | z-index: 0;
79 |
80 | background-color: @input-background-color;
81 |
82 | width: ~"calc(100% - 18em)"; // just tried out what looked OK
83 | }
84 |
85 | .status-bar .arduino-upload select {
86 | color: @text-color;
87 | border: none;
88 | text-shadow: none;
89 |
90 | z-index: 0;
91 |
92 | background-color: @button-background-color;
93 | &:hover {
94 | background-color: @button-background-color-hover;
95 | }
96 | &.selected,
97 | &.selected:hover {
98 | // we want the selected button to behave like the :hover button; it's on top of the other buttons.
99 | z-index: 1;
100 | background-color: @button-background-color-selected;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------