Làm cách nào để tổ chức cấu trúc các dự án Arduino của tôi để kiểm soát nguồn dễ dàng?


75

Lâu lắm rồi tôi mới tìm kiếm một câu trả lời hay cho câu hỏi này.

Thông thường, bất kỳ dự án Arduino nào nhưng đơn giản nhất sẽ bao gồm:

  • Tệp mã nguồn chính MyProject.ino
  • Các thư viện dành riêng cho dự án ( MyProjectLibrary1.h, MyProjectLibrary1.cpp...)
  • Thư viện của bên thứ ba (thường là nguồn mở miễn phí, được thêm thủ công vào thư mục thư viện Arduino)
  • Sơ đồ, sơ đồ PCB
  • Tài liệu
  • ...

Tất cả điều này làm cho khó có thể giữ toàn bộ mã và tài liệu của một dự án trong Quản lý mã nguồn (ví dụ: trên Subversion, Git hoặc GitHub).

Quản lý kiểm soát nguồn dự án của bạn có nghĩa là quản lý phiên bản của tất cả các tệp được sử dụng bởi dự án bao gồm các thư viện của bên thứ 3.

Bây giờ cho một dự án duy nhất, tôi cần xác định cấu trúc thư mục:

  • Bao gồm tất cả các tệp dự án như được mô tả ở trên
  • Tôi hoàn toàn có thể cam kết với một công cụ quản lý mã nguồn (bao gồm cả phụ thuộc của bên thứ 3)
  • Tôi có thể kiểm tra bất cứ nơi nào trên ổ cứng của mình và xây dựng dự án từ đó (có phải là một vị trí duy nhất theo quy định của Arduino IDE)
  • Tôi có thể nén vào một kho lưu trữ độc lập mà tôi có thể gửi cho một người bạn để anh ta xây dựng dễ dàng nhất có thể (không cần tải thêm thủ công)

Điều tôi thấy đặc biệt khó khăn với các dự án Arduino là việc quản lý các phụ thuộc thư viện bên ngoài. Các nhà phát triển dự án Java có kho lưu trữ maven cho điều đó và điều đó giúp ích rất nhiều trong việc quản lý tất cả các thiết bị bên ngoài. Nhưng chúng tôi không có một hệ thống tương đương cho các thư viện Arduino.

Tôi sẽ quan tâm để biết làm thế nào các nhà sản xuất dự án Arduino khác đối phó với các khía cạnh này trong các dự án riêng của họ.

Cũng lưu ý rằng tôi sẵn sàng thay đổi quy trình phát triển của mình, bao gồm cả IDE của tôi (hiện tại tôi sử dụng Eclipse với plugin Arduino và sau đó tôi đảm bảo các dự án của mình cũng có thể hoạt động trực tiếp với Arduino IDE).


Tôi cũng đã phải vật lộn với điều này. Tôi có hai dự án cần các phiên bản khác nhau của thư viện bên ngoài và hiện tại chúng nằm ngoài sự kiểm soát phiên bản.
Cyberg Ribbon

1
Xin lưu ý để nghiên cứu thêm rằng đó là những người quản lý gói . JavaScript có Node.js / npm và bower, PHP có PEAR và Trình soạn thảo, v.v.
kaiser

Câu trả lời:


30

Cách tổ chức một dự án arduino của tôi khá đơn giản, tất cả các dự án của tôi đều là kho git để có ít nhất những điều sau:

Tôi có một sở thích sử dụng trình soạn thảo yêu thích của tôi và Makefile mà tôi đã tạo ra để chống lại hầu hết các trường hợp sử dụng (và tôi thậm chí còn cải thiện cái mà tôi sẽ chia sẻ sớm).

Đối với các thư viện, tôi thích giữ chúng làm kho lưu trữ của riêng mình và sử dụng mô hình con git để đưa chúng vào dự án. Vì nhiều thư viện được viết bởi cộng đồng được chia sẻ dưới dạng kho git, đó là một giải pháp chung tốt. Sau đó, trong Makefile, tôi chỉ cần thêm đường dẫn thư viện mà tôi muốn đưa vào biến LOCALLIBS .

Mặc dù, đối với một số dự án, việc đóng gói các thư viện vào một thư viện lớp trừu tượng phần cứng được tạo cho dự án, thì tôi thích sử dụng một đường dẫn như:

  • project
    • project.ino
    • Makefile
    • project_hal_lib
      • library1
      • library2
      • library3
      • Giáo dục

Mặc dù, với arduino 1.5.xa, cách thức mới để chỉ định các thư viện được cung cấp, sẽ cung cấp một cách để tạo và xây dựng các dự án arduino giống như cách chúng ta đã làm với pipy và virtualenv trong python, tức là bạn xác định bộ thư viện bạn cần và chúng được tải về.


Tôi đã làm việc trên một câu trả lời tương tự. Bạn đánh bại tôi vào nó!
asheeshr

+1 Cảm ơn! Cách này trông khá thú vị. Tôi sẽ phải dùng thử trong tuần này (tuy nhiên tôi cần kiểm tra cách thiết lập công cụ Makefile trước).
jfpoilpret

@AsheeshR nếu câu trả lời của bạn tương tự, điều đó có nghĩa là nó vẫn có một số khác biệt, phải không? Tôi muốn biết về những điều này!
jfpoilpret

Trên thực tế, những thay đổi chính đi kèm với phiên bản tiếp theo của Makefile của tôi sẽ là khả năng flashsử dụng một lập trình viên hoặc uploadsử dụng bộ tải khởi động. Cũng như xử lý việc hợp nhất bootloader với firmware. Tôi cũng đã viết một setter cầu chì in-makefile.
zmo

@zmo bounty xứng đáng, mặc dù giải pháp Makefile của bạn không thể hoạt động trong tình huống của tôi (sử dụng Windows nhưng tôi không chỉ định điểm đó). Nhưng tôi tin rằng sử dụng một trong những giải pháp makefile hiện có là cách tốt nhất. Một khi tôi đã tìm thấy một cái vòi cho tôi, tôi sẽ đăng câu trả lời của mình ở đây.
jfpoilpret

23

Cách đơn giản nhất để làm điều này là sao chép các tệp tiêu đề và mã của thư viện vào thư mục nguồn của bạn và bao gồm chúng.

myproject/
    myproject.ino
    somelib.h
    somelib.cpp

Trong mã của bạn, bạn có thể làm include "somelib.h"

Mặt trái của vấn đề này là các thư viện phải nằm trong cùng một thư mục, không phải thư mục con, vì vậy nó làm cho thư mục của bạn trông lộn xộn.


Về cấu trúc thư mục của toàn bộ dự án của tôi, bao gồm cả sơ đồ và tài liệu, tôi thường trông như thế này:

myproject/
  schematics/ - eagle files or whatever you may have
  docs/       - include relevant datasheets here
  test/       - any test cases or other code to test parts of the system.
  myproject/  - since Arduino code must be in a directory of the same name
    myproject.ino
    ...

Một nhược điểm khác là tôi sẽ phải sao chép cùng một thư viện trên nhiều dự án. Ngoài ra, tôi không rõ nếu bạn chỉ đặt các thư viện CỦA BẠN ở đó hoặc cả thư viện của bên thứ 3?
jfpoilpret

Điểm đầu tiên: Đó không thực sự là một mặt trái, đó chỉ là tác dụng phụ của việc giữ các thư viện và nguồn dự án cùng nhau như bạn muốn với kiểm soát phiên bản. Điều gì nếu một dự án khác cần một phiên bản cập nhật của thư viện? Điều gì nếu bạn sửa đổi nó? Điểm thứ hai: cả hai sẽ hoạt động.
sachleen

1
Tôi không. Arduino IDE khá hạn chế theo nhiều cách. Bạn có thể muốn nhìn vào một môi trường tốt hơn để làm việc trong đó có hỗ trợ tốt hơn cho việc này. Mọi người đã tạo các tệp tùy chỉnh cho phép bạn nhập thư viện từ các nguồn khác.
sachleen

1
Đây không phải là một cách tốt để tổ chức các dự án theo quan điểm của giấy phép phần mềm. Nếu bạn đang bao gồm các thư viện của bên thứ ba trong dự án của bạn, có thể có các giấy phép khác nhau, thì bạn có thể vi phạm chúng ngay khi bạn bắt đầu chia sẻ tệp dự án. Các giấy phép nguồn mở khác nhau thường không tương thích với nhau.
asheeshr

3
@AsheeshR có tất cả các tệp của bạn trong một thư mục để IDE arduino không phàn nàn không phải là một cách tốt để tổ chức các dự án. Đó chỉ là một cách. Hãy đề xuất một giải pháp tốt hơn. Tôi không biết cái nào vẫn cho phép bạn sử dụng phần mềm Arduino.
sachleen

20

Các mô đun con Git cực kỳ mạnh mẽ khi tổ chức nhiều kho lưu trữ lồng nhau. Xử lý nhiều thư viện từ các nguồn khác nhau và thậm chí xử lý các phần trong dự án của riêng bạn có thể được lưu trữ ở các nguồn khác nhau trở nên dễ dàng với các mô đun con git.

Cấu trúc thư mục

Một cách để tổ chức các dự án của bạn sẽ là:

  • projectA - Danh mục phụ huynh

    • projectA - Thư mục mã nguồn chứa mã Arduino

      1. dự ánA.ino
      2. tiêu đề.h
      3. thực hiện.cpp
    • docs - thư mục tài liệu chính của bạn

    • sơ đồ - chúng có thể được duy trì riêng trên một repo Git riêng hoặc một phần của cùng một repo

    • libs - Điều này sẽ chứa các thư viện bên thứ ba của bạn.

      1. libA - Chúng có thể được duy trì như kho lưu trữ của bên thứ ba
      2. libC - ...
    • giấy phép

    • Sẵn sàng

    • Makefile - Cần thiết để xử lý các phụ thuộc trên các thư mục

Quy trình làm việc

Bạn sẽ tuân theo chu kỳ thay đổi thông thường của mình, thêm và cam kết khi có liên quan đến kho lưu trữ chính. Mọi thứ trở nên thú vị với các kho phụ.

Bạn có tùy chọn thêm một kho lưu trữ vào thư mục mẹ của kho lưu trữ chính của bạn. Điều này có nghĩa là bất kỳ phần nào trong cấu trúc thư mục của bạn, ví dụ như tài liệu, sơ đồ, v.v. đều có thể được duy trì như một kho lưu trữ riêng biệt và được cập nhật liên tục từ đó.

Bạn có thể làm điều này bằng cách sử dụng git submodule add <repo.git>lệnh. Để giữ cho nó cập nhật, bạn có thể sử dụng git submodule update <path>.

Khi nói đến việc duy trì nhiều thư viện của bên thứ ba trong kho lưu trữ của bạn để mỗi thư viện có thể được kiểm soát phiên bản hoặc mỗi thư viện có thể được cập nhật nếu cần, git mô đun lại giúp bạn tiết kiệm một ngày!

Để thêm repo của bên thứ ba vào libs , hãy sử dụng lệnh git submodule add <lib1.git> libs/lib1. Sau đó, để duy trì thư viện tại một điểm cố định trong chu kỳ phát hành, hãy kiểm tra thư viện và thực hiện cam kết. Để giữ cho thư viện cập nhật, sử dụng lệnh git submodule update <path>.

Giờ đây, bạn có thể duy trì nhiều kho lưu trữ trong một kho lưu trữ chính cũng như nhiều thư viện bên thứ ba trong các giai đoạn phát hành độc lập.

Phương pháp tiếp cận thư mục đơn

Mặc dù cách tiếp cận thư mục duy nhất là đơn giản nhất, nhưng không thể phiên bản kiểm soát các phần của thư mục mà không có nhiều đau đớn. Do đó, cách tiếp cận đơn giản không thể chứa được các kho khác nhau với các trạng thái khác nhau trong dự án.

Cách tiếp cận này cho phép duy trì nhiều kho lưu trữ nhưng cần phải có Makefile để xử lý quá trình biên dịch và liên kết.

Tùy thuộc vào mức độ phức tạp của dự án của bạn, phương pháp tối ưu có thể được chọn.


1
+1, nhưng chỉ là một sidenote: Submodules Git khá không ổn định và có khả năng theo dõi lỏng lẻo. Nó làm cho có sự khác biệt nếu bạn sử dụng một thư mục duy nhất hoặc bội số (như vendor, node_modules, vv). Git tham khảo chúng và theo dõi nó.
kaiser

"Sẽ không có gì khác biệt nếu bạn sử dụng một thư mục hoặc bội số (như nhà cung cấp, node_modules, v.v.)." Tôi không hiểu phần này. Bạn có thể giải thích?
asheeshr

17

Đây là cách cuối cùng tôi quyết định làm theo cho các dự án của tôi.

Arduino-CMake

Quyết định quan trọng đầu tiên tôi đưa ra là lựa chọn công cụ xây dựng có thể hoạt động cho môi trường của tôi (Windows) nhưng không giới hạn ở nó (tôi muốn các dự án của mình dễ dàng được người khác sử dụng lại).

Tôi đã thử nghiệm nhiều công cụ tạo mã nguồn mở Arduino khác nhau:

  • Guyzmo Makefile (được đề xuất bởi @zmo answer): đây chỉ là một Makefile tiêu chuẩn được làm thủ công cho các bản dựng Arduino; đây là một Unix Makefile nhưng có một cảng tốt của Unix làm cho Windows ; Tuy nhiên, thật không may, Makefile này chỉ hoạt động cho Unix, tất nhiên nó có thể được điều chỉnh cho Windows, nhưng tôi muốn có một công cụ hoạt động "vượt trội".
  • Arduino-Makefile (được đề xuất bởi câu trả lời @adnues): đây là một dự án tiên tiến hơn, dựa trên Unix Makefile, nhằm mục đích dễ dàng tái sử dụng bởi tất cả các dự án Arduino; nó được ghi nhận là hoạt động trên Mac, Linux và Windows, nhưng hỗ trợ Windows đã chứng minh sai trong các thử nghiệm đầu tiên của tôi (nhiều phụ thuộc vào shell Unix).
  • Graduino (không được đề xuất bởi bất kỳ câu trả lời): Công cụ xây dựng này được dựa trên nổi tiếng gradle công cụ xây dựng từ groovy thế giới; công cụ này có vẻ được thực hiện khá tốt nhưng đòi hỏi một số (ít) Groovy / gradle knowlegde, và chỉ có ít tài liệu; Tôi quyết định không đi theo nó vì gánh nặng của việc cài đặt Groovy và gradle chỉ cho nó (Tôi muốn tránh quá nhiều điều kiện tiên quyết cho những người muốn xây dựng dự án của tôi trên môi trường của họ).
  • Arduino-CMake (không được đề xuất bởi bất kỳ câu trả lời nào): đây có vẻ là điều tốt nhất, nó có một lịch sử lâu dài, có nhiều người ủng hộ và bảo trì, được ghi chép rất tốt, đi kèm với các ví dụ đơn giản và cũng có một vài bài đăng blog hướng dẫn tốt về Web, ví dụ ở đây ở đó . Nó dựa trên CMake , một "Tạo nền tảng chéo".

Tôi cũng đã tìm thấy ArduinoDevel , một công cụ xây dựng Arduino khác - trong đó tôi chưa thử nghiệm - có thể tạo tệp Unix Makefiles hoặc ant build.xml ; cái đó có vẻ thú vị nhưng hơi hạn chế về chức năng.

Cuối cùng tôi quyết định đi với Arduino-CMake :

  • thật dễ dàng để thiết lập: chỉ cần cài đặt CMake trên máy của bạn và sao chép Arduino-CMake trong một số thư mục có thể truy cập dễ dàng (thông qua các đường dẫn tương đối) từ các thư mục dự án của bạn.
  • các ví dụ hoạt động tốt cho tôi (chỉ cần làm theo các nhận xét trong CMakeLists.txttệp cấu hình để điều chỉnh các thuộc tính cần thiết cho môi trường của tôi, ví dụ như loại Arduino, cổng nối tiếp)
  • bạn có thể tổ chức các dự án của bạn theo bất kỳ cách nào bạn muốn
  • nó có thể tạo các tệp cấu hình cho các công cụ xây dựng khác nhau ngoài đó (tôi mới chỉ thử nghiệm các Makefile Unix ), bao gồm các dự án Eclipse .
  • trong phần tạo được tạo, một số mục tiêu được tạo để hỗ trợ:

    • xây dựng thư viện
    • xây dựng chương trình
    • chương trình tải lên bảng
    • ra mắt màn hình nối tiếp
    • và một vài người khác tôi chưa thử nghiệm

Kết cấu dự án

Vì Arduono-CMake không áp đặt bất kỳ cấu trúc thư mục nào cho dự án của bạn, bạn có thể chọn cấu trúc phù hợp với mình nhất.

Đây là những gì tôi đã thực hiện cá nhân (điều đó vẫn đòi hỏi phải tinh chỉnh thêm, nhưng tôi hài lòng với nó bây giờ):

nhập mô tả hình ảnh ở đây

Tôi đã quyết định đặt tất cả các dự án của mình vào một arduino-stuffthư mục chung (mà tôi cam kết với github nói chung, tôi biết tôi có thể sử dụng các mô đun con git cho một tổ chức tốt hơn trên github, nhưng chưa có thời gian để kiểm tra điều đó).

arduino-stuff có nội dung như sau:

  • build: đó là một thư mục trong đó cmake và make sẽ tạo ra tất cả nội dung của chúng (tệp tạo tệp, bộ đệm, tệp đối tượng ...); cái này không được cam kết với github
  • cmake: cái đó chỉ là một bản sao (chưa sửa đổi) của thư mục cmake Arduino-CMake . Cái này được cài đặt trên github để dễ dàng hơn cho những người muốn xây dựng dự án của tôi
  • CMakeLists.txt: đó là cấu hình CMake "toàn cầu" khai báo tất cả các giá trị mặc định cho môi trường của tôi (bảng, cổng nối tiếp) và danh sách các thư mục con xây dựng mục tiêu xây dựng
  • TaskManager: đây là dự án đầu tiên của tôi dựa trên Arduino-CMake, đây là một thư viện với các ví dụ; idrectory này cũng chứa một CMakeLists.txtmục tiêu cho biết các mục tiêu cho dự án

Những điểm cần cải thiện

Các giải pháp hiện tại là không hoàn hảo mặc dù. Trong số các cải tiến tôi thấy (đó là dự án Arduino-CMake bao gồm những cải tiến này nếu chúng thấy phù hợp):

  • Tính năng sao chép thư mục thư viện từ dự án hiện tại sang thư mục thư viện Arduino
  • Tính năng tải thư viện lên github
  • Tính năng tải xuống thư viện từ github

2
Bạn đã thử PlatformIO chưa? Nó có thể không xuất hiện khi bạn hỏi câu hỏi này .. platformio.org
ohhorob 13/07/2016

4
MyProject
|_MyProject
  |_MyProject.ino
  |_data
  |  |_documentation 
  |  |_PCB
  |  |_schematics
  |_src
     |_MyProjectLibrary1
     |_ThirdPartyLibrary

Thư mục MyProject (kho lưu trữ gốc)

Lý do tôi đề xuất MyProjectthư mục gốc có vẻ dư thừa là bạn đã đề cập bằng GitHub. Khi bạn tải xuống (chứ không phải sao chép) nội dung của kho lưu trữ GitHub, tên nhánh hoặc thẻ được gắn vào tên kho lưu trữ (ví dụ:MyProject-master). Arduino IDE yêu cầu tên thư mục phác thảo khớp với tên tệp phác thảo. Nếu bạn mở tệp .ino trong thư mục không khớp với tên phác thảo, Arduino IDE sẽ nhắc bạn tạo thư mục phác thảo có tên thích hợp và di chuyển bản phác thảo vào thư mục đó. Ngoài ra, đây không phải là một trải nghiệm ban đầu tốt cho người dùng, vấn đề lớn hơn là Arduino IDE có thể không sao chép tất cả các tệp liên quan khác vào thư mục mới tạo, có thể khiến chương trình không còn biên dịch được nữa. Bằng cách đặt bản phác thảo vào thư mục con, bạn tránh được GitHub thay đổi tên của thư mục phác thảo.

Nếu điều tên tệp GitHub không phải là vấn đề đối với bạn thì thư mục gốc dự phòng là không cần thiết.

thư mục dữ liệu

Tôi khuyên bạn nên sử dụng datathư mục con cho các tệp không phải mã của mình vì Arduino IDE có cách xử lý đặc biệt đối với các thư mục con của tên đó. Họ được sao chép vào vị trí mới khi bạn làm một File> Save As ... . Thư mục con của bất kỳ tên nào khác là không.

thư mục src

Thư srcmục con có thuộc tính đặc biệt cho phép biên dịch đệ quy . Điều này có nghĩa là bạn có thể để các thư viện trong thư mục đó và đưa chúng vào bản phác thảo của bạn như thế này:

#include "src/MyProjectLibrary1/MyProjectLibrary1.h"
#include "src/ThirdPartyLibrary/ThirdPartyLibrary.h"

Các Arduino 1,5 Library cấu trúc thư mục định dạng cũng được hỗ trợ, bạn chỉ cần điều chỉnh của bạn #includebáo cáo cho phù hợp.

Lưu ý rằng chỉ có Arduino IDE 1.6.10 (arduino-builder 1.3.19) và mới hơn hỗ trợ biên dịch phác thảo đệ quy.

Thật không may, một số thư viện sử dụng #includecú pháp không chính xác cho tệp cục bộ bao gồm (ví dụ #include <ThirdPartyLibrary.h>thay vì #include "ThirdPartyLibrary.h"). Điều này vẫn hoạt động khi thư viện được cài đặt vào một trong các librariesthư mục Arduino nhưng không hoạt động khi thư viện được gói cùng với bản phác thảo. Vì vậy, một số thư viện có thể yêu cầu chỉnh sửa nhỏ để sử dụng theo cách này.

Tôi rất thích cách này thay thế cho việc bỏ tất cả các tệp thư viện vào thư mục gốc của thư mục phác thảo vì nó lộn xộn và mọi tệp thư viện sẽ được hiển thị trong Arduino IDE dưới dạng các tab khi bạn mở bản phác thảo (Tất nhiên là bất kỳ tệp nguồn nào bạn làm muốn có thể chỉnh sửa từ Arduino IDE nên được đặt trong thư mục gốc phác thảo).

Có thể sử dụng các thư viện đi kèm tại chỗ cũng phù hợp với mục tiêu khác của bạn:

gửi cho một người bạn để anh ta xây dựng dễ dàng nhất có thể

Loại bỏ yêu cầu để cài đặt thủ công các thư viện làm cho dự án dễ sử dụng hơn nhiều.

Điều này cũng sẽ tránh mọi khả năng xung đột với các phiên bản khác của tệp thư viện cùng tên có thể được cài đặt trước đó.


3

Bạn có thể sử dụng makefile https://github.com/sudar/Arduino-Makefile để biên dịch mã Arduino. Bạn không nhất thiết cần IDE.


1
Tôi đã thử nhưng thật không may, nó sẽ chỉ hoạt động trên các máy Unix và tôi cần hỗ trợ Windows. Hiện tại tôi đang đánh giá một dự án khác dựa trên CMake nhưng tôi chưa thực hiện được. Tôi sẽ đăng câu trả lời khi tôi quyết định chọn một công cụ.
jfpoilpret

0

Có lẽ thực sự muộn trò chơi nhưng đó là một câu hỏi đủ phổ biến để trả lời bằng các phương pháp hơi khác so với các phương pháp đã được đăng.

Nếu bạn cần duy trì khả năng tương thích trực tiếp với Arduino IDE, bạn có thể sử dụng một cái gì đó giống như cái tôi đã nêu ở đây:

https://gitlab.com/mikealger/ExampleArduinoProjectStr struct / tree / master /ExSSketchBook

Tôi đã dựa trên hầu hết các ghi chú của Arduino - Cấu trúc dự án và quy trình xây dựng , và một số mẹo tôi đã chọn trong nhiều năm qua.

Tôi thực sự không biết tại sao điều này rất khó tìm thấy trực tiếp qua các trang Arduino, có vẻ như thật ngớ ngẩn đến từ một nền tảng bán chuyên nghiệp mà quá trình xây dựng rất khó hiểu.

điều may mắn nhất ngoài kia


Liên kết gitlab dường như bị phá vỡ
Greenonline

lẻ nó làm việc với các liên kết trực tiếp? tức là gitlab.com/mikealger/ExampleArduinoProjectStr struct
Mike Alger

Trên thực tế cả hai liên kết đều hoạt động trong Firefox, nhưng không hoạt động trong phiên bản Chrome lỗi thời 49.0.2623.112 (64-bit) của tôi. Không có gì phải lo lắng, tôi đoán vậy. :-)
Greenonline
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.