1 ;;; ede-arduino.el --- EDE support for arduino projects / sketches -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2012 Eric M. Ludlam
5 ;; Author: Eric M. Ludlam <eric@siege-engine.com>
7 ;; This program is free software; you can redistribute it and/or
8 ;; modify it under the terms of the GNU General Public License as
9 ;; published by the Free Software Foundation, either version 3 of the
10 ;; License, or (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful, but
13 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ;; General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see http://www.gnu.org/licenses/.
22 ;; EDE support for arduino projects.
24 ;; For most basic configurations, you will still need to use the
25 ;; arduino IDE to set and change preferences. Use the command
26 ;; 'ede-arduino-sync' to syncrhonize with changes made in the arduino
30 ;; To download an arduino mode for your code, see this mode:
31 ;; https://github.com/bookest/arduino-mode
36 (defcustom ede-arduino-makefile-name
"Makefile"
37 "File name to use for generated Makefile."
41 (defcustom ede-arduino-make-command
"make"
42 "Command used to run Makefiles."
46 (defcustom ede-arduino-container-prefix nil
47 "The location of the arduino installs container prefix."
52 (defcustom ede-arduino-preferences-file
"~/.arduino/preferences.txt"
53 "The location of personl preferences for the arduino IDE.
54 Note: If this changes, we need to also update the autoload feature."
58 (defcustom ede-arduino-boards-file
"hardware/arduino/avr/boards.txt"
59 "The location of the arduino boards file."
63 (defcustom ede-arduino-avrdude-baudrate nil
64 "Override the board specific baud rates."
69 ;;; Arduino Preferences
71 ;; Derive data from the arduino IDE's preferences.
74 (defclass ede-arduino-prefs
()
75 ((timestamp :initform nil
)
76 (prefssize :initform nil
)
77 (board :initform
"uno")
78 (port :initform
"/dev/ttyUSB1")
79 (sketchbook :initform
"~/Arduino")
80 (boardobj :initform nil
))
81 "Class containing arduino preferences.")
83 (defvar ede-arduino-active-prefs nil
84 "The currently active preferences for Arduino development.")
86 (defun ede-arduino-sync ()
87 "Synchronize arduino development preferences with the arduino IDE.
88 Synchronization pulls preferences from `ede-arduino-preferences-file'
89 for use in Emacs. It does not copy preferences or changes made in
90 Emacs back to the Arduino IDE."
92 (when (not (file-exists-p ede-arduino-preferences-file
))
93 (if (y-or-n-p "Can't find arduino preferences. Start IDE to configure? ")
95 (error "EDE cannot build/upload arduino projects without preferences from the arduino IDE")))
96 (ede-arduino-read-prefs ede-arduino-preferences-file
)
97 ede-arduino-active-prefs
)
99 (defun ede-arduino-read-prefs (prefsfile)
100 "Read in arduino preferences from the PREFSFILE."
101 (let* ((buff (get-file-buffer prefsfile
))
102 (stats (file-attributes prefsfile
))
108 (when (not ede-arduino-active-prefs
)
109 (setq ede-arduino-active-prefs
(make-instance 'ede-arduino-prefs
)))
111 ;; Only update the prefs if the prefs file changed.
112 (when (or (not (oref ede-arduino-active-prefs timestamp
))
113 (/= (or (oref ede-arduino-active-prefs prefssize
) 0) size
)
114 (not (equal (oref ede-arduino-active-prefs timestamp
) mod
)))
116 (setq buff
(find-file-noselect prefsfile
)
118 (with-current-buffer buff
120 (goto-char (point-min))
121 (when (not (re-search-forward "^serial.port=" nil t
))
122 (error "Cannot find serial.port from the arduino preferences"))
123 (oset ede-arduino-active-prefs port
124 (buffer-substring-no-properties (point) (point-at-eol)))
126 (goto-char (point-min))
127 (when (not (re-search-forward "^board=" nil t
))
128 (error "Cannot find board from the arduino preferences"))
129 (setq board
(buffer-substring-no-properties (point) (point-at-eol)))
130 (oset ede-arduino-active-prefs board board
)
132 (goto-char (point-min))
133 (when (not (re-search-forward "^sketchbook.path=" nil t
))
134 (error "Cannot find sketchbook.path from the arduino preferences"))
135 (oset ede-arduino-active-prefs sketchbook
136 (file-name-as-directory
138 (buffer-substring-no-properties (point) (point-at-eol)))))
140 (when kill
(kill-buffer buff
))
142 (oset ede-arduino-active-prefs boardobj
(ede-arduino-board-data board
))
143 (oset ede-arduino-active-prefs prefssize size
)
144 (oset ede-arduino-active-prefs timestamp mod
))))))
146 ;;; Arduino Intuition
148 ;; Examine the environment to find arduino library locations
149 ;; so we can call the utilities.
151 (defcustom ede-arduino-arduino-command
"arduino"
152 "The command used for starting the arduino IDE.
153 The IDE is actually a script, so the purpose here is only to look up
154 where the arduino APPDIR is.
156 If you are customizing this variable, consider the short-cut of just
157 customizing the `ede-arduino-appdir' variable instead."
161 (defcustom ede-arduino-appdir nil
162 "The location of the arduino build environment's application.
163 This is also where Arduino.mk will be found."
167 (defun ede-arduino ()
168 "Launch the arduino IDE."
170 (let ((b (get-buffer-create "*Arduino IDE*"))
171 (cd default-directory
))
172 (with-current-buffer b
173 (setq default-directory cd
)
175 (apply #'start-process
"arduino" b ede-arduino-arduino-command nil
)))
177 (defun ede-arduino-find-install (&optional full-path
)
178 "Return the `FULL-PATH' where arduino IDE code is installed.
180 If `full-path' is set return a full path including container prefix,
183 ((and ede-arduino-appdir
184 (file-exists-p (concat ede-arduino-container-prefix ede-arduino-appdir
)))
186 (concat ede-arduino-container-prefix ede-arduino-appdir
)
188 ((and ede-arduino-appdir
(file-exists-p ede-arduino-appdir
))
191 ;; Derive by looking up the arduino script.
192 (let ((arduinofile ede-arduino-arduino-command
))
193 (when (not (file-exists-p arduinofile
))
194 ;; Look up where it might be...
195 (setq arduinofile
(locate-file arduinofile exec-path
))
197 (when (not (file-exists-p arduinofile
))
198 (error "Cannot find arduino command location"))
200 (let ((buff (get-file-buffer arduinofile
))
203 (setq buff
(find-file-noselect arduinofile
)
205 (with-current-buffer buff
207 (goto-char (point-min))
209 (when (not (re-search-forward "APPDIR=" nil t
))
210 (error "Cannot find APPDIR from the arduino command"))
213 (setq ede-arduino-appdir
214 (buffer-substring-no-properties (point) (point-at-eol)))
215 (when kill
(kill-buffer buff
)))))))))))
217 (defun ede-arduino-Arduino.mk
()
218 "Return the location of Arduino's makefile helper."
219 (expand-file-name "Arduino.mk" (ede-arduino-find-install)))
221 (defun ede-arduino-Arduino-Version ()
222 "Return the version of the installed Arduino."
223 (let ((vfile (expand-file-name "lib/version.txt" (ede-arduino-find-install t
))))
224 (let ((buff (get-file-buffer vfile
))
227 (setq buff
(find-file-noselect vfile
)
230 (with-current-buffer buff
232 (goto-char (point-min))
233 (buffer-substring-no-properties (point) (point-at-eol))))
234 (if kill
(kill-buffer buff
))))))
236 (defun ede-arduino-boards.txt
()
237 "Return the location of Arduino's boards.txt file."
238 (expand-file-name ede-arduino-boards-file
(ede-arduino-find-install t
)))
240 (defun ede-arduino-libdir (&optional library
)
241 "Return the full file location of LIBRARY.
242 If LIBRARY is not provided as an argument, just return the library directory."
243 (let ((libdir (expand-file-name "libraries" (ede-arduino-find-install))))
245 (expand-file-name library libdir
)
248 ;;; Arduino Board Reading
250 ;; Load data from boards.txt
252 (defclass ede-arduino-board
()
253 ((name :initarg
:name
256 "The name of the arduino board represented by this object.")
257 (protocol :initarg
:protocol
260 "The protocol used to talk to the board.")
261 (speed :initarg
:speed
264 "The SPEED of the arduino board's serial upload.")
265 (maximum-size :initarg
:maximum-size
268 "The MAXIMUM_SIZE of the arduino board's uploadable target .")
272 "The MCU of the arduino board.")
273 (f_cpu :initarg
:f_cpu
276 "The F_CPU of the arduino board.")
280 "The core name for this board."))
281 "Class for containing key aspect of the arduino board.")
283 (defun ede-arduino-board-data (boardname)
284 "Read in the data from baords.txt for BOARDNAME.
285 Data returned is the intputs needed for the Makefile."
286 (let* ((buff (get-file-buffer (ede-arduino-boards.txt
)))
297 (setq buff
(find-file-noselect (ede-arduino-boards.txt
))
300 (with-current-buffer buff
302 (goto-char (point-min))
303 (when (not (re-search-forward (concat "^" boardname
".name=") nil t
))
304 (error "Cannot find %s.name looking up board" boardname
))
305 (setq name
(buffer-substring-no-properties (point) (point-at-eol)))
307 (goto-char (point-min))
308 (when (not (re-search-forward (concat "^" boardname
".upload.protocol=") nil t
))
309 (error "Cannot find %s.upload.protocol looking up board" boardname
))
310 (setq protocol
(buffer-substring-no-properties (point) (point-at-eol)))
312 (goto-char (point-min))
313 (when (not (re-search-forward (concat "^" boardname
".upload.speed=") nil t
))
314 (error "Cannot find %s.upload.speed looking up board" boardname
))
315 (setq speed
(buffer-substring-no-properties (point) (point-at-eol)))
317 (goto-char (point-min))
318 (when (not (re-search-forward (concat "^" boardname
".upload.maximum_size=") nil t
))
319 (error "Cannot find %s.upload.maximum_size looking up board" boardname
))
320 (setq size
(buffer-substring-no-properties (point) (point-at-eol)))
322 (goto-char (point-min))
323 (when (not (re-search-forward (concat "^" boardname
".build.mcu=") nil t
))
324 (error "Cannot find %s.build.mcu looking up board" boardname
))
325 (setq mcu
(buffer-substring-no-properties (point) (point-at-eol)))
327 (goto-char (point-min))
328 (when (not (re-search-forward (concat "^" boardname
".build.f_cpu=") nil t
))
329 (error "Cannot find %s.build.f_cpu looking up board" boardname
))
330 (setq f_cpu
(buffer-substring-no-properties (point) (point-at-eol)))
332 (goto-char (point-min))
333 (when (not (re-search-forward (concat "^" boardname
".build.core=") nil t
))
334 (error "Cannot find %s.build.core looking up board" boardname
))
335 (setq core
(buffer-substring-no-properties (point) (point-at-eol)))
337 (when kill
(kill-buffer buff
))
339 (make-instance 'ede-arduino-board
;; boardname
349 (defun ede-arduino-root (&optional dir basefile
)
350 "Get the root project directory for DIR.
351 The only arduino sketches allowed are those configured by the arduino IDE
352 in their sketch directory.
354 If BASEFILE is non-nil, then convert root to the project basename also.
356 Consider expanding this at some later date."
357 (let* ((prefs (ede-arduino-sync))
358 ;; without expansion the comparison in the next step fails
359 ;; for relative files
360 (dir (expand-file-name dir
))
361 (sketchroot (and prefs
(oref prefs sketchbook
))))
362 (when (and sketchroot
363 (< (length sketchroot
) (length dir
))
364 (string= sketchroot
(substring dir
0 (length sketchroot
))))
365 ;; The subdir in DIR just below sketchroot is always the root of this
367 (let* ((dirtail (substring dir
(length sketchroot
)))
368 (dirsplit (split-string dirtail
"/" t
))
369 (root (expand-file-name (car dirsplit
) sketchroot
)))
370 (when (file-directory-p root
)
372 (let ((tmp (expand-file-name (concat (car dirsplit
) ".pde") root
)))
373 ;; Also check for the desired file in a buffer if the
374 ;; user just made the file but not saved it yet.
375 (when (or (not (file-exists-p tmp
)) (not (get-file-buffer tmp
)))
376 (setq tmp
(expand-file-name (concat (car dirsplit
) ".ino") root
)))
381 (defun ede-arduino-file (&optional dir
)
382 "Get a file representing the root of this arduino project.
383 It is a file ending in .pde or .ino that has the same basename as
384 the directory it is in. Optional argument DIR is the directory
386 (ede-arduino-root dir t
))
389 (defun ede-arduino-load (dir &optional _rootproj
)
390 "Return an Arduino project object if there is one.
391 Return nil if there isn't one.
392 Argument DIR is the directory it is created for.
393 ROOTPROJ is not used, sinc there is only one project for a directory tree."
394 (let* ((root (ede-arduino-root dir
))
395 (proj (and root
(ede-directory-get-open-project root
)))
396 (_prefs (ede-arduino-sync)))
399 (message "Opening existing project")
402 ;; Create a new project here.
405 (message "Creating new project")
406 (let* ((name (file-name-nondirectory (directory-file-name root
)))
407 (pde (expand-file-name (concat name
".pde") root
)))
408 (when (not (file-exists-p pde
))
409 (setq pde
(expand-file-name (concat name
".ino") root
)))
410 (setq proj
(ede-arduino-project name
412 :directory
(file-name-as-directory dir
)
415 (ede-add-project-to-global-list proj
))
416 (message "Project loading/creation failed")))))
419 (require 'ede
/auto
) ; for `ede-project-autoload'
423 'ede-project-class-files
424 (ede-project-autoload :name
"Arduino sketch"
427 (ede-project-autoload-dirmatch
428 :fromconfig
(expand-file-name ede-arduino-preferences-file
)
429 :configregex
"^sketchbook.path=\\([^\n]+\\)$"
431 :proj-file
'ede-arduino-file
432 :proj-root
'ede-arduino-root
433 :load-type
'ede-arduino-load
434 :class-sym
'ede-arduino-project
441 ;; The classes for arduino projects include arduino (PDE) files, plus C, CPP, and H files.
444 (defclass ede-arduino-target
(ede-target)
446 "EDE Arduino C files target. Includes PDE, C, C++ and anything else we find.")
449 (defclass ede-arduino-project
(ede-project)
450 ((keybindings :initform
'(("U" . ede-arduino-upload
)))
453 [ "Upload Project to Board" ede-arduino-upload
]
454 [ "Serial Monitor" cedet-arduino-serial-monitor
]
456 [ "Edit Projectfile" ede-edit-file-target
457 (ede-buffer-belongs-to-project-p) ]
459 [ "Update Version" ede-update-version ede-object
]
460 [ "Version Control Status" ede-vc-project-directory ede-object
]
462 [ "Rescan Project Files" ede-rescan-toplevel t
]
464 "EDE Arduino project.")
466 ;;; TARGET MANAGEMENT
468 (cl-defmethod ede-find-target ((proj ede-arduino-project
) _buffer
)
469 "Find an EDE target in PROJ for BUFFER.
470 If one doesn't exist, create a new one for this directory."
471 (let* ((targets (oref proj targets
))
472 (dir default-directory
)
473 (ans (object-assoc dir
:path targets
))
476 (setq ans
(make-instance 'ede-arduino-target
;; dir
477 :name
(file-name-nondirectory
478 (directory-file-name dir
))
481 (object-add-to-list proj
:targets ans
))
486 (defun ede-arduino-upload ()
487 "Compile the current project, and upload the result to the board."
489 (project-compile-project (ede-current-project)
490 (concat ede-arduino-make-command
" all upload")))
494 (defun cedet-arduino-serial-monitor ()
495 "Start up a serial monitor for a running arduino board.
498 (let ((prefs (ede-arduino-sync)))
499 ;; @TODO - read the setup function for something configuring the
500 ;; serial line w/ a baud rate, and use that.
501 (serial-term (oref prefs port
) 9600)
502 ;; Always go to line mode, as arduino serial isn't typically used
503 ;; for input, just debugging output.
506 (cl-defmethod project-compile-project ((proj ede-arduino-project
) &optional command
)
507 "Compile the entire current project PROJ.
508 Argument COMMAND is the command to use when compiling."
509 ;; 1) Create the mini-makefile.
510 (ede-arduino-create-makefile proj
)
512 (compile (or command ede-arduino-make-command
)))
514 (cl-defmethod project-compile-target ((_obj ede-arduino-target
) &optional command
)
515 "Compile the current target OBJ.
516 Argument COMMAND is the command to use for compiling the target."
517 (project-compile-project (ede-current-project) command
))
519 (cl-defmethod project-debug-target ((_target ede-arduino-target
))
520 "Run the current project derived from TARGET in a debugger."
521 (error "No Debugger support for Arduino"))
524 (require 'semantic
/db
)
526 (cl-defmethod ede-preprocessor-map ((_this ede-arduino-target
))
527 "Get the pre-processor map for some generic C code."
528 ;; wiring.h and pins_arduino.h have lots of #defines in them.
530 (let* ((wiring_h (expand-file-name "hardware/arduino/cores/arduino/wiring.h"
531 (ede-arduino-find-install)))
532 (table (when (and wiring_h
(file-exists-p wiring_h
))
533 (semanticdb-file-table-object wiring_h
)))
534 (filemap '(("HIGH" .
"0x1")
537 (when (semanticdb-needs-refresh-p table
)
538 (semanticdb-refresh-table table
))
539 (setq filemap
(append filemap
(oref table lexical-table
))))
542 (cl-defmethod ede-system-include-path ((_this ede-arduino-target
))
543 "Get the system include path used by project THIS."
544 (let* ((_prefs (ede-arduino-sync))
545 (iphardware (expand-file-name "hardware/arduino/cores/arduino"
546 (ede-arduino-find-install)))
547 (libs (ede-arduino-guess-libs))
550 (expand-file-name (concat "libraries/" lib
)
551 (ede-arduino-find-install)))
553 (cons iphardware iplibs
)))
555 (defun ede-arduino-guess-sketch ()
556 "Return the file that is the core of the current project sketch."
557 (let* ((proj ede-object-project
)
558 (sketch (expand-file-name (concat (oref proj name
) ".pde")
559 (oref proj directory
))))
560 (if (file-exists-p sketch
)
562 (setq sketch
(expand-file-name (concat (oref proj name
) ".ino")
563 (oref proj directory
)))
564 (if (file-exists-p sketch
)
566 (error "Cannot guess primary sketch file for project %s"
567 (eieio-object-name proj
))))))
569 ;;; Makefile Creation
571 ;; Use SRecode, and the ede-srecode tool to build our Makefile.
572 (require 'ede
/srecode
)
574 (cl-defmethod ede-arduino-create-makefile ((proj ede-arduino-project
))
575 "Create an arduino based Makefile for project PROJ."
576 (let* ((mfilename (expand-file-name ede-arduino-makefile-name
577 (oref proj directory
)))
578 (prefs (ede-arduino-sync))
579 (board (oref prefs boardobj
))
580 (vers (ede-arduino-Arduino-Version))
581 (sketch (ede-arduino-guess-sketch))
583 ;; (buff-to-kill nil)
585 (when (and (string= (file-name-extension sketch
) "ino")
586 (version< vers
"1.0"))
587 (error "Makefile doesn't support .ino files until Arduino 1.0"))
588 (when (and (string= (file-name-extension sketch
) "pde")
589 (version<= "1.0" vers
))
590 (error "Makefile doesn't support .pde files after Arduino 1.0"))
592 (setq orig-buffer
(get-file-buffer mfilename
))
594 ;; (setq buff-to-kill
595 (find-file-noselect mfilename
)
597 (goto-char (point-min))
598 (if (and (not (eobp))
599 (not (looking-at "# Automatically Generated \\w+ by EDE.")))
600 (if (not (y-or-n-p (format "Really replace %s? " mfilename
)))
601 (error "Not replacing Makefile"))
602 (message "Replaced EDE Makefile"))
605 ;; Insert a giant pile of stuff that is common between
606 ;; one of our Makefiles, and a Makefile.in
609 "TARGET" (oref proj name
)
610 "ARDUINO_LIBS" (mapconcat #'identity
(ede-arduino-guess-libs) " ")
611 "MCU" (oref board mcu
)
612 "F_CPU" (oref board f_cpu
)
613 "PORT" (oref prefs port
)
614 "AVRDUDE_ARD_BAUDRATE" (or ede-arduino-avrdude-baudrate
(oref board speed
))
615 "AVRDUDE_ARD_PROGRAMMER" (oref board protocol
)
616 "ARDUINO_MK" (ede-arduino-Arduino.mk
)
617 "ARDUINO_HOME" (ede-arduino-find-install)))
619 (when (not orig-buffer
) (kill-buffer (current-buffer))))))
621 ;;; Arduino Sketch Code Inspector
623 ;; Inspect the code in an arduino sketch, and guess things, like which libraries to include.
625 (defun ede-arduino-guess-libs ()
626 "Guess which libraries this sketch use."
629 (sketch (ede-arduino-guess-sketch))
630 (sketch-buffer (find-file-noselect sketch
))
631 (arduino-libraries (with-current-buffer sketch-buffer
632 (if (boundp 'arduino-libraries
)
637 (dolist (lib (split-string arduino-libraries
))
640 (let* ((orig-buffer (get-file-buffer sketch
))
644 (setq buff
(find-file-noselect sketch
))
646 (goto-char (point-min))
647 (while (re-search-forward "#include <\\([[:word:]_]+\\).h>" nil t
)
648 (setq tmp
(match-string 1))
649 (unless (file-exists-p (concat tmp
".h"))
651 (let* ((lib (match-string 1))
652 (libdir (ede-arduino-libdir lib
))
653 (util (expand-file-name "utility" libdir
)))
654 ;; Some libraries need a utility added to the library list.
655 (when (file-exists-p util
)
656 (push (concat lib
"/utility") libs
))
657 ;; Push real lib after the utility
659 (when (not orig-buffer
) (kill-buffer buff
)))))
662 (provide 'ede-arduino
)
664 ;;; ede-arduino.el ends here