diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89b8bd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Mac OS X +*.DS_Store + + +# Xcode +*.pbxuser +*.mode2v3 +*.mode1v3 +*.perspective +*.perspectivev3 +*.xcuserstate +xcuserdata/ + +## Generic Files To Ignore +*~ +*.swp +*.out diff --git a/Documentation/AddFramework.png b/Documentation/AddFramework.png new file mode 100644 index 0000000..b58a00d Binary files /dev/null and b/Documentation/AddFramework.png differ diff --git a/Documentation/RunScript.png b/Documentation/RunScript.png new file mode 100644 index 0000000..3237e39 Binary files /dev/null and b/Documentation/RunScript.png differ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..85c7c69 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,636 @@ +# GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 [Free Software Foundation, Inc.](http://fsf.org/) + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +## Preamble + +The GNU General Public License is a free, copyleft license for software and +other kinds of works. + +The licenses for most software and other practical works are designed to take +away your freedom to share and change the works. By contrast, the GNU General +Public License is intended to guarantee your freedom to share and change all +versions of a program--to make sure it remains free software for all its users. +We, the Free Software Foundation, use the GNU General Public License for most +of our software; it applies also to any other work released this way by its +authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for them if you wish), that you +receive source code or can get it if you want it, that you can change the +software or use pieces of it in new free programs, and that you know you can do +these things. + +To protect your rights, we need to prevent others from denying you these rights +or asking you to surrender the rights. Therefore, you have certain +responsibilities if you distribute copies of the software, or if you modify it: +responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must pass on to the recipients the same freedoms that you received. +You must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: + + 1. assert copyright on the software, and + 2. offer you this License giving you legal permission to copy, distribute + and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that +there is no warranty for this free software. For both users' and authors' sake, +the GPL requires that modified versions be marked as changed, so that their +problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified +versions of the software inside them, although the manufacturer can do so. This +is fundamentally incompatible with the aim of protecting users' freedom to +change the software. The systematic pattern of such abuse occurs in the area of +products for individuals to use, which is precisely where it is most +unacceptable. Therefore, we have designed this version of the GPL to prohibit +the practice for those products. If such problems arise substantially in other +domains, we stand ready to extend this provision to those domains in future +versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States +should not allow patents to restrict development and use of software on +general-purpose computers, but in those that do, we wish to avoid the special +danger that patents applied to a free program could make it effectively +proprietary. To prevent this, the GPL assures that patents cannot be used to +render the program non-free. + +The precise terms and conditions for copying, distribution and modification +follow. + +## TERMS AND CONDITIONS + +### 0. Definitions. + +*This License* refers to version 3 of the GNU General Public License. + +*Copyright* also means copyright-like laws that apply to other kinds of works, +such as semiconductor masks. + +*The Program* refers to any copyrightable work licensed under this License. +Each licensee is addressed as *you*. *Licensees* and *recipients* may be +individuals or organizations. + +To *modify* a work means to copy from or adapt all or part of the work in a +fashion requiring copyright permission, other than the making of an exact copy. +The resulting work is called a *modified version* of the earlier work or a work +*based on* the earlier work. + +A *covered work* means either the unmodified Program or a work based on the +Program. + +To *propagate* a work means to do anything with it that, without permission, +would make you directly or secondarily liable for infringement under applicable +copyright law, except executing it on a computer or modifying a private copy. +Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To *convey* a work means any kind of propagation that enables other parties to +make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays *Appropriate Legal Notices* to the +extent that it includes a convenient and prominently visible feature that + + 1. displays an appropriate copyright notice, and + 2. tells the user that there is no warranty for the work (except to the + extent that warranties are provided), that licensees may convey the work + under this License, and how to view a copy of this License. + +If the interface presents a list of user commands or options, such as a menu, a +prominent item in the list meets this criterion. + +### 1. Source Code. + +The *source code* for a work means the preferred form of the work for making +modifications to it. *Object code* means any non-source form of a work. + +A *Standard Interface* means an interface that either is an official standard +defined by a recognized standards body, or, in the case of interfaces specified +for a particular programming language, one that is widely used among developers +working in that language. + +The *System Libraries* of an executable work include anything, other than the +work as a whole, that (a) is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and (b) serves only +to enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code +form. A *Major Component*, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on +which the executable work runs, or a compiler used to produce the work, or an +object code interpreter used to run it. + +The *Corresponding Source* for a work in object code form means all the source +code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. +However, it does not include the work's System Libraries, or general-purpose +tools or generally available free programs which are used unmodified in +performing those activities but which are not part of the work. For example, +Corresponding Source includes interface definition files associated with source +files for the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, such as +by intimate data communication or control flow between those subprograms and +other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +### 2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright on +the Program, and are irrevocable provided the stated conditions are met. This +License explicitly affirms your unlimited permission to run the unmodified +Program. The output from running a covered work is covered by this License only +if the output, given its content, constitutes a covered work. This License +acknowledges your rights of fair use or other equivalent, as provided by +copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey +covered works to others for the sole purpose of having them make modifications +exclusively for you, or provide you with facilities for running those works, +provided that you comply with the terms of this License in conveying all +material for which you do not control copyright. Those thus making or running +the covered works for you must do so exclusively on your behalf, under your +direction and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes it +unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure +under any applicable law fulfilling obligations under article 11 of the WIPO +copyright treaty adopted on 20 December 1996, or similar laws prohibiting or +restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention is +effected by exercising rights under this License with respect to the covered +work, and you disclaim any intention to limit operation or modification of the +work as a means of enforcing, against the work's users, your or third parties' +legal rights to forbid circumvention of technological measures. + +### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive it, +in any medium, provided that you conspicuously and appropriately publish on +each copy an appropriate copyright notice; keep intact all notices stating that +this License and any non-permissive terms added in accord with section 7 apply +to the code; keep intact all notices of the absence of any warranty; and give +all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may +offer support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce it +from the Program, in the form of source code under the terms of section 4, +provided that you also meet all of these conditions: + + - a) The work must carry prominent notices stating that you modified it, and + giving a relevant date. + - b) The work must carry prominent notices stating that it is released under + this License and any conditions added under section 7. This requirement + modifies the requirement in section 4 to *keep intact all notices*. + - c) You must license the entire work, as a whole, under this License to + anyone who comes into possession of a copy. This License will therefore + apply, along with any applicable section 7 additional terms, to the whole + of the work, and all its parts, regardless of how they are packaged. This + License gives no permission to license the work in any other way, but it + does not invalidate such permission if you have separately received it. + - d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your work need + not make them do so. + +A compilation of a covered work with other separate and independent works, +which are not by their nature extensions of the covered work, and which are not +combined with it such as to form a larger program, in or on a volume of a +storage or distribution medium, is called an *aggregate* if the compilation and +its resulting copyright are not used to limit the access or legal rights of the +compilation's users beyond what the individual works permit. Inclusion of a +covered work in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections 4 +and 5, provided that you also convey the machine-readable Corresponding Source +under the terms of this License, in one of these ways: + + - a) Convey the object code in, or embodied in, a physical product (including + a physical distribution medium), accompanied by the Corresponding Source + fixed on a durable physical medium customarily used for software + interchange. + - b) Convey the object code in, or embodied in, a physical product (including + a physical distribution medium), accompanied by a written offer, valid for + at least three years and valid for as long as you offer spare parts or + customer support for that product model, to give anyone who possesses the + object code either + 1. a copy of the Corresponding Source for all the software in the product + that is covered by this License, on a durable physical medium + customarily used for software interchange, for a price no more than your + reasonable cost of physically performing this conveying of source, or + 2. access to copy the Corresponding Source from a network server at no + charge. + - c) Convey individual copies of the object code with a copy of the written + offer to provide the Corresponding Source. This alternative is allowed only + occasionally and noncommercially, and only if you received the object code + with such an offer, in accord with subsection 6b. + - d) Convey the object code by offering access from a designated place + (gratis or for a charge), and offer equivalent access to the Corresponding + Source in the same way through the same place at no further charge. You + need not require recipients to copy the Corresponding Source along with the + object code. If the place to copy the object code is a network server, the + Corresponding Source may be on a different server operated by you or a + third party) that supports equivalent copying facilities, provided you + maintain clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the Corresponding + Source, you remain obligated to ensure that it is available for as long as + needed to satisfy these requirements. + - e) Convey the object code using peer-to-peer transmission, provided you + inform other peers where the object code and Corresponding Source of the + work are being offered to the general public at no charge under subsection + 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A *User Product* is either + + 1. a *consumer product*, which means any tangible personal property which is + normally used for personal, family, or household purposes, or + 2. anything designed or sold for incorporation into a dwelling. + +In determining whether a product is a consumer product, doubtful cases shall be +resolved in favor of coverage. For a particular product received by a +particular user, *normally used* refers to a typical or common use of that +class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, +the product. A product is a consumer product regardless of whether the product +has substantial commercial, industrial or non-consumer uses, unless such uses +represent the only significant mode of use of the product. + +*Installation Information* for a User Product means any methods, procedures, +authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified +version of its Corresponding Source. The information must suffice to ensure +that the continued functioning of the modified object code is in no case +prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as part of a +transaction in which the right of possession and use of the User Product is +transferred to the recipient in perpetuity or for a fixed term (regardless of +how the transaction is characterized), the Corresponding Source conveyed under +this section must be accompanied by the Installation Information. But this +requirement does not apply if neither you nor any third party retains the +ability to install modified object code on the User Product (for example, the +work has been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates for a +work that has been modified or installed by the recipient, or for the User +Product in which it has been modified or installed. Access to a network may be +denied when the modification itself materially and adversely affects the +operation of the network or violates the rules and protocols for communication +across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord +with this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require +no special password or key for unpacking, reading or copying. + +### 7. Additional Terms. + +*Additional permissions* are terms that supplement the terms of this License by +making exceptions from one or more of its conditions. Additional permissions +that are applicable to the entire Program shall be treated as though they were +included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may +be used separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when +you modify the work.) You may place additional permissions on material, added +by you to a covered work, for which you have or can give appropriate copyright +permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + + - a) Disclaiming warranty or limiting liability differently from the terms of + sections 15 and 16 of this License; or + - b) Requiring preservation of specified reasonable legal notices or author + attributions in that material or in the Appropriate Legal Notices displayed + by works containing it; or + - c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in reasonable + ways as different from the original version; or + - d) Limiting the use for publicity purposes of names of licensors or authors + of the material; or + - e) Declining to grant rights under trademark law for use of some trade + names, trademarks, or service marks; or + - f) Requiring indemnification of licensors and authors of that material by + anyone who conveys the material (or modified versions of it) with + contractual assumptions of liability to the recipient, for any liability + that these contractual assumptions directly impose on those licensors and + authors. + +All other non-permissive additional terms are considered *further restrictions* +within the meaning of section 10. If the Program as you received it, or any +part of it, contains a notice stating that it is governed by this License along +with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or +conveying under this License, you may add to a covered work material governed +by the terms of that license document, provided that the further restriction +does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, +in the relevant source files, a statement of the additional terms that apply to +those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements +apply either way. + +### 8. Termination. + +You may not propagate or modify a covered work except as expressly provided +under this License. Any attempt otherwise to propagate or modify it is void, +and will automatically terminate your rights under this License (including any +patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated + + - a) provisionally, unless and until the copyright holder explicitly and + finally terminates your license, and + - b) permanently, if the copyright holder fails to notify you of the + violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated +permanently if the copyright holder notifies you of the violation by some +reasonable means, this is the first time you have received notice of violation +of this License (for any work) from that copyright holder, and you cure the +violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. If +your rights have been terminated and not permanently reinstated, you do not +qualify to receive new licenses for the same material under section 10. + +### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy +of the Program. Ancillary propagation of a covered work occurring solely as a +consequence of using peer-to-peer transmission to receive a copy likewise does +not require acceptance. However, nothing other than this License grants you +permission to propagate or modify any covered work. These actions infringe +copyright if you do not accept this License. Therefore, by modifying or +propagating a covered work, you indicate your acceptance of this License to do +so. + +### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a +license from the original licensors, to run, modify and propagate that work, +subject to this License. You are not responsible for enforcing compliance by +third parties with this License. + +An *entity transaction* is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered work +results from an entity transaction, each party to that transaction who receives +a copy of the work also receives whatever licenses to the work the party's +predecessor in interest had or could give under the previous paragraph, plus a +right to possession of the Corresponding Source of the work from the +predecessor in interest, if the predecessor has it or can get it with +reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights +granted or affirmed under this License. For example, you may not impose a +license fee, royalty, or other charge for exercise of rights granted under this +License, and you may not initiate litigation (including a cross-claim or +counterclaim in a lawsuit) alleging that any patent claim is infringed by +making, using, selling, offering for sale, or importing the Program or any +portion of it. + +### 11. Patents. + +A *contributor* is a copyright holder who authorizes use under this License of +the Program or a work on which the Program is based. The work thus licensed is +called the contributor's *contributor version*. + +A contributor's *essential patent claims* are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, +that would be infringed by some manner, permitted by this License, of making, +using, or selling its contributor version, but do not include claims that would +be infringed only as a consequence of further modification of the contributor +version. For purposes of this definition, *control* includes the right to grant +patent sublicenses in a manner consistent with the requirements of this +License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent +license under the contributor's essential patent claims, to make, use, sell, +offer for sale, import and otherwise run, modify and propagate the contents of +its contributor version. + +In the following three paragraphs, a *patent license* is any express agreement +or commitment, however denominated, not to enforce a patent (such as an express +permission to practice a patent or covenant not to sue for patent +infringement). To *grant* such a patent license to a party means to make such +an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of +charge and under the terms of this License, through a publicly available +network server or other readily accessible means, then you must either + + 1. cause the Corresponding Source to be so available, or + 2. arrange to deprive yourself of the benefit of the patent license for this + particular work, or + 3. arrange, in a manner consistent with the requirements of this License, to + extend the patent license to downstream recipients. + +*Knowingly relying* means you have actual knowledge that, but for the patent +license, your conveying the covered work in a country, or your recipient's use +of the covered work in a country, would infringe one or more identifiable +patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a +patent license to some of the parties receiving the covered work authorizing +them to use, propagate, modify or convey a specific copy of the covered work, +then the patent license you grant is automatically extended to all recipients +of the covered work and works based on it. + +A patent license is *discriminatory* if it does not include within the scope of +its coverage, prohibits the exercise of, or is conditioned on the non-exercise +of one or more of the rights that are specifically granted under this License. +You may not convey a covered work if you are a party to an arrangement with a +third party that is in the business of distributing software, under which you +make payment to the third party based on the extent of your activity of +conveying the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory patent +license + + - a) in connection with copies of the covered work conveyed by you (or copies + made from those copies), or + - b) primarily for and in connection with specific products or compilations + that contain the covered work, unless you entered into that arrangement, or + that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to +you under applicable patent law. + +### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not excuse +you from the conditions of this License. If you cannot convey a covered work so +as to satisfy simultaneously your obligations under this License and any other +pertinent obligations, then as a consequence you may not convey it at all. For +example, if you agree to terms that obligate you to collect a royalty for +further conveying from those to whom you convey the Program, the only way you +could satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have permission to +link or combine any covered work with a work licensed under version 3 of the +GNU Affero General Public License into a single combined work, and to convey +the resulting work. The terms of this License will continue to apply to the +part which is the covered work, but the special requirements of the GNU Affero +General Public License, section 13, concerning interaction through a network +will apply to the combination as such. + +### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program specifies +that a certain numbered version of the GNU General Public License *or any later +version* applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by +the Free Software Foundation. If the Program does not specify a version number +of the GNU General Public License, you may choose any version ever published by +the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the +GNU General Public License can be used, that proxy's public statement of +acceptance of a version permanently authorizes you to choose that version for +the Program. + +Later license versions may give you additional or different permissions. +However, no additional obligations are imposed on any author or copyright +holder as a result of your choosing to follow a later version. + +### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE +LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER +PARTIES PROVIDE THE PROGRAM *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE +THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot +be given local legal effect according to their terms, reviewing courts shall +apply local law that most closely approximates an absolute waiver of all civil +liability in connection with the Program, unless a warranty or assumption of +liability accompanies a copy of the Program in return for a fee. + +## END OF TERMS AND CONDITIONS ### + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively state the exclusion +of warranty; and each file should have at least the *copyright* line and a +pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like +this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w` and `show c` should show the appropriate +parts of the General Public License. Of course, your program's commands might +be different; for a GUI interface, you would use an *about box*. + +You should also get your employer (if you work as a programmer) or school, if +any, to sign a *copyright disclaimer* for the program, if necessary. For more +information on this, and how to apply and follow the GNU GPL, see +[http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). + +The GNU General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may consider +it more useful to permit linking proprietary applications with the library. If +this is what you want to do, use the GNU Lesser General Public License instead +of this License. But first, please read +[http://www.gnu.org/philosophy/why-not-lgpl.html](http://www.gnu.org/philosophy/why-not-lgpl.html). diff --git a/MadgwickAHRS.xcodeproj/project.pbxproj b/MadgwickAHRS.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6e4b217 --- /dev/null +++ b/MadgwickAHRS.xcodeproj/project.pbxproj @@ -0,0 +1,415 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + D3623B7B1D0C5B9B0015FFF3 /* CoreMotionMadgwickTestDriverDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = D3623B7A1D0C568C0015FFF3 /* CoreMotionMadgwickTestDriverDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D38B14921D0B89DD00C88130 /* MadgwickAHRS.h in Headers */ = {isa = PBXBuildFile; fileRef = D38B14911D0B89DD00C88130 /* MadgwickAHRS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D38B14991D0B89DD00C88130 /* MadgwickAHRS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D38B148E1D0B89DD00C88130 /* MadgwickAHRS.framework */; }; + D38B149E1D0B89DD00C88130 /* MadgwickAHRSTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D38B149D1D0B89DD00C88130 /* MadgwickAHRSTests.m */; }; + D38B14AA1D0B8A0000C88130 /* MadgwickSensorFusion.h in Headers */ = {isa = PBXBuildFile; fileRef = D38B14A81D0B8A0000C88130 /* MadgwickSensorFusion.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D38B14AB1D0B8A0000C88130 /* MadgwickSensorFusion.m in Sources */ = {isa = PBXBuildFile; fileRef = D38B14A91D0B8A0000C88130 /* MadgwickSensorFusion.m */; }; + D38B14AE1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = D38B14AC1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D38B14AF1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = D38B14AD1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + D38B149A1D0B89DD00C88130 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D38B14851D0B89DD00C88130 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D38B148D1D0B89DD00C88130; + remoteInfo = MadgwickAHRS; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + D3623B7A1D0C568C0015FFF3 /* CoreMotionMadgwickTestDriverDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreMotionMadgwickTestDriverDelegate.h; sourceTree = ""; }; + D38B148E1D0B89DD00C88130 /* MadgwickAHRS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MadgwickAHRS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D38B14911D0B89DD00C88130 /* MadgwickAHRS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MadgwickAHRS.h; sourceTree = ""; }; + D38B14931D0B89DD00C88130 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D38B14981D0B89DD00C88130 /* MadgwickAHRSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MadgwickAHRSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D38B149D1D0B89DD00C88130 /* MadgwickAHRSTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MadgwickAHRSTests.m; sourceTree = ""; }; + D38B149F1D0B89DD00C88130 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D38B14A81D0B8A0000C88130 /* MadgwickSensorFusion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MadgwickSensorFusion.h; sourceTree = ""; }; + D38B14A91D0B8A0000C88130 /* MadgwickSensorFusion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MadgwickSensorFusion.m; sourceTree = ""; }; + D38B14AC1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreMotionMadgwickTestDriver.h; sourceTree = ""; }; + D38B14AD1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreMotionMadgwickTestDriver.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D38B148A1D0B89DD00C88130 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D38B14951D0B89DD00C88130 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D38B14991D0B89DD00C88130 /* MadgwickAHRS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D38B14841D0B89DD00C88130 = { + isa = PBXGroup; + children = ( + D38B14901D0B89DD00C88130 /* MadgwickAHRS */, + D38B149C1D0B89DD00C88130 /* MadgwickAHRSTests */, + D38B148F1D0B89DD00C88130 /* Products */, + ); + sourceTree = ""; + }; + D38B148F1D0B89DD00C88130 /* Products */ = { + isa = PBXGroup; + children = ( + D38B148E1D0B89DD00C88130 /* MadgwickAHRS.framework */, + D38B14981D0B89DD00C88130 /* MadgwickAHRSTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + D38B14901D0B89DD00C88130 /* MadgwickAHRS */ = { + isa = PBXGroup; + children = ( + D38B14AC1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.h */, + D38B14AD1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.m */, + D3623B7A1D0C568C0015FFF3 /* CoreMotionMadgwickTestDriverDelegate.h */, + D38B14931D0B89DD00C88130 /* Info.plist */, + D38B14911D0B89DD00C88130 /* MadgwickAHRS.h */, + D38B14A81D0B8A0000C88130 /* MadgwickSensorFusion.h */, + D38B14A91D0B8A0000C88130 /* MadgwickSensorFusion.m */, + ); + path = MadgwickAHRS; + sourceTree = ""; + }; + D38B149C1D0B89DD00C88130 /* MadgwickAHRSTests */ = { + isa = PBXGroup; + children = ( + D38B149D1D0B89DD00C88130 /* MadgwickAHRSTests.m */, + D38B149F1D0B89DD00C88130 /* Info.plist */, + ); + path = MadgwickAHRSTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D38B148B1D0B89DD00C88130 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D38B14921D0B89DD00C88130 /* MadgwickAHRS.h in Headers */, + D38B14AE1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.h in Headers */, + D3623B7B1D0C5B9B0015FFF3 /* CoreMotionMadgwickTestDriverDelegate.h in Headers */, + D38B14AA1D0B8A0000C88130 /* MadgwickSensorFusion.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D38B148D1D0B89DD00C88130 /* MadgwickAHRS */ = { + isa = PBXNativeTarget; + buildConfigurationList = D38B14A21D0B89DD00C88130 /* Build configuration list for PBXNativeTarget "MadgwickAHRS" */; + buildPhases = ( + D38B14891D0B89DD00C88130 /* Sources */, + D38B148A1D0B89DD00C88130 /* Frameworks */, + D38B148B1D0B89DD00C88130 /* Headers */, + D38B148C1D0B89DD00C88130 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MadgwickAHRS; + productName = MadgwickAHRS; + productReference = D38B148E1D0B89DD00C88130 /* MadgwickAHRS.framework */; + productType = "com.apple.product-type.framework"; + }; + D38B14971D0B89DD00C88130 /* MadgwickAHRSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D38B14A51D0B89DD00C88130 /* Build configuration list for PBXNativeTarget "MadgwickAHRSTests" */; + buildPhases = ( + D38B14941D0B89DD00C88130 /* Sources */, + D38B14951D0B89DD00C88130 /* Frameworks */, + D38B14961D0B89DD00C88130 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D38B149B1D0B89DD00C88130 /* PBXTargetDependency */, + ); + name = MadgwickAHRSTests; + productName = MadgwickAHRSTests; + productReference = D38B14981D0B89DD00C88130 /* MadgwickAHRSTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D38B14851D0B89DD00C88130 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0730; + ORGANIZATIONNAME = Softwarenerd; + TargetAttributes = { + D38B148D1D0B89DD00C88130 = { + CreatedOnToolsVersion = 7.3.1; + }; + D38B14971D0B89DD00C88130 = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = D38B14881D0B89DD00C88130 /* Build configuration list for PBXProject "MadgwickAHRS" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D38B14841D0B89DD00C88130; + productRefGroup = D38B148F1D0B89DD00C88130 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D38B148D1D0B89DD00C88130 /* MadgwickAHRS */, + D38B14971D0B89DD00C88130 /* MadgwickAHRSTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D38B148C1D0B89DD00C88130 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D38B14961D0B89DD00C88130 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D38B14891D0B89DD00C88130 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D38B14AB1D0B8A0000C88130 /* MadgwickSensorFusion.m in Sources */, + D38B14AF1D0B8C7D00C88130 /* CoreMotionMadgwickTestDriver.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D38B14941D0B89DD00C88130 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D38B149E1D0B89DD00C88130 /* MadgwickAHRSTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + D38B149B1D0B89DD00C88130 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D38B148D1D0B89DD00C88130 /* MadgwickAHRS */; + targetProxy = D38B149A1D0B89DD00C88130 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + D38B14A01D0B89DD00C88130 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + D38B14A11D0B89DD00C88130 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + D38B14A31D0B89DD00C88130 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = MadgwickAHRS/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.MadgwickAHRS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + D38B14A41D0B89DD00C88130 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = MadgwickAHRS/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.MadgwickAHRS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + D38B14A61D0B89DD00C88130 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = MadgwickAHRSTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.MadgwickAHRSTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + D38B14A71D0B89DD00C88130 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = MadgwickAHRSTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.MadgwickAHRSTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D38B14881D0B89DD00C88130 /* Build configuration list for PBXProject "MadgwickAHRS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D38B14A01D0B89DD00C88130 /* Debug */, + D38B14A11D0B89DD00C88130 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D38B14A21D0B89DD00C88130 /* Build configuration list for PBXNativeTarget "MadgwickAHRS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D38B14A31D0B89DD00C88130 /* Debug */, + D38B14A41D0B89DD00C88130 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D38B14A51D0B89DD00C88130 /* Build configuration list for PBXNativeTarget "MadgwickAHRSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D38B14A61D0B89DD00C88130 /* Debug */, + D38B14A71D0B89DD00C88130 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D38B14851D0B89DD00C88130 /* Project object */; +} diff --git a/MadgwickAHRS.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MadgwickAHRS.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..d031cf8 --- /dev/null +++ b/MadgwickAHRS.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/MadgwickAHRS.xcodeproj/xcshareddata/xcschemes/MadgwickAHRS.xcscheme b/MadgwickAHRS.xcodeproj/xcshareddata/xcschemes/MadgwickAHRS.xcscheme new file mode 100644 index 0000000..5eee765 --- /dev/null +++ b/MadgwickAHRS.xcodeproj/xcshareddata/xcschemes/MadgwickAHRS.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MadgwickAHRS/CoreMotionMadgwickTestDriver.h b/MadgwickAHRS/CoreMotionMadgwickTestDriver.h new file mode 100644 index 0000000..76d9d4f --- /dev/null +++ b/MadgwickAHRS/CoreMotionMadgwickTestDriver.h @@ -0,0 +1,30 @@ +// +// CoreMotionMadgwickTestDriver.h +// MadgwickAHRS +// +// Created by Brian Lambert on 6/7/16. +// +// CoreMotionMadgwickTestDriver uses CoreMotion device motion updates as IMU input data to +// MadgwickAHRS. It then uses the resulting quaternion to calculate Euler angles. +// + +#import +#import "CoreMotionMadgwickTestDriverDelegate.h" + +// CoreMotionMadgwickTestDriver interface. +@interface CoreMotionMadgwickTestDriver : NSObject + +// Properties. +@property (nonatomic, weak, nullable) id delegate; + +// Class initializer. +- (nullable instancetype)initSampleFrequencyHz:(float)sampleFrequencyHz + beta:(float)beta; + +// Starts the driver. +- (void)start; + +// Stop the driver. +- (void)stop; + +@end diff --git a/MadgwickAHRS/CoreMotionMadgwickTestDriver.m b/MadgwickAHRS/CoreMotionMadgwickTestDriver.m new file mode 100644 index 0000000..b4ebc96 --- /dev/null +++ b/MadgwickAHRS/CoreMotionMadgwickTestDriver.m @@ -0,0 +1,197 @@ +// +// CoreMotionMadgwickTestDriver.m +// MadgwickAHRS +// +// Created by Brian Lambert on 6/7/16. +// +// CoreMotionMadgwickTestDriver uses CoreMotion device motion updates as IMU input data to +// MadgwickAHRS. It then uses the resulting quaternion to calculate Euler angles. +// + +#import +#import "CoreMotionMadgwickTestDriver.h" +#import "MadgwickSensorFusion.h" + +// Normalizes. +float NormalizeAngle(float angle) +{ + return fmodf(angle + 360.0f, 360.0f); +} + +// Converts radians to degrees. +static inline float RadiansToDegrees(float radians) +{ + return radians * 180.0f / M_PI; +} + +// Calculates Euler angles from quaternion. +void CalculateEulerAnglesFromQuaternion(float q0, float q1, float q2, float q3, float * roll, float * pitch, float * yaw) +{ + const float w2 = q0 * q0; + const float x2 = q1 * q1; + const float y2 = q2 * q2; + const float z2 = q3 * q3; + const float unitLength = w2 + x2 + y2 + z2; // Normalised == 1, otherwise correction divisor. + const float abcd = q0 * q1 + q2 * q3; + const float eps = 1e-7; // TODO: pick from your math lib instead of hardcoding. + if (abcd > (0.5-eps) * unitLength) + { + *roll = 0; + *pitch = M_PI; + *yaw = 2.0f * atan2(q2, q0); + } + else if (abcd < (-0.5 + eps) * unitLength) + { + *roll = 0; + *pitch = -M_PI; + *yaw = -2.0f * atan2(q2, q0); + } + else + { + const float adbc = q0 * q3 - q1 * q2; + const float acbd = q0 * q2 - q1 * q3; + *roll = atan2(2.0f * acbd, 1.0f - 2.0f * (y2 + x2)); + *pitch = asin(2.0f * abcd / unitLength); + *yaw = atan2(2.0f * adbc, 1.0f - 2.0f * (z2 + x2)); + } +} + +// CoreMotionMadgwickTestDriver implementation. +@implementation CoreMotionMadgwickTestDriver +{ +@private + // The motion manager. + CMMotionManager * _motionManager; + + // The operation queue. + NSOperationQueue * _operationQueue; + + // The Madgwick sensor fusion. + MadgwickSensorFusion * _madgwickSensorFusion; +} + +// Class initializer. +- (nullable instancetype)initSampleFrequencyHz:(float)sampleFrequencyHz + beta:(float)beta; +{ + // Initialize superclass. + self = [super init]; + + // Handle errors. + if (!self) + { + return nil; + } + + // Allocate and initialize the motion manager. + _motionManager = [[CMMotionManager alloc] init]; + [_motionManager setShowsDeviceMovementDisplay:YES]; + [_motionManager setDeviceMotionUpdateInterval:1.0 / sampleFrequencyHz]; + + // Allocate and initialize the operation queue. + _operationQueue = [[NSOperationQueue alloc] init]; + [_operationQueue setName:@"DeviceMotion"]; + [_operationQueue setMaxConcurrentOperationCount:1]; + + // Allocate and initialize the Madgwick sensor fusion. + _madgwickSensorFusion = [[MadgwickSensorFusion alloc] initWithSampleFrequencyHz:sampleFrequencyHz + beta:beta]; + + // Done. + return self; +} + +// Starts the driver. +- (void)start +{ + // The device motion handler. + CMDeviceMotionHandler handler = ^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) + { + // Employ Madgwick AHRS sensor fusion. + [_madgwickSensorFusion updateWithGyroscopeX:(float)[motion rotationRate].x + gyroscopeY:(float)[motion rotationRate].y + gyroscopeZ:(float)[motion rotationRate].z + accelerometerX:(float)[motion gravity].x * -1.0f + accelerometerY:(float)[motion gravity].y * -1.0f + accelerometerZ:(float)[motion gravity].z * -1.0f + magnetometerX:(float)[motion magneticField].field.x + magnetometerY:(float)[motion magneticField].field.y + magnetometerZ:(float)[motion magneticField].field.z]; + + // Calculate roll, pitch, yaw. + float roll, pitch, yaw; + CalculateEulerAnglesFromQuaternion([_madgwickSensorFusion q0], + [_madgwickSensorFusion q1], + [_madgwickSensorFusion q2], + [_madgwickSensorFusion q3], + &roll, + &pitch, + &yaw); + roll = NormalizeAngle(RadiansToDegrees(roll)); + pitch = NormalizeAngle(RadiansToDegrees(pitch)); + yaw = NormalizeAngle(RadiansToDegrees(yaw)); + + // Obtain CoreMotion roll, pitch and yaw for comparison logging below. + float coreMotionRoll = NormalizeAngle(RadiansToDegrees([[motion attitude] roll])); + float coreMotionPitch = NormalizeAngle(RadiansToDegrees([[motion attitude] pitch])); + float coreMotionYaw = NormalizeAngle(RadiansToDegrees([[motion attitude] yaw])); + + // Notify the delegate. + if ([[self delegate] respondsToSelector:@selector(coreMotionMadgwickTestDriver: + didUpdateGyroscopeX: + gyroscopeY: + gyroscopeZ: + accelerometerX: + accelerometerY: + accelerometerZ: + magnetometerX: + magnetometerY: + magnetometerZ: + quaternion0: + quaternion1: + quaternion2: + quaternion3: + roll: + pitch: + yaw: + coreMotionRoll: + coreMotionPitch: + coreMotionYaw:)]) + { + [[self delegate] coreMotionMadgwickTestDriver:self + didUpdateGyroscopeX:[motion rotationRate].x + gyroscopeY:[motion rotationRate].y + gyroscopeZ:[motion rotationRate].z + accelerometerX:[motion gravity].x * -1.0f + accelerometerY:[motion gravity].y * -1.0f + accelerometerZ:[motion gravity].z * -1.0f + magnetometerX:[motion magneticField].field.x + magnetometerY:[motion magneticField].field.y + magnetometerZ:[motion magneticField].field.z + quaternion0:[_madgwickSensorFusion q0] + quaternion1:[_madgwickSensorFusion q1] + quaternion2:[_madgwickSensorFusion q2] + quaternion3:[_madgwickSensorFusion q3] + roll:roll + pitch:pitch + yaw:yaw + coreMotionRoll:coreMotionRoll + coreMotionPitch:coreMotionPitch + coreMotionYaw:coreMotionYaw]; + } + }; + + // Start motion updates. + [_motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical + toQueue:_operationQueue + withHandler:handler]; +} + +// Stops the driver. +- (void)stop +{ + // Stop motion updates. + [_motionManager stopDeviceMotionUpdates]; +} + +@end \ No newline at end of file diff --git a/MadgwickAHRS/CoreMotionMadgwickTestDriverDelegate.h b/MadgwickAHRS/CoreMotionMadgwickTestDriverDelegate.h new file mode 100644 index 0000000..a3a1406 --- /dev/null +++ b/MadgwickAHRS/CoreMotionMadgwickTestDriverDelegate.h @@ -0,0 +1,38 @@ +// +// CoreMotionMadgwickTestDriverDelegate.h +// MadgwickAHRS +// +// Created by Brian Lambert on 6/11/16. +// Copyright © 2016 Softwarenerd. All rights reserved. +// + +// Forward declarations. +@class CoreMotionMadgwickTestDriver; + +// CoreMotionMadgwickTestDriverDelegate protocol. +@protocol CoreMotionMadgwickTestDriverDelegate +@required + +// Notifies the delegate of an update. +- (void)coreMotionMadgwickTestDriver:(CoreMotionMadgwickTestDriver *)coreMotionMadgwickTestDriver + didUpdateGyroscopeX:(float)gx + gyroscopeY:(float)gy + gyroscopeZ:(float)gz + accelerometerX:(float)ax + accelerometerY:(float)ay + accelerometerZ:(float)az + magnetometerX:(float)mx + magnetometerY:(float)my + magnetometerZ:(float)mz + quaternion0:(float)q0 + quaternion1:(float)q1 + quaternion2:(float)q2 + quaternion3:(float)q3 + roll:(float)roll + pitch:(float)pitch + yaw:(float)yaw + coreMotionRoll:(float)coreMotionRoll + coreMotionPitch:(float)coreMotionPitch + coreMotionYaw:(float)coreMotionYaw; + +@end diff --git a/MadgwickAHRS/Info.plist b/MadgwickAHRS/Info.plist new file mode 100644 index 0000000..d3de8ee --- /dev/null +++ b/MadgwickAHRS/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/MadgwickAHRS/MadgwickAHRS.h b/MadgwickAHRS/MadgwickAHRS.h new file mode 100644 index 0000000..b0e87e8 --- /dev/null +++ b/MadgwickAHRS/MadgwickAHRS.h @@ -0,0 +1,20 @@ +// +// MadgwickAHRS.h +// MadgwickAHRS +// +// Created by Brian Lambert on 6/10/16. +// Copyright © 2016 Softwarenerd. All rights reserved. +// + +#import + +//! Project version number for MadgwickAHRS. +FOUNDATION_EXPORT double MadgwickAHRSVersionNumber; + +//! Project version string for MadgwickAHRS. +FOUNDATION_EXPORT const unsigned char MadgwickAHRSVersionString[]; + +// Public headers. +#import +#import + diff --git a/MadgwickAHRS/MadgwickSensorFusion.h b/MadgwickAHRS/MadgwickSensorFusion.h new file mode 100644 index 0000000..4935cbe --- /dev/null +++ b/MadgwickAHRS/MadgwickSensorFusion.h @@ -0,0 +1,50 @@ +// +// MadgwickSensorFusion.h +// MadgwickAHRS +// +// Implementation of Madgwick's IMU and AHRS algorithms. +// See: http://www.x-io.co.uk/open-source-imu-and-ahrs-algorithms/ +// +// Date Author Notes +// -------------------------------------------------------------------------------------------------- +// 29/09/2011 SOH Madgwick Initial release +// 02/10/2011 SOH Madgwick Optimised for reduced CPU load +// 19/02/2012 SOH Madgwick Magnetometer measurement is normalised +// 02/06/2016 Brian Lambert Ported to Objective-C. +// + +@import Foundation; + +// MadgwickSensorFusion interface. +@interface MadgwickSensorFusion : NSObject + +// Quaternion properties. +@property (nonatomic, readonly) float q0; +@property (nonatomic, readonly) float q1; +@property (nonatomic, readonly) float q2; +@property (nonatomic, readonly) float q3; + +// Class initializer. +- (nullable instancetype)initWithSampleFrequencyHz:(float)sampleFrequencyHz + beta:(float)beta; + +// Update with gyroscope and accelerometer. +- (void)updateWithGyroscopeX:(float)gx + gyroscopeY:(float)gy + gyroscopeZ:(float)gz + accelerometerX:(float)ax + accelerometerY:(float)ay + accelerometerZ:(float)az; + +// Update with gyroscope, accelerometer and magnetometer. +- (void)updateWithGyroscopeX:(float)gx + gyroscopeY:(float)gy + gyroscopeZ:(float)gz + accelerometerX:(float)ax + accelerometerY:(float)ay + accelerometerZ:(float)az + magnetometerX:(float)mx + magnetometerY:(float)my + magnetometerZ:(float)mz; + +@end diff --git a/MadgwickAHRS/MadgwickSensorFusion.m b/MadgwickAHRS/MadgwickSensorFusion.m new file mode 100644 index 0000000..2a31f0c --- /dev/null +++ b/MadgwickAHRS/MadgwickSensorFusion.m @@ -0,0 +1,252 @@ +// +// MadgwickSensorFusion.m +// MadgwickAHRS +// +// Implementation of Madgwick's IMU and AHRS algorithms. +// See: http://www.x-io.co.uk/open-source-imu-and-ahrs-algorithms/ +// +// Date Author Notes +// -------------------------------------------------------------------------------------------------- +// 29/09/2011 SOH Madgwick Initial release +// 02/10/2011 SOH Madgwick Optimised for reduced CPU load +// 19/02/2012 SOH Madgwick Magnetometer measurement is normalised +// 02/06/2016 Brian Lambert Ported to Objective-C. +// + +#include +#import "MadgwickSensorFusion.h" + +// Inverse square root. +static inline float invSqrt(float x) +{ + float xhalf = 0.5f * x; + int i = *(int*)&x; // get bits for floating value + i = 0x5f375a86 - (i >> 1); // gives initial guess y0 + x = *(float*)&i; // convert bits back to float + x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy + return x; +} + +// MadgwickSensorFusion implementation. +@implementation MadgwickSensorFusion +{ +@private + // Sample frequency. + float _sampleFrequencyHz; + + // Beta. + float _beta; +} + +// Class initializer. +- (nullable instancetype)initWithSampleFrequencyHz:(float)sampleFrequencyHz + beta:(float)beta; +{ + // Initialize superclass. + self = [super init]; + + // Handle errors. + if (!self) + { + return nil; + } + + _sampleFrequencyHz = sampleFrequencyHz; + _beta = beta; + _q0 = 1.0f; + _q1 = 0.0f; + _q2 = 0.0f; + _q3 = 0.0f; + + // Done. + return self; +} + +// Update with gyroscope and accelerometer. +- (void)updateWithGyroscopeX:(float)gx + gyroscopeY:(float)gy + gyroscopeZ:(float)gz + accelerometerX:(float)ax + accelerometerY:(float)ay + accelerometerZ:(float)az +{ + float recipNorm; + float s0, s1, s2, s3; + float qDot1, qDot2, qDot3, qDot4; + float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2 ,_8q1, _8q2, q0q0, q1q1, q2q2, q3q3; + + // Rate of change of quaternion from gyroscope + qDot1 = 0.5f * (-_q1 * gx - _q2 * gy - _q3 * gz); + qDot2 = 0.5f * (_q0 * gx + _q2 * gz - _q3 * gy); + qDot3 = 0.5f * (_q0 * gy - _q1 * gz + _q3 * gx); + qDot4 = 0.5f * (_q0 * gz + _q1 * gy - _q2 * gx); + + // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation) + if (!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) + { + // Normalise accelerometer measurement + recipNorm = invSqrt(ax * ax + ay * ay + az * az); + ax *= recipNorm; + ay *= recipNorm; + az *= recipNorm; + + // Auxiliary variables to avoid repeated arithmetic + _2q0 = 2.0f * _q0; + _2q1 = 2.0f * _q1; + _2q2 = 2.0f * _q2; + _2q3 = 2.0f * _q3; + _4q0 = 4.0f * _q0; + _4q1 = 4.0f * _q1; + _4q2 = 4.0f * _q2; + _8q1 = 8.0f * _q1; + _8q2 = 8.0f * _q2; + q0q0 = _q0 * _q0; + q1q1 = _q1 * _q1; + q2q2 = _q2 * _q2; + q3q3 = _q3 * _q3; + + // Gradient decent algorithm corrective step + s0 = _4q0 * q2q2 + _2q2 * ax + _4q0 * q1q1 - _2q1 * ay; + s1 = _4q1 * q3q3 - _2q3 * ax + 4.0f * q0q0 * _q1 - _2q0 * ay - _4q1 + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * az; + s2 = 4.0f * q0q0 * _q2 + _2q0 * ax + _4q2 * q3q3 - _2q3 * ay - _4q2 + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * az; + s3 = 4.0f * q1q1 * _q3 - _2q1 * ax + 4.0f * q2q2 * _q3 - _2q2 * ay; + recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude + s0 *= recipNorm; + s1 *= recipNorm; + s2 *= recipNorm; + s3 *= recipNorm; + + // Apply feedback step + qDot1 -= _beta * s0; + qDot2 -= _beta * s1; + qDot3 -= _beta * s2; + qDot4 -= _beta * s3; + } + + // Integrate rate of change of quaternion to yield quaternion + _q0 += qDot1 * (1.0f / _sampleFrequencyHz); + _q1 += qDot2 * (1.0f / _sampleFrequencyHz); + _q2 += qDot3 * (1.0f / _sampleFrequencyHz); + _q3 += qDot4 * (1.0f / _sampleFrequencyHz); + + // Normalise quaternion + recipNorm = invSqrt(_q0 * _q0 + _q1 * _q1 + _q2 * _q2 + _q3 * _q3); + _q0 *= recipNorm; + _q1 *= recipNorm; + _q2 *= recipNorm; + _q3 *= recipNorm; +} + +// Update with gyroscope, accelerometer and magnetometer. +- (void)updateWithGyroscopeX:(float)gx + gyroscopeY:(float)gy + gyroscopeZ:(float)gz + accelerometerX:(float)ax + accelerometerY:(float)ay + accelerometerZ:(float)az + magnetometerX:(float)mx + magnetometerY:(float)my + magnetometerZ:(float)mz +{ + float recipNorm; + float s0, s1, s2, s3; + float qDot1, qDot2, qDot3, qDot4; + float hx, hy; + float _2q0mx, _2q0my, _2q0mz, _2q1mx, _2bx, _2bz, _4bx, _4bz, _2q0, _2q1, _2q2, _2q3, _2q0q2, _2q2q3, q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3; + + // Use IMU algorithm if magnetometer measurement invalid (avoids NaN in magnetometer normalisation) + if ((mx == 0.0f) && (my == 0.0f) && (mz == 0.0f)) + { + [self updateWithGyroscopeX:gx + gyroscopeY:gy + gyroscopeZ:gz + accelerometerX:ax + accelerometerY:ay + accelerometerZ:az]; + return; + } + + // Rate of change of quaternion from gyroscope + qDot1 = 0.5f * (-_q1 * gx - _q2 * gy - _q3 * gz); + qDot2 = 0.5f * (_q0 * gx + _q2 * gz - _q3 * gy); + qDot3 = 0.5f * (_q0 * gy - _q1 * gz + _q3 * gx); + qDot4 = 0.5f * (_q0 * gz + _q1 * gy - _q2 * gx); + + // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation) + if (!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) + { + // Normalise accelerometer measurement + recipNorm = invSqrt(ax * ax + ay * ay + az * az); + ax *= recipNorm; + ay *= recipNorm; + az *= recipNorm; + + // Normalise magnetometer measurement + recipNorm = invSqrt(mx * mx + my * my + mz * mz); + mx *= recipNorm; + my *= recipNorm; + mz *= recipNorm; + + // Auxiliary variables to avoid repeated arithmetic + _2q0mx = 2.0f * _q0 * mx; + _2q0my = 2.0f * _q0 * my; + _2q0mz = 2.0f * _q0 * mz; + _2q1mx = 2.0f * _q1 * mx; + _2q0 = 2.0f * _q0; + _2q1 = 2.0f * _q1; + _2q2 = 2.0f * _q2; + _2q3 = 2.0f * _q3; + _2q0q2 = 2.0f * _q0 * _q2; + _2q2q3 = 2.0f * _q2 * _q3; + q0q0 = _q0 * _q0; + q0q1 = _q0 * _q1; + q0q2 = _q0 * _q2; + q0q3 = _q0 * _q3; + q1q1 = _q1 * _q1; + q1q2 = _q1 * _q2; + q1q3 = _q1 * _q3; + q2q2 = _q2 * _q2; + q2q3 = _q2 * _q3; + q3q3 = _q3 * _q3; + + // Reference direction of Earth's magnetic field + hx = mx * q0q0 - _2q0my * _q3 + _2q0mz * _q2 + mx * q1q1 + _2q1 * my * _q2 + _2q1 * mz * _q3 - mx * q2q2 - mx * q3q3; + hy = _2q0mx * _q3 + my * q0q0 - _2q0mz * _q1 + _2q1mx * _q2 - my * q1q1 + my * q2q2 + _2q2 * mz * _q3 - my * q3q3; + _2bx = sqrt(hx * hx + hy * hy); + _2bz = -_2q0mx * _q2 + _2q0my * _q1 + mz * q0q0 + _2q1mx * _q3 - mz * q1q1 + _2q2 * my * _q3 - mz * q2q2 + mz * q3q3; + _4bx = 2.0f * _2bx; + _4bz = 2.0f * _2bz; + + // Gradient decent algorithm corrective step + s0 = -_2q2 * (2.0f * q1q3 - _2q0q2 - ax) + _2q1 * (2.0f * q0q1 + _2q2q3 - ay) - _2bz * _q2 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * _q3 + _2bz * _q1) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * _q2 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz); + s1 = _2q3 * (2.0f * q1q3 - _2q0q2 - ax) + _2q0 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * _q1 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + _2bz * _q3 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * _q2 + _2bz * _q0) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * _q3 - _4bz * _q1) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz); + s2 = -_2q0 * (2.0f * q1q3 - _2q0q2 - ax) + _2q3 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * _q2 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + (-_4bx * _q2 - _2bz * _q0) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * _q1 + _2bz * _q3) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * _q0 - _4bz * _q2) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz); + s3 = _2q1 * (2.0f * q1q3 - _2q0q2 - ax) + _2q2 * (2.0f * q0q1 + _2q2q3 - ay) + (-_4bx * _q3 + _2bz * _q1) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * _q0 + _2bz * _q2) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * _q1 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz); + recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude + s0 *= recipNorm; + s1 *= recipNorm; + s2 *= recipNorm; + s3 *= recipNorm; + + // Apply feedback step + qDot1 -= _beta * s0; + qDot2 -= _beta * s1; + qDot3 -= _beta * s2; + qDot4 -= _beta * s3; + } + + // Integrate rate of change of quaternion to yield quaternion + _q0 += qDot1 * (1.0f / _sampleFrequencyHz); + _q1 += qDot2 * (1.0f / _sampleFrequencyHz); + _q2 += qDot3 * (1.0f / _sampleFrequencyHz); + _q3 += qDot4 * (1.0f / _sampleFrequencyHz); + + // Normalise quaternion + recipNorm = invSqrt(_q0 * _q0 + _q1 * _q1 + _q2 * _q2 + _q3 * _q3); + _q0 *= recipNorm; + _q1 *= recipNorm; + _q2 *= recipNorm; + _q3 *= recipNorm; +} + +@end \ No newline at end of file diff --git a/MadgwickAHRSTests/Info.plist b/MadgwickAHRSTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/MadgwickAHRSTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/MadgwickAHRSTests/MadgwickAHRSTests.m b/MadgwickAHRSTests/MadgwickAHRSTests.m new file mode 100644 index 0000000..f67a886 --- /dev/null +++ b/MadgwickAHRSTests/MadgwickAHRSTests.m @@ -0,0 +1,119 @@ +// +// MadgwickAHRSTests.m +// MadgwickAHRSTests +// +// Created by Brian Lambert on 6/10/16. +// Copyright © 2016 Softwarenerd. All rights reserved. +// + +#import +#import "MadgwickSensorFusion.h" + +// Normalizes. +float NormalizeAngle(float angle) +{ + return fmodf(angle + 360.0f, 360.0f); +} + +// Converts radians to degrees. +static inline float RadiansToDegrees(float radians) +{ + return radians * 180.0f / M_PI; +} + +// Calculates Euler angles from quaternion. +void CalculateEulerAnglesFromQuaternion(float q0, float q1, float q2, float q3, float * roll, float * pitch, float * yaw) +{ + const float w2 = q0 * q0; + const float x2 = q1 * q1; + const float y2 = q2 * q2; + const float z2 = q3 * q3; + const float unitLength = w2 + x2 + y2 + z2; // Normalised == 1, otherwise correction divisor. + const float abcd = q0 * q1 + q2 * q3; + const float eps = 1e-7; // TODO: pick from your math lib instead of hardcoding. + if (abcd > (0.5-eps) * unitLength) + { + *roll = 0; + *pitch = M_PI; + *yaw = 2.0f * atan2(q2, q0); + } + else if (abcd < (-0.5 + eps) * unitLength) + { + *roll = 0; + *pitch = -M_PI; + *yaw = -2.0f * atan2(q2, q0); + } + else + { + const float adbc = q0 * q3 - q1 * q2; + const float acbd = q0 * q2 - q1 * q3; + *roll = atan2(2.0f * acbd, 1.0f - 2.0f * (y2 + x2)); + *pitch = asin(2.0f * abcd / unitLength); + *yaw = atan2(2.0f * adbc, 1.0f - 2.0f * (z2 + x2)); + } +} + +// MadgwickAHRSTests interface. +@interface MadgwickAHRSTests : XCTestCase +@end + +// MadgwickAHRSTests implementation. +@implementation MadgwickAHRSTests + +// Setup. +- (void)setUp +{ + [super setUp]; +} + +// Teardown. +- (void)tearDown +{ + [super tearDown]; +} + +// Madgwick test A. +- (void)testMadgwickA +{ + // Test. + NSString * sampleJSON = @"[{\"pitch\":0.5264893,\"gz\":0.0004721961,\"my\":0,\"yaw\":0.03433228,\"coreMotionRoll\":359.509,\"mz\":0,\"ax\":0.00856893,\"coreMotionPitch\":0.03717041,\"coreMotionYaw\":0.0003051758,\"gx\":0.000979821,\"ay\":0.0006486682,\"gy\":0.0001413785,\"az\":0.9999631,\"mx\":0,\"roll\":353.1229},{\"pitch\":359.9064,\"gz\":-0.00058764,\"my\":0,\"yaw\":0.004882812,\"coreMotionRoll\":359.5904,\"mz\":0,\"ax\":0.007148615,\"coreMotionPitch\":359.9442,\"coreMotionYaw\":0.003265381,\"gx\":-9.922422e-05,\"ay\":-0.0009733105,\"gy\":0.0001388151,\"az\":0.999974,\"mx\":0,\"roll\":0.004669189},{\"pitch\":0.2915344,\"gz\":-0.001647469,\"my\":0,\"yaw\":0.007385254,\"coreMotionRoll\":359.6066,\"mz\":0,\"ax\":0.006866026,\"coreMotionPitch\":359.9293,\"coreMotionYaw\":0.003875732,\"gx\":-0.00117847,\"ay\":-0.001234906,\"gy\":0.000138258,\"az\":0.9999757,\"mx\":0,\"roll\":353.111},{\"pitch\":359.911,\"gz\":0.0004648018,\"my\":0,\"yaw\":359.9985,\"coreMotionRoll\":359.6366,\"mz\":0,\"ax\":0.00634326,\"coreMotionPitch\":359.9267,\"coreMotionYaw\":0.005981445,\"gx\":0.000960644,\"ay\":-0.001279598,\"gy\":0.001242075,\"az\":0.9999791,\"mx\":0,\"roll\":0.01568604},{\"pitch\":1.100098,\"gz\":0.0004716274,\"my\":-18.01436,\"yaw\":2.781006,\"coreMotionRoll\":359.6564,\"mz\":-21.21292,\"ax\":0.00599631,\"coreMotionPitch\":359.917,\"coreMotionYaw\":0.009277344,\"gx\":-0.0001210217,\"ay\":-0.001449067,\"gy\":0.001269948,\"az\":0.999981,\"mx\":-39.24414,\"roll\":353.7953},{\"pitch\":2.127441,\"gz\":0.0004861102,\"my\":-18.2394,\"yaw\":6.087921,\"coreMotionRoll\":359.6654,\"mz\":-21.13721,\"ax\":0.005840272,\"coreMotionPitch\":359.9029,\"coreMotionYaw\":155.4675,\"gx\":-0.001221335,\"ay\":-0.001695231,\"gy\":0.0002259741,\"az\":0.9999815,\"mx\":-39.39549,\"roll\":347.8245},{\"pitch\":2.918884,\"gz\":-0.0005725962,\"my\":-18.08928,\"yaw\":9.976746,\"coreMotionRoll\":359.6707,\"mz\":-21.3631,\"ax\":0.005747447,\"coreMotionPitch\":359.8967,\"coreMotionYaw\":155.469,\"gx\":-0.001241715,\"ay\":-0.001802874,\"gy\":-0.000834419,\"az\":0.9999819,\"mx\":-39.24484,\"roll\":342.2617},{\"pitch\":3.395905,\"gz\":-0.0005935672,\"my\":-18.16438,\"yaw\":14.8356,\"coreMotionRoll\":359.6769,\"mz\":-21.13829,\"ax\":0.005639632,\"coreMotionPitch\":359.8953,\"coreMotionYaw\":155.4705,\"gx\":0.0008934455,\"ay\":-0.001827041,\"gy\":0.0002295439,\"az\":0.9999824,\"mx\":-39.245,\"roll\":337.5668},{\"pitch\":3.261566,\"gz\":-0.0005935144,\"my\":-17.71436,\"yaw\":20.87393,\"coreMotionRoll\":359.673,\"mz\":-21.21283,\"ax\":0.005707813,\"coreMotionPitch\":359.8964,\"coreMotionYaw\":155.4746,\"gx\":0.0008834388,\"ay\":-0.001809077,\"gy\":0.0001997134,\"az\":0.9999821,\"mx\":-39.16719,\"roll\":334.5395},{\"pitch\":2.743958,\"gz\":0.001545258,\"my\":-18.01414,\"yaw\":27.57236,\"coreMotionRoll\":359.6851,\"mz\":-21.59238,\"ax\":0.005496477,\"coreMotionPitch\":359.9007,\"coreMotionYaw\":155.4771,\"gx\":-0.001224232,\"ay\":-0.001733401,\"gy\":0.001313187,\"az\":0.9999834,\"mx\":-38.71971,\"roll\":334.1277},{\"pitch\":1.631714,\"gz\":0.0004798245,\"my\":-18.01424,\"yaw\":34.12677,\"coreMotionRoll\":359.6827,\"mz\":-21.4341,\"ax\":0.005537265,\"coreMotionPitch\":359.8982,\"coreMotionYaw\":155.4827,\"gx\":-0.0001730853,\"ay\":-0.001777576,\"gy\":0.0002277463,\"az\":0.9999831,\"mx\":-39.69447,\"roll\":334.4159},{\"pitch\":0.5903931,\"gz\":0.001601837,\"my\":-17.93913,\"yaw\":40.5376,\"coreMotionRoll\":359.6805,\"mz\":-21.66147,\"ax\":0.005575487,\"coreMotionPitch\":359.9007,\"coreMotionYaw\":155.4879,\"gx\":0.002010253,\"ay\":-0.00173293,\"gy\":0.001289319,\"az\":0.999983,\"mx\":-39.39432,\"roll\":335.0559},{\"pitch\":359.626,\"gz\":-0.0005246151,\"my\":-18.23932,\"yaw\":46.78391,\"coreMotionRoll\":359.6854,\"mz\":-21.28784,\"ax\":0.00549029,\"coreMotionPitch\":359.8986,\"coreMotionYaw\":155.491,\"gx\":0.0009288067,\"ay\":-0.001770687,\"gy\":0.001273021,\"az\":0.9999834,\"mx\":-39.32071,\"roll\":335.781},{\"pitch\":358.6909,\"gz\":0.0005717687,\"my\":-18.23929,\"yaw\":52.84467,\"coreMotionRoll\":359.6847,\"mz\":-21.36411,\"ax\":0.005503466,\"coreMotionPitch\":359.9033,\"coreMotionYaw\":155.4957,\"gx\":-0.001196151,\"ay\":-0.001687269,\"gy\":0.001287353,\"az\":0.9999834,\"mx\":-39.17082,\"roll\":336.6324},{\"pitch\":357.7881,\"gz\":-0.0005003662,\"my\":-18.08939,\"yaw\":58.73987,\"coreMotionRoll\":359.6848,\"mz\":-21.13684,\"ax\":0.00550069,\"coreMotionPitch\":359.9014,\"coreMotionYaw\":155.4998,\"gx\":0.000935566,\"ay\":-0.001720674,\"gy\":0.000214548,\"az\":0.9999834,\"mx\":-39.39452,\"roll\":337.522},{\"pitch\":356.9998,\"gz\":-0.0004932038,\"my\":-18.16438,\"yaw\":64.45319,\"coreMotionRoll\":359.6867,\"mz\":-21.13893,\"ax\":0.005467175,\"coreMotionPitch\":359.8968,\"coreMotionYaw\":155.5027,\"gx\":-0.0001344203,\"ay\":-0.001801449,\"gy\":0.000212512,\"az\":0.9999834,\"mx\":-39.17,\"roll\":338.5226},{\"pitch\":356.2814,\"gz\":-0.0004721422,\"my\":-17.86435,\"yaw\":69.97742,\"coreMotionRoll\":359.6789,\"mz\":-21.21701,\"ax\":0.005604142,\"coreMotionPitch\":359.9073,\"coreMotionYaw\":155.5059,\"gx\":-0.002268021,\"ay\":-0.001617905,\"gy\":-0.0008509943,\"az\":0.999983,\"mx\":-38.71817,\"roll\":339.584},{\"pitch\":355.6748,\"gz\":0.001621265,\"my\":-18.01428,\"yaw\":75.33875,\"coreMotionRoll\":359.6771,\"mz\":-21.36546,\"ax\":0.005635374,\"coreMotionPitch\":359.9055,\"coreMotionYaw\":155.5076,\"gx\":-0.0001259915,\"ay\":-0.001649251,\"gy\":0.002344245,\"az\":0.9999828,\"mx\":-38.94437,\"roll\":340.7151},{\"pitch\":355.1684,\"gz\":0.00162953,\"my\":-18.31429,\"yaw\":80.51544,\"coreMotionRoll\":359.6812,\"mz\":-21.36429,\"ax\":0.00556452,\"coreMotionPitch\":359.9016,\"coreMotionYaw\":155.5114,\"gx\":-0.002267306,\"ay\":-0.001718301,\"gy\":0.002341616,\"az\":0.999983,\"mx\":-39.17131,\"roll\":341.8837},{\"pitch\":354.7627,\"gz\":-0.0005025471,\"my\":-18.31416,\"yaw\":85.48795,\"coreMotionRoll\":359.6765,\"mz\":-21.58929,\"ax\":0.00564519,\"coreMotionPitch\":359.9017,\"coreMotionYaw\":155.517,\"gx\":-0.001207108,\"ay\":-0.001714817,\"gy\":0.001260646,\"az\":0.9999826,\"mx\":-39.17165,\"roll\":343.0611},{\"pitch\":354.4624,\"gz\":0.0005554502,\"my\":-18.53928,\"yaw\":90.25879,\"coreMotionRoll\":359.6874,\"mz\":-21.36676,\"ax\":0.005455916,\"coreMotionPitch\":359.8995,\"coreMotionYaw\":155.5216,\"gx\":-0.003344004,\"ay\":-0.001753647,\"gy\":0.003388743,\"az\":0.9999835,\"mx\":-38.94778,\"roll\":344.2699},{\"pitch\":354.2736,\"gz\":0.001610477,\"my\":-18.53929,\"yaw\":94.83987,\"coreMotionRoll\":359.6895,\"mz\":-21.29303,\"ax\":0.005420057,\"coreMotionPitch\":359.8964,\"coreMotionYaw\":155.5253,\"gx\":0.001997071,\"ay\":-0.001808128,\"gy\":-0.0008544789,\"az\":0.9999837,\"mx\":-38.79767,\"roll\":345.4387},{\"pitch\":354.117,\"gz\":0.0005417952,\"my\":-18.2394,\"yaw\":99.22943,\"coreMotionRoll\":359.6873,\"mz\":-21.14102,\"ax\":0.005457308,\"coreMotionPitch\":359.9012,\"coreMotionYaw\":155.5299,\"gx\":-0.0001431877,\"ay\":-0.001725242,\"gy\":0.001259688,\"az\":0.9999836,\"mx\":-38.9455,\"roll\":346.5743},{\"pitch\":354.0229,\"gz\":0.0005318171,\"my\":-18.08939,\"yaw\":103.4232,\"coreMotionRoll\":359.6794,\"mz\":-21.14003,\"ax\":0.005596395,\"coreMotionPitch\":359.9007,\"coreMotionYaw\":155.5335,\"gx\":-0.001214946,\"ay\":-0.001733326,\"gy\":0.002314281,\"az\":0.9999828,\"mx\":-39.01952,\"roll\":347.6774},{\"pitch\":354.0078,\"gz\":0.0005451886,\"my\":-18.31447,\"yaw\":107.4103,\"coreMotionRoll\":359.6784,\"mz\":-20.98802,\"ax\":0.005612301,\"coreMotionPitch\":359.8974,\"coreMotionYaw\":155.5372,\"gx\":-0.003353131,\"ay\":-0.001791405,\"gy\":0.002314666,\"az\":0.9999827,\"mx\":-39.32076,\"roll\":348.7499},{\"pitch\":354.0503,\"gz\":0.0005181289,\"my\":-17.86432,\"yaw\":111.2149,\"coreMotionRoll\":359.6768,\"mz\":-21.28629,\"ax\":0.005639995,\"coreMotionPitch\":359.8927,\"coreMotionYaw\":155.5414,\"gx\":0.001984719,\"ay\":-0.001872927,\"gy\":0.0001839434,\"az\":0.9999824,\"mx\":-39.39328,\"roll\":349.7408},{\"pitch\":354.1326,\"gz\":0.001607992,\"my\":-17.93916,\"yaw\":114.7867,\"coreMotionRoll\":359.6785,\"mz\":-21.58838,\"ax\":0.005610745,\"coreMotionPitch\":359.8929,\"coreMotionYaw\":155.5467,\"gx\":-0.004424463,\"ay\":-0.001868801,\"gy\":0.001250984,\"az\":0.9999825,\"mx\":-39.16922,\"roll\":350.7054},{\"pitch\":354.2748,\"gz\":0.0005337178,\"my\":-18.01405,\"yaw\":118.1411,\"coreMotionRoll\":359.68,\"mz\":-21.81163,\"ax\":0.005585566,\"coreMotionPitch\":359.8969,\"coreMotionYaw\":155.5484,\"gx\":-0.004427614,\"ay\":-0.001799988,\"gy\":0.00230322,\"az\":0.9999828,\"mx\":-39.39503,\"roll\":351.6176},{\"pitch\":354.5161,\"gz\":0.001573929,\"my\":-18.31421,\"yaw\":121.2901,\"coreMotionRoll\":359.6844,\"mz\":-21.51047,\"ax\":0.005508173,\"coreMotionPitch\":359.8969,\"coreMotionYaw\":155.5528,\"gx\":0.001984259,\"ay\":-0.001798682,\"gy\":-0.0008820838,\"az\":0.9999832,\"mx\":-39.62154,\"roll\":352.4596},{\"pitch\":354.8075,\"gz\":0.000482962,\"my\":-18.53923,\"yaw\":124.1971,\"coreMotionRoll\":359.6952,\"mz\":-21.43793,\"ax\":0.00532046,\"coreMotionPitch\":359.8993,\"coreMotionYaw\":155.5574,\"gx\":0.003046888,\"ay\":-0.00175722,\"gy\":0.0001624246,\"az\":0.9999843,\"mx\":-39.39789,\"roll\":353.2589},{\"pitch\":355.1059,\"gz\":0.001561856,\"my\":-18.68939,\"yaw\":126.8858,\"coreMotionRoll\":359.6972,\"mz\":-21.14021,\"ax\":0.005284442,\"coreMotionPitch\":359.9057,\"coreMotionYaw\":155.5623,\"gx\":-0.0001542046,\"ay\":-0.001645855,\"gy\":0.001234441,\"az\":0.9999847,\"mx\":-39.17342,\"roll\":354.0023},{\"pitch\":355.3894,\"gz\":0.002603529,\"my\":-18.38936,\"yaw\":129.4217,\"coreMotionRoll\":359.6907,\"mz\":-21.21255,\"ax\":0.005397901,\"coreMotionPitch\":359.9117,\"coreMotionYaw\":155.5687,\"gx\":0.00198162,\"ay\":-0.001541039,\"gy\":0.001235588,\"az\":0.9999843,\"mx\":-39.39658,\"roll\":354.652},{\"pitch\":355.6687,\"gz\":0.0004847745,\"my\":-18.38939,\"yaw\":131.7621,\"coreMotionRoll\":359.6879,\"mz\":-21.13629,\"ax\":0.005446805,\"coreMotionPitch\":359.908,\"coreMotionYaw\":155.5718,\"gx\":0.0009046478,\"ay\":-0.001605289,\"gy\":0.0001566671,\"az\":0.9999839,\"mx\":-39.54646,\"roll\":355.2295},{\"pitch\":355.92,\"gz\":0.0005062941,\"my\":-18.08934,\"yaw\":133.9038,\"coreMotionRoll\":359.6873,\"mz\":-21.21692,\"ax\":0.005458171,\"coreMotionPitch\":359.9076,\"coreMotionYaw\":155.5754,\"gx\":-0.00444238,\"ay\":-0.001612683,\"gy\":0.0001536806,\"az\":0.9999838,\"mx\":-38.79463,\"roll\":355.7503},{\"pitch\":356.1986,\"gz\":0.001534431,\"my\":-18.16428,\"yaw\":135.8723,\"coreMotionRoll\":359.6863,\"mz\":-21.36647,\"ax\":0.00547462,\"coreMotionPitch\":359.911,\"coreMotionYaw\":155.5792,\"gx\":-0.001236772,\"ay\":-0.00155253,\"gy\":0.00121535,\"az\":0.9999838,\"mx\":-38.87035,\"roll\":356.2238},{\"pitch\":356.4715,\"gz\":0.0004678846,\"my\":-18.31416,\"yaw\":137.6733,\"coreMotionRoll\":359.687,\"mz\":-21.58865,\"ax\":0.005462487,\"coreMotionPitch\":359.9157,\"coreMotionYaw\":155.583,\"gx\":-0.001240588,\"ay\":-0.001470953,\"gy\":0.00120634,\"az\":0.999984,\"mx\":-39.24664,\"roll\":356.643},{\"pitch\":356.7129,\"gz\":0.001535555,\"my\":-18.23914,\"yaw\":139.3323,\"coreMotionRoll\":359.6805,\"mz\":-21.66347,\"ax\":0.005575384,\"coreMotionPitch\":359.9179,\"coreMotionYaw\":155.5877,\"gx\":-0.004444705,\"ay\":-0.001432535,\"gy\":0.002269549,\"az\":0.9999834,\"mx\":-39.24628,\"roll\":357.0155},{\"pitch\":356.9751,\"gz\":-0.0006070503,\"my\":-18.31413,\"yaw\":140.8083,\"coreMotionRoll\":359.6846,\"mz\":-21.6662,\"ax\":0.005504036,\"coreMotionPitch\":359.9132,\"coreMotionYaw\":155.5912,\"gx\":-0.002317743,\"ay\":-0.00151554,\"gy\":0.0001205732,\"az\":0.9999837,\"mx\":-38.94676,\"roll\":357.3406},{\"pitch\":357.2334,\"gz\":0.0004406889,\"my\":-18.31436,\"yaw\":142.1909,\"coreMotionRoll\":359.6828,\"mz\":-21.21237,\"ax\":0.005536242,\"coreMotionPitch\":359.9135,\"coreMotionYaw\":155.5945,\"gx\":0.00195963,\"ay\":-0.001510022,\"gy\":-0.0009344265,\"az\":0.9999835,\"mx\":-39.39609,\"roll\":357.6137},{\"pitch\":357.4207,\"gz\":-0.0006419985,\"my\":-17.78943,\"yaw\":143.5224,\"coreMotionRoll\":359.6808,\"mz\":-21.05855,\"ax\":0.005571826,\"coreMotionPitch\":359.915,\"coreMotionYaw\":155.5983,\"gx\":-0.0001831505,\"ay\":-0.00148295,\"gy\":0.001170302,\"az\":0.9999834,\"mx\":-39.69244,\"roll\":357.8459},{\"pitch\":357.6596,\"gz\":0.0004323749,\"my\":-18.31444,\"yaw\":144.6353,\"coreMotionRoll\":359.6833,\"mz\":-21.06493,\"ax\":0.005526522,\"coreMotionPitch\":359.9124,\"coreMotionYaw\":155.6019,\"gx\":-0.0001798495,\"ay\":-0.001528975,\"gy\":0.0001182057,\"az\":0.9999835,\"mx\":-39.09587,\"roll\":358.0772},{\"pitch\":357.8608,\"gz\":0.001488912,\"my\":-18.01443,\"yaw\":145.6737,\"coreMotionRoll\":359.6803,\"mz\":-21.06612,\"ax\":0.005579283,\"coreMotionPitch\":359.9117,\"coreMotionYaw\":155.606,\"gx\":-0.000177448,\"ay\":-0.001540831,\"gy\":0.0001198155,\"az\":0.9999833,\"mx\":-38.86893,\"roll\":358.2698},{\"pitch\":358.0075,\"gz\":0.0004088411,\"my\":-17.63916,\"yaw\":146.6653,\"coreMotionRoll\":359.6817,\"mz\":-21.58827,\"ax\":0.005555403,\"coreMotionPitch\":359.9109,\"coreMotionYaw\":155.611,\"gx\":-0.001252046,\"ay\":-0.001555962,\"gy\":0.001166102,\"az\":0.9999834,\"mx\":-39.09227,\"roll\":358.4285},{\"pitch\":358.1768,\"gz\":0.002557534,\"my\":-17.93916,\"yaw\":147.5289,\"coreMotionRoll\":359.6823,\"mz\":-21.58965,\"ax\":0.005545975,\"coreMotionPitch\":359.9076,\"coreMotionYaw\":155.6157,\"gx\":-0.001245,\"ay\":-0.00161348,\"gy\":-0.0009387675,\"az\":0.9999833,\"mx\":-39.01921,\"roll\":358.5721},{\"pitch\":358.3682,\"gz\":-0.0006760737,\"my\":-18.31421,\"yaw\":148.248,\"coreMotionRoll\":359.6824,\"mz\":-21.51492,\"ax\":0.005542397,\"coreMotionPitch\":359.9034,\"coreMotionYaw\":155.6165,\"gx\":0.000878058,\"ay\":-0.001686718,\"gy\":8.802882e-05,\"az\":0.9999832,\"mx\":-39.09654,\"roll\":358.7157},{\"pitch\":358.5306,\"gz\":0.001444385,\"my\":-18.23917,\"yaw\":148.9154,\"coreMotionRoll\":359.6871,\"mz\":-21.59038,\"ax\":0.00546077,\"coreMotionPitch\":359.9043,\"coreMotionYaw\":155.6199,\"gx\":0.0008839994,\"ay\":-0.001670488,\"gy\":9.921527e-05,\"az\":0.9999837,\"mx\":-39.02116,\"roll\":358.8377},{\"pitch\":358.6822,\"gz\":0.001452167,\"my\":-18.53928,\"yaw\":149.5044,\"coreMotionRoll\":359.6908,\"mz\":-21.36293,\"ax\":0.005395923,\"coreMotionPitch\":359.9009,\"coreMotionYaw\":155.6214,\"gx\":0.0008832296,\"ay\":-0.001730008,\"gy\":-0.0009614732,\"az\":0.999984,\"mx\":-39.39778,\"roll\":358.942},{\"pitch\":358.8116,\"gz\":0.0003710037,\"my\":-18.61444,\"yaw\":150.0167,\"coreMotionRoll\":359.6853,\"mz\":-21.06375,\"ax\":0.005493084,\"coreMotionPitch\":359.9046,\"coreMotionYaw\":155.6238,\"gx\":-0.00125836,\"ay\":-0.001664342,\"gy\":0.001146106,\"az\":0.9999835,\"mx\":-39.32281,\"roll\":359.0448},{\"pitch\":358.922,\"gz\":0.002490931,\"my\":-18.31439,\"yaw\":150.5208,\"coreMotionRoll\":359.6854,\"mz\":-21.1393,\"ax\":0.005490921,\"coreMotionPitch\":359.9072,\"coreMotionYaw\":155.6289,\"gx\":0.000886303,\"ay\":-0.001619029,\"gy\":0.001163056,\"az\":0.9999836,\"mx\":-39.17097,\"roll\":359.1237},{\"pitch\":359.0396,\"gz\":0.0003535832,\"my\":-18.38936,\"yaw\":150.9379,\"coreMotionRoll\":359.6839,\"mz\":-21.21638,\"ax\":0.005517023,\"coreMotionPitch\":359.9078,\"coreMotionYaw\":155.6321,\"gx\":0.001946289,\"ay\":-0.001608678,\"gy\":-0.0009843793,\"az\":0.9999835,\"mx\":-38.94658,\"roll\":359.1859},{\"pitch\":359.0986,\"gz\":-0.0007263452,\"my\":-17.93947,\"yaw\":151.3752,\"coreMotionRoll\":359.6841,\"mz\":-20.98965,\"ax\":0.005512783,\"coreMotionPitch\":359.9089,\"coreMotionYaw\":155.6332,\"gx\":-0.0001948564,\"ay\":-0.001589929,\"gy\":0.00112442,\"az\":0.9999835,\"mx\":-39.01831,\"roll\":359.2368},{\"pitch\":359.1959,\"gz\":0.001393586,\"my\":-18.31424,\"yaw\":151.7253,\"coreMotionRoll\":359.6833,\"mz\":-21.44057,\"ax\":0.00552692,\"coreMotionPitch\":359.9092,\"coreMotionYaw\":155.6384,\"gx\":0.001949256,\"ay\":-0.001585012,\"gy\":0.001141037,\"az\":0.9999835,\"mx\":-39.02142,\"roll\":359.2956},{\"pitch\":359.2708,\"gz\":0.0003224445,\"my\":-18.08926,\"yaw\":152.0519,\"coreMotionRoll\":359.6842,\"mz\":-21.36693,\"ax\":0.005511577,\"coreMotionPitch\":359.906,\"coreMotionYaw\":155.6406,\"gx\":0.00194362,\"ay\":-0.001640038,\"gy\":6.564402e-05,\"az\":0.9999835,\"mx\":-38.79486,\"roll\":359.3361},{\"pitch\":359.3327,\"gz\":0.0003183155,\"my\":-18.08928,\"yaw\":152.3552,\"coreMotionRoll\":359.6872,\"mz\":-21.36565,\"ax\":0.005459195,\"coreMotionPitch\":359.9019,\"coreMotionYaw\":155.6425,\"gx\":0.001941743,\"ay\":-0.001712768,\"gy\":-0.001000705,\"az\":0.9999836,\"mx\":-38.94485,\"roll\":359.3657},{\"pitch\":359.3968,\"gz\":0.000323965,\"my\":-18.31419,\"yaw\":152.5854,\"coreMotionRoll\":359.6831,\"mz\":-21.51749,\"ax\":0.005530021,\"coreMotionPitch\":359.8974,\"coreMotionYaw\":155.6455,\"gx\":-0.0001958435,\"ay\":-0.001790159,\"gy\":6.14348e-05,\"az\":0.9999831,\"mx\":-38.79654,\"roll\":359.4079},{\"pitch\":359.4584,\"gz\":0.0002978705,\"my\":-18.46431,\"yaw\":152.7927,\"coreMotionRoll\":359.6838,\"mz\":-21.2903,\"ax\":0.005518876,\"coreMotionPitch\":359.8971,\"coreMotionYaw\":155.649,\"gx\":0.0008709485,\"ay\":-0.001796427,\"gy\":0.001116991,\"az\":0.9999831,\"mx\":-39.09718,\"roll\":359.4492},{\"pitch\":359.4964,\"gz\":0.002430052,\"my\":-18.31444,\"yaw\":153.0076,\"coreMotionRoll\":359.6801,\"mz\":-21.06557,\"ax\":0.005582965,\"coreMotionPitch\":359.8929,\"coreMotionYaw\":155.655,\"gx\":-0.0001904502,\"ay\":-0.00186848,\"gy\":0.002196129,\"az\":0.9999827,\"mx\":-39.02087,\"roll\":359.4843},{\"pitch\":359.5302,\"gz\":0.0003003032,\"my\":-18.31436,\"yaw\":153.1934,\"coreMotionRoll\":359.6832,\"mz\":-21.21492,\"ax\":0.005528975,\"coreMotionPitch\":359.8905,\"coreMotionYaw\":155.6588,\"gx\":-0.000199997,\"ay\":-0.001911107,\"gy\":5.067863e-05,\"az\":0.9999829,\"mx\":-39.09608,\"roll\":359.5024},{\"pitch\":359.5507,\"gz\":0.001362351,\"my\":-18.16431,\"yaw\":153.3761,\"coreMotionRoll\":359.6797,\"mz\":-21.29083,\"ax\":0.005589877,\"coreMotionPitch\":359.8876,\"coreMotionYaw\":155.6641,\"gx\":-0.001266798,\"ay\":-0.00196175,\"gy\":5.469121e-05,\"az\":0.9999825,\"mx\":-38.94524,\"roll\":359.5161},{\"pitch\":359.5828,\"gz\":-0.001858426,\"my\":-18.08936,\"yaw\":153.5321,\"coreMotionRoll\":359.6801,\"mz\":-21.21565,\"ax\":0.005582666,\"coreMotionPitch\":359.8938,\"coreMotionYaw\":155.6668,\"gx\":0.00085936,\"ay\":-0.001853688,\"gy\":0.001088615,\"az\":0.9999827,\"mx\":-38.94463,\"roll\":359.5336},{\"pitch\":359.61,\"gz\":0.0002865542,\"my\":-18.01416,\"yaw\":153.6886,\"coreMotionRoll\":359.6766,\"mz\":-21.59111,\"ax\":0.005643997,\"coreMotionPitch\":359.8948,\"coreMotionYaw\":155.6696,\"gx\":0.00086443,\"ay\":-0.001836,\"gy\":-0.002083542,\"az\":0.9999824,\"mx\":-38.86971,\"roll\":359.5301},{\"pitch\":359.6421,\"gz\":0.0002677446,\"my\":-18.31416,\"yaw\":153.8206,\"coreMotionRoll\":359.6695,\"mz\":-21.58737,\"ax\":0.005768515,\"coreMotionPitch\":359.8997,\"coreMotionYaw\":155.672,\"gx\":0.0008621584,\"ay\":-0.001751178,\"gy\":-0.001028397,\"az\":0.9999818,\"mx\":-39.39665,\"roll\":359.5369},{\"pitch\":359.6776,\"gz\":0.001346652,\"my\":-18.53912,\"yaw\":153.8875,\"coreMotionRoll\":359.6727,\"mz\":-21.66612,\"ax\":0.005712711,\"coreMotionPitch\":359.8927,\"coreMotionYaw\":155.675,\"gx\":-0.002340297,\"ay\":-0.00187299,\"gy\":4.35768e-05,\"az\":0.9999819,\"mx\":-39.02322,\"roll\":359.564},{\"pitch\":359.7129,\"gz\":0.002374081,\"my\":-18.46428,\"yaw\":153.9758,\"coreMotionRoll\":359.6767,\"mz\":-21.36465,\"ax\":0.005642672,\"coreMotionPitch\":359.8937,\"coreMotionYaw\":155.678,\"gx\":0.0008655287,\"ay\":-0.001855152,\"gy\":0.001107736,\"az\":0.9999824,\"mx\":-39.17229,\"roll\":359.5866},{\"pitch\":359.7239,\"gz\":0.0002553217,\"my\":-18.31429,\"yaw\":154.0709,\"coreMotionRoll\":359.6729,\"mz\":-21.36365,\"ax\":0.005708193,\"coreMotionPitch\":359.8948,\"coreMotionYaw\":155.6818,\"gx\":-0.0002102108,\"ay\":-0.001835916,\"gy\":2.929562e-05,\"az\":0.999982,\"mx\":-39.24631,\"roll\":359.5916},{\"pitch\":359.7473,\"gz\":0.000242449,\"my\":-18.53938,\"yaw\":154.1187,\"coreMotionRoll\":359.674,\"mz\":-21.13985,\"ax\":0.005689398,\"coreMotionPitch\":359.8947,\"coreMotionYaw\":155.6855,\"gx\":-0.001281816,\"ay\":-0.001837701,\"gy\":0.001086296,\"az\":0.9999821,\"mx\":-39.17244,\"roll\":359.6113},{\"pitch\":359.7703,\"gz\":0.000219234,\"my\":-18.31429,\"yaw\":154.1891,\"coreMotionRoll\":359.6696,\"mz\":-21.36493,\"ax\":0.005766772,\"coreMotionPitch\":359.8937,\"coreMotionYaw\":155.6887,\"gx\":0.001920773,\"ay\":-0.001855326,\"gy\":1.864502e-05,\"az\":0.9999816,\"mx\":-39.09631,\"roll\":359.6132},{\"pitch\":359.7554,\"gz\":-0.0008262666,\"my\":-18.08928,\"yaw\":154.2985,\"coreMotionRoll\":359.6705,\"mz\":-21.36183,\"ax\":0.005750468,\"coreMotionPitch\":359.8961,\"coreMotionYaw\":155.6918,\"gx\":-0.0002199278,\"ay\":-0.001813869,\"gy\":-0.001049811,\"az\":0.9999818,\"mx\":-39.39485,\"roll\":359.5996},{\"pitch\":359.7643,\"gz\":-0.000852853,\"my\":-18.08928,\"yaw\":154.3638,\"coreMotionRoll\":359.6667,\"mz\":-21.36565,\"ax\":0.005817236,\"coreMotionPitch\":359.8947,\"coreMotionYaw\":155.6954,\"gx\":-0.0002223363,\"ay\":-0.001837735,\"gy\":0.001067633,\"az\":0.9999814,\"mx\":-38.94485,\"roll\":359.6096},{\"pitch\":359.7414,\"gz\":0.0002006195,\"my\":-17.86411,\"yaw\":154.4743,\"coreMotionRoll\":359.6642,\"mz\":-21.73819,\"ax\":0.005859886,\"coreMotionPitch\":359.8987,\"coreMotionYaw\":155.6987,\"gx\":-0.002357262,\"ay\":-0.001767978,\"gy\":0.003195216,\"az\":0.9999813,\"mx\":-39.16895,\"roll\":359.6188},{\"pitch\":359.7838,\"gz\":0.002313891,\"my\":-18.38916,\"yaw\":154.5129,\"coreMotionRoll\":359.6642,\"mz\":-21.58884,\"ax\":0.005861111,\"coreMotionPitch\":359.9065,\"coreMotionYaw\":155.7029,\"gx\":0.0008558103,\"ay\":-0.001631279,\"gy\":0.003211278,\"az\":0.9999815,\"mx\":-39.24713,\"roll\":359.6435},{\"pitch\":359.8412,\"gz\":0.001245286,\"my\":-18.46422,\"yaw\":154.5028,\"coreMotionRoll\":359.6617,\"mz\":-21.44284,\"ax\":0.005905428,\"coreMotionPitch\":359.9144,\"coreMotionYaw\":155.7062,\"gx\":0.001919213,\"ay\":-0.001493907,\"gy\":0.001075008,\"az\":0.9999815,\"mx\":-38.79741,\"roll\":359.6578},{\"pitch\":359.8787,\"gz\":0.000178749,\"my\":-18.53928,\"yaw\":154.4913,\"coreMotionRoll\":359.6616,\"mz\":-21.36676,\"ax\":0.005905708,\"coreMotionPitch\":359.9118,\"coreMotionYaw\":155.7085,\"gx\":0.001915198,\"ay\":-0.001538745,\"gy\":0.001066701,\"az\":0.9999814,\"mx\":-38.94778,\"roll\":359.6668},{\"pitch\":359.8863,\"gz\":0.0001884511,\"my\":-18.53935,\"yaw\":154.4988,\"coreMotionRoll\":359.6617,\"mz\":-21.21484,\"ax\":0.005903488,\"coreMotionPitch\":359.9173,\"coreMotionYaw\":155.7122,\"gx\":-0.0002242047,\"ay\":-0.001443823,\"gy\":2.554371e-06,\"az\":0.9999815,\"mx\":-39.17256,\"roll\":359.6633},{\"pitch\":359.8768,\"gz\":-0.0008971599,\"my\":-18.23938,\"yaw\":154.5172,\"coreMotionRoll\":359.6648,\"mz\":-21.1423,\"ax\":0.005849991,\"coreMotionPitch\":359.9123,\"coreMotionYaw\":155.7166,\"gx\":-0.001299684,\"ay\":-0.00153151,\"gy\":0.002113198,\"az\":0.9999817,\"mx\":-38.7955,\"roll\":359.6696},{\"pitch\":359.8603,\"gz\":0.001237156,\"my\":-18.08939,\"yaw\":154.5763,\"coreMotionRoll\":359.664,\"mz\":-21.14066,\"ax\":0.005864511,\"coreMotionPitch\":359.904,\"coreMotionYaw\":155.719,\"gx\":-0.0002242621,\"ay\":-0.001675481,\"gy\":0.001068724,\"az\":0.9999814,\"mx\":-38.94452,\"roll\":359.662},{\"pitch\":359.8472,\"gz\":-0.0008982651,\"my\":-18.08928,\"yaw\":154.6257,\"coreMotionRoll\":359.6601,\"mz\":-21.36438,\"ax\":0.00593221,\"coreMotionPitch\":359.9021,\"coreMotionYaw\":155.7214,\"gx\":-0.0002350071,\"ay\":-0.001708112,\"gy\":-1.464539e-05,\"az\":0.9999809,\"mx\":-39.09485,\"roll\":359.6491},{\"pitch\":359.8421,\"gz\":0.002284326,\"my\":-18.08916,\"yaw\":154.6852,\"coreMotionRoll\":359.667,\"mz\":-21.58937,\"ax\":0.005811661,\"coreMotionPitch\":359.906,\"coreMotionYaw\":155.7257,\"gx\":-0.000226808,\"ay\":-0.001640512,\"gy\":0.001068849,\"az\":0.9999818,\"mx\":-39.09518,\"roll\":359.6495},{\"pitch\":359.8734,\"gz\":0.002284387,\"my\":-18.4641,\"yaw\":154.6684,\"coreMotionRoll\":359.6665,\"mz\":-21.7422,\"ax\":0.005820593,\"coreMotionPitch\":359.9047,\"coreMotionYaw\":155.7283,\"gx\":-0.0002274263,\"ay\":-0.001663727,\"gy\":0.001069049,\"az\":0.9999817,\"mx\":-38.87286,\"roll\":359.6662},{\"pitch\":359.8829,\"gz\":0.001216387,\"my\":-18.38916,\"yaw\":154.6761,\"coreMotionRoll\":359.6671,\"mz\":-21.5901,\"ax\":0.005810787,\"coreMotionPitch\":359.9101,\"coreMotionYaw\":155.731,\"gx\":-0.0002337184,\"ay\":-0.001568523,\"gy\":-5.808123e-06,\"az\":0.9999819,\"mx\":-39.09714,\"roll\":359.6656},{\"pitch\":359.9116,\"gz\":0.0001565078,\"my\":-18.68915,\"yaw\":154.614,\"coreMotionRoll\":359.662,\"mz\":-21.5934,\"ax\":0.005899329,\"coreMotionPitch\":359.9053,\"coreMotionYaw\":155.7327,\"gx\":-0.001306321,\"ay\":-0.001652278,\"gy\":-1.414373e-05,\"az\":0.9999812,\"mx\":-38.79909,\"roll\":359.6776},{\"pitch\":359.9003,\"gz\":0.002294919,\"my\":-18.31438,\"yaw\":154.6218,\"coreMotionRoll\":359.6581,\"mz\":-21.14313,\"ax\":0.005966361,\"coreMotionPitch\":359.909,\"coreMotionYaw\":155.7365,\"gx\":-0.002370207,\"ay\":-0.001589088,\"gy\":-0.001062803,\"az\":0.9999809,\"mx\":-38.72098,\"roll\":359.6664},{\"pitch\":359.8978,\"gz\":0.001198698,\"my\":-18.31451,\"yaw\":154.6373,\"coreMotionRoll\":359.6576,\"mz\":-20.91621,\"ax\":0.005976182,\"coreMotionPitch\":359.9089,\"coreMotionYaw\":155.7399,\"gx\":-0.0002405035,\"ay\":-0.001589527,\"gy\":-1.635341e-05,\"az\":0.9999809,\"mx\":-38.94565,\"roll\":359.6612},{\"pitch\":359.8803,\"gz\":0.002249905,\"my\":-18.01439,\"yaw\":154.6942,\"coreMotionRoll\":359.6583,\"mz\":-21.14111,\"ax\":0.005963117,\"coreMotionPitch\":359.9108,\"coreMotionYaw\":155.7422,\"gx\":-0.0002367597,\"ay\":-0.001557356,\"gy\":0.002115191,\"az\":0.999981,\"mx\":-38.86903,\"roll\":359.6617},{\"pitch\":359.8997,\"gz\":0.0001169614,\"my\":-18.38951,\"yaw\":154.6834,\"coreMotionRoll\":359.6585,\"mz\":-20.9164,\"ax\":0.005960138,\"coreMotionPitch\":359.9127,\"coreMotionYaw\":155.7455,\"gx\":0.00082065,\"ay\":-0.00152324,\"gy\":-3.083424e-05,\"az\":0.9999811,\"mx\":-38.94614,\"roll\":359.6624},{\"pitch\":359.8754,\"gz\":0.0001087592,\"my\":-17.93921,\"yaw\":154.7589,\"coreMotionRoll\":359.6603,\"mz\":-21.51274,\"ax\":0.005928389,\"coreMotionPitch\":359.9172,\"coreMotionYaw\":155.7483,\"gx\":0.0008169377,\"ay\":-0.00144579,\"gy\":-3.622272e-05,\"az\":0.9999814,\"mx\":-39.24409,\"roll\":359.645},{\"pitch\":359.8825,\"gz\":0.0022508,\"my\":-18.01428,\"yaw\":154.8048,\"coreMotionRoll\":359.6567,\"mz\":-21.36546,\"ax\":0.00599105,\"coreMotionPitch\":359.9157,\"coreMotionYaw\":155.7526,\"gx\":0.001892513,\"ay\":-0.001470521,\"gy\":-0.002141378,\"az\":0.999981,\"mx\":-38.94437,\"roll\":359.6291},{\"pitch\":359.9279,\"gz\":0.001124281,\"my\":-18.38916,\"yaw\":154.7852,\"coreMotionRoll\":359.6555,\"mz\":-21.59138,\"ax\":0.006012325,\"coreMotionPitch\":359.921,\"coreMotionYaw\":155.7544,\"gx\":0.004022044,\"ay\":-0.001378863,\"gy\":0.00314972,\"az\":0.999981,\"mx\":-38.94714,\"roll\":359.6591},{\"pitch\":359.9285,\"gz\":0.0001142053,\"my\":-18.38928,\"yaw\":154.7657,\"coreMotionRoll\":359.6596,\"mz\":-21.36638,\"ax\":0.005941701,\"coreMotionPitch\":359.9223,\"coreMotionYaw\":155.7583,\"gx\":-0.001324355,\"ay\":-0.001356568,\"gy\":-3.943795e-05,\"az\":0.9999814,\"mx\":-38.94681,\"roll\":359.6627},{\"pitch\":359.9272,\"gz\":0.001159225,\"my\":-18.31424,\"yaw\":154.7803,\"coreMotionRoll\":359.6572,\"mz\":-21.43929,\"ax\":0.005982703,\"coreMotionPitch\":359.9214,\"coreMotionYaw\":155.761,\"gx\":0.0008127298,\"ay\":-0.001371237,\"gy\":-3.747539e-05,\"az\":0.9999812,\"mx\":-39.17143,\"roll\":359.6584},{\"pitch\":359.9293,\"gz\":0.00113701,\"my\":-18.2394,\"yaw\":154.7987,\"coreMotionRoll\":359.6617,\"mz\":-21.13976,\"ax\":0.005904581,\"coreMotionPitch\":359.9175,\"coreMotionYaw\":155.763,\"gx\":0.001877649,\"ay\":-0.001439381,\"gy\":0.001018284,\"az\":0.9999815,\"mx\":-39.09549,\"roll\":359.6617},{\"pitch\":359.9007,\"gz\":0.0001002991,\"my\":-17.8644,\"yaw\":154.868,\"coreMotionRoll\":359.664,\"mz\":-21.1382,\"ax\":0.005863473,\"coreMotionPitch\":359.9219,\"coreMotionYaw\":155.765,\"gx\":0.0008056783,\"ay\":-0.001363285,\"gy\":-0.002173202,\"az\":0.9999819,\"mx\":-39.16805,\"roll\":359.6357},{\"pitch\":359.902,\"gz\":-0.001005166,\"my\":-17.86438,\"yaw\":154.881,\"coreMotionRoll\":359.6636,\"mz\":-21.1433,\"ax\":0.00587072,\"coreMotionPitch\":359.9174,\"coreMotionYaw\":155.7661,\"gx\":0.0007983968,\"ay\":-0.001441308,\"gy\":0.002057342,\"az\":0.9999817,\"mx\":-38.56805,\"roll\":359.6506},{\"pitch\":359.8935,\"gz\":5.410878e-05,\"my\":-17.78923,\"yaw\":154.9343,\"coreMotionRoll\":359.6636,\"mz\":-21.43991,\"ax\":0.005870806,\"coreMotionPitch\":359.9207,\"coreMotionYaw\":155.7689,\"gx\":0.00186675,\"ay\":-0.00138456,\"gy\":0.0009994169,\"az\":0.9999818,\"mx\":-38.94301,\"roll\":359.6479},{\"pitch\":359.9095,\"gz\":6.854608e-05,\"my\":-18.01427,\"yaw\":154.9335,\"coreMotionRoll\":359.6608,\"mz\":-21.36739,\"ax\":0.005920298,\"coreMotionPitch\":359.9296,\"coreMotionYaw\":155.7714,\"gx\":0.0007986229,\"ay\":-0.001228037,\"gy\":-6.097496e-05,\"az\":0.9999817,\"mx\":-38.71938,\"roll\":359.6511},{\"pitch\":359.9266,\"gz\":8.220971e-05,\"my\":-18.2392,\"yaw\":154.9061,\"coreMotionRoll\":359.6626,\"mz\":-21.5173,\"ax\":0.005888144,\"coreMotionPitch\":359.9278,\"coreMotionYaw\":155.7736,\"gx\":-0.0002743814,\"ay\":-0.001260467,\"gy\":-0.002187825,\"az\":0.9999819,\"mx\":-38.79605,\"roll\":359.6481},{\"pitch\":359.9484,\"gz\":0.001127827,\"my\":-18.2392,\"yaw\":154.8887,\"coreMotionRoll\":359.6602,\"mz\":-21.5173,\"ax\":0.005930542,\"coreMotionPitch\":359.9287,\"coreMotionYaw\":155.7751,\"gx\":0.00186594,\"ay\":-0.001243927,\"gy\":-0.001118763,\"az\":0.9999816,\"mx\":-38.79605,\"roll\":359.6502},{\"pitch\":359.955,\"gz\":0.001124712,\"my\":-18.38921,\"yaw\":154.8632,\"coreMotionRoll\":359.6668,\"mz\":-21.51639,\"ax\":0.005815781,\"coreMotionPitch\":359.9264,\"coreMotionYaw\":155.7782,\"gx\":-0.0002766182,\"ay\":-0.001284062,\"gy\":-6.098098e-05,\"az\":0.9999822,\"mx\":-38.94702,\"roll\":359.661},{\"pitch\":359.9499,\"gz\":0.001109007,\"my\":-18.2394,\"yaw\":154.8578,\"coreMotionRoll\":359.6606,\"mz\":-21.14166,\"ax\":0.005922814,\"coreMotionPitch\":359.9247,\"coreMotionYaw\":155.7823,\"gx\":-0.0002823584,\"ay\":-0.001313822,\"gy\":0.0009953977,\"az\":0.9999816,\"mx\":-38.8705,\"roll\":359.6681},{\"pitch\":359.9657,\"gz\":4.913116e-05,\"my\":-18.53937,\"yaw\":154.7884,\"coreMotionRoll\":359.6642,\"mz\":-21.14432,\"ax\":0.005860441,\"coreMotionPitch\":359.9213,\"coreMotionYaw\":155.7842,\"gx\":-0.001354884,\"ay\":-0.001373942,\"gy\":0.0009879353,\"az\":0.9999819,\"mx\":-38.64745,\"roll\":359.6866},{\"pitch\":359.9386,\"gz\":2.78604e-05,\"my\":-17.86423,\"yaw\":154.8312,\"coreMotionRoll\":359.6661,\"mz\":-21.44202,\"ax\":0.005827811,\"coreMotionPitch\":359.9259,\"coreMotionYaw\":155.7871,\"gx\":0.0007762215,\"ay\":-0.00129347,\"gy\":0.0009824397,\"az\":0.9999822,\"mx\":-38.71851,\"roll\":359.6759},{\"pitch\":359.9365,\"gz\":0.001095574,\"my\":-17.93927,\"yaw\":154.852,\"coreMotionRoll\":359.6679,\"mz\":-21.36847,\"ax\":0.005795379,\"coreMotionPitch\":359.9203,\"coreMotionYaw\":155.7909,\"gx\":0.001841511,\"ay\":-0.001390649,\"gy\":-0.001136112,\"az\":0.9999822,\"mx\":-38.56888,\"roll\":359.6636},{\"pitch\":359.9454,\"gz\":3.569209e-05,\"my\":-18.08914,\"yaw\":154.8372,\"coreMotionRoll\":359.668,\"mz\":-21.59448,\"ax\":0.00579413,\"coreMotionPitch\":359.9279,\"coreMotionYaw\":155.7944,\"gx\":0.0007696724,\"ay\":-0.001259047,\"gy\":-0.001144375,\"az\":0.9999824,\"mx\":-38.49519,\"roll\":359.6614},{\"pitch\":359.9269,\"gz\":0.001081324,\"my\":-17.86421,\"yaw\":154.908,\"coreMotionRoll\":359.6652,\"mz\":-21.5132,\"ax\":0.005843141,\"coreMotionPitch\":359.9301,\"coreMotionYaw\":155.7946,\"gx\":0.002903674,\"ay\":-0.001219491,\"gy\":-0.001140548,\"az\":0.9999822,\"mx\":-39.16861,\"roll\":359.6419},{\"pitch\":359.9437,\"gz\":0.00108032,\"my\":-18.16411,\"yaw\":154.9007,\"coreMotionRoll\":359.6626,\"mz\":-21.66711,\"ax\":0.0058885,\"coreMotionPitch\":359.9286,\"coreMotionYaw\":155.7983,\"gx\":0.001834843,\"ay\":-0.001246833,\"gy\":-7.860897e-05,\"az\":0.9999819,\"mx\":-38.79579,\"roll\":359.65},{\"pitch\":359.9607,\"gz\":-0.001062647,\"my\":-18.5392,\"yaw\":154.8401,\"coreMotionRoll\":359.663,\"mz\":-21.51675,\"ax\":0.005882406,\"coreMotionPitch\":359.9214,\"coreMotionYaw\":155.8016,\"gx\":-0.0003168708,\"ay\":-0.001372168,\"gy\":0.002023863,\"az\":0.9999818,\"mx\":-38.948,\"roll\":359.676},{\"pitch\":359.9751,\"gz\":-3.725319e-06,\"my\":-18.38914,\"yaw\":154.7792,\"coreMotionRoll\":359.6585,\"mz\":-21.59584,\"ax\":0.005960548,\"coreMotionPitch\":359.9199,\"coreMotionYaw\":155.8051,\"gx\":-0.0003201356,\"ay\":-0.001398777,\"gy\":0.00202894,\"az\":0.9999813,\"mx\":-38.42215,\"roll\":359.6944},{\"pitch\":359.9478,\"gz\":0.001086104,\"my\":-18.16438,\"yaw\":154.8015,\"coreMotionRoll\":359.6562,\"mz\":-21.14084,\"ax\":0.006001029,\"coreMotionPitch\":359.913,\"coreMotionYaw\":155.8081,\"gx\":-0.0003170621,\"ay\":-0.001518569,\"gy\":-0.001145929,\"az\":0.9999809,\"mx\":-38.94501,\"roll\":359.6728},{\"pitch\":359.9492,\"gz\":-1.810729e-05,\"my\":-18.23935,\"yaw\":154.787,\"coreMotionRoll\":359.6555,\"mz\":-21.21793,\"ax\":0.006012845,\"coreMotionPitch\":359.9166,\"coreMotionYaw\":155.8105,\"gx\":0.0007412111,\"ay\":-0.001454916,\"gy\":0.002024856,\"az\":0.9999809,\"mx\":-38.72061,\"roll\":359.681},{\"pitch\":359.9413,\"gz\":-0.001069375,\"my\":-17.8643,\"yaw\":154.7874,\"coreMotionRoll\":359.6542,\"mz\":-21.29648,\"ax\":0.006035341,\"coreMotionPitch\":359.918,\"coreMotionYaw\":155.812,\"gx\":0.0007306023,\"ay\":-0.00143063,\"gy\":-0.001171363,\"az\":0.9999807,\"mx\":-38.19328,\"roll\":359.6646},{\"pitch\":359.908,\"gz\":0.002122753,\"my\":-17.78928,\"yaw\":154.8655,\"coreMotionRoll\":359.658,\"mz\":-21.36491,\"ax\":0.005969443,\"coreMotionPitch\":359.9176,\"coreMotionYaw\":155.8146,\"gx\":0.0007415022,\"ay\":-0.001438308,\"gy\":-8.313576e-05,\"az\":0.9999812,\"mx\":-38.9429,\"roll\":359.6474},{\"pitch\":359.9121,\"gz\":-2.616154e-05,\"my\":-18.08913,\"yaw\":154.8723,\"coreMotionRoll\":359.6625,\"mz\":-21.66629,\"ax\":0.005890884,\"coreMotionPitch\":359.9182,\"coreMotionYaw\":155.8158,\"gx\":0.0007270203,\"ay\":-0.001427203,\"gy\":0.0009576986,\"az\":0.9999816,\"mx\":-38.8703,\"roll\":359.655},{\"pitch\":359.8958,\"gz\":-2.050936e-05,\"my\":-18.01402,\"yaw\":154.8943,\"coreMotionRoll\":359.6563,\"mz\":-21.89046,\"ax\":0.005997957,\"coreMotionPitch\":359.9169,\"coreMotionYaw\":155.8181,\"gx\":-0.001410058,\"ay\":-0.001450792,\"gy\":0.002019742,\"az\":0.999981,\"mx\":-38.94514,\"roll\":359.6608},{\"pitch\":359.8979,\"gz\":-0.001088004,\"my\":-18.31416,\"yaw\":154.8795,\"coreMotionRoll\":359.6522,\"mz\":-21.58929,\"ax\":0.006070395,\"coreMotionPitch\":359.9136,\"coreMotionYaw\":155.8198,\"gx\":-0.001421549,\"ay\":-0.00150778,\"gy\":0.0009465707,\"az\":0.9999804,\"mx\":-39.17165,\"roll\":359.6646},{\"pitch\":359.8875,\"gz\":0.001032306,\"my\":-18.01428,\"yaw\":154.936,\"coreMotionRoll\":359.6476,\"mz\":-21.36165,\"ax\":0.006150594,\"coreMotionPitch\":359.9073,\"coreMotionYaw\":155.8235,\"gx\":0.00178377,\"ay\":-0.001617995,\"gy\":-0.001162973,\"az\":0.9999798,\"mx\":-39.39436,\"roll\":359.642},{\"pitch\":359.9281,\"gz\":0.001039025,\"my\":-18.6144,\"yaw\":154.8738,\"coreMotionRoll\":359.6471,\"mz\":-21.14131,\"ax\":0.00615899,\"coreMotionPitch\":359.9081,\"coreMotionYaw\":155.8264,\"gx\":0.0007152386,\"ay\":-0.00160405,\"gy\":-0.001162,\"az\":0.9999797,\"mx\":-39.02293,\"roll\":359.651},{\"pitch\":359.9289,\"gz\":-0.002163854,\"my\":-18.38931,\"yaw\":154.8312,\"coreMotionRoll\":359.6569,\"mz\":-21.29138,\"ax\":0.0059877,\"coreMotionPitch\":359.909,\"coreMotionYaw\":155.827,\"gx\":-0.001440003,\"ay\":-0.001588832,\"gy\":-0.0001279038,\"az\":0.9999808,\"mx\":-38.94669,\"roll\":359.658},{\"pitch\":359.9239,\"gz\":0.00100519,\"my\":-18.31429,\"yaw\":154.832,\"coreMotionRoll\":359.6603,\"mz\":-21.36493,\"ax\":0.005929435,\"coreMotionPitch\":359.9109,\"coreMotionYaw\":155.8292,\"gx\":-0.0003676236,\"ay\":-0.001554985,\"gy\":0.002018483,\"az\":0.9999812,\"mx\":-39.09631,\"roll\":359.67},{\"pitch\":359.9006,\"gz\":-3.04549e-05,\"my\":-18.0891,\"yaw\":154.8741,\"coreMotionRoll\":359.6591,\"mz\":-21.73746,\"ax\":0.005948895,\"coreMotionPitch\":359.9168,\"coreMotionYaw\":155.8313,\"gx\":-0.0003708867,\"ay\":-0.001451327,\"gy\":-0.002235075,\"az\":0.9999812,\"mx\":-39.3204,\"roll\":359.645},{\"pitch\":359.9078,\"gz\":-6.88315e-05,\"my\":-18.16428,\"yaw\":154.8736,\"coreMotionRoll\":359.6614,\"mz\":-21.36583,\"ax\":0.005908887,\"coreMotionPitch\":359.9109,\"coreMotionYaw\":155.8313,\"gx\":0.0006895815,\"ay\":-0.001554255,\"gy\":0.0009464303,\"az\":0.9999813,\"mx\":-38.94534,\"roll\":359.6544},{\"pitch\":359.8741,\"gz\":0.001019934,\"my\":-17.93921,\"yaw\":154.9464,\"coreMotionRoll\":359.6666,\"mz\":-21.51082,\"ax\":0.005818787,\"coreMotionPitch\":359.9037,\"coreMotionYaw\":155.8331,\"gx\":-0.0003755821,\"ay\":-0.001681595,\"gy\":-0.001166198,\"az\":0.9999816,\"mx\":-39.4691,\"roll\":359.6369},{\"pitch\":359.8997,\"gz\":-0.001128626,\"my\":-18.38913,\"yaw\":154.9127,\"coreMotionRoll\":359.6644,\"mz\":-21.66447,\"ax\":0.005857465,\"coreMotionPitch\":359.9039,\"coreMotionYaw\":155.8342,\"gx\":0.0006781357,\"ay\":-0.001677349,\"gy\":-0.001185907,\"az\":0.9999815,\"mx\":-39.17226,\"roll\":359.6441},{\"pitch\":359.8892,\"gz\":-6.672789e-05,\"my\":-18.53928,\"yaw\":154.8892,\"coreMotionRoll\":359.6684,\"mz\":-21.36229,\"ax\":0.005786981,\"coreMotionPitch\":359.8938,\"coreMotionYaw\":155.8386,\"gx\":-0.003598522,\"ay\":-0.001853216,\"gy\":0.002008032,\"az\":0.9999815,\"mx\":-39.47278,\"roll\":359.6664},{\"pitch\":359.891,\"gz\":-0.001137792,\"my\":-18.4642,\"yaw\":154.8833,\"coreMotionRoll\":359.6646,\"mz\":-21.51083,\"ax\":0.005854308,\"coreMotionPitch\":359.9018,\"coreMotionYaw\":155.8406,\"gx\":-0.0003965655,\"ay\":-0.001713317,\"gy\":-0.0001247618,\"az\":0.9999814,\"mx\":-39.62251,\"roll\":359.6645},{\"pitch\":359.8979,\"gz\":-8.093432e-05,\"my\":-18.46428,\"yaw\":154.8588,\"coreMotionRoll\":359.6733,\"mz\":-21.36401,\"ax\":0.005701552,\"coreMotionPitch\":359.9037,\"coreMotionYaw\":155.8414,\"gx\":-0.001467434,\"ay\":-0.001681254,\"gy\":0.0009459883,\"az\":0.9999824,\"mx\":-39.24728,\"roll\":359.6767},{\"pitch\":359.8862,\"gz\":0.0009794546,\"my\":-18.08939,\"yaw\":154.9023,\"coreMotionRoll\":359.6749,\"mz\":-21.13748,\"ax\":0.005673563,\"coreMotionPitch\":359.903,\"coreMotionYaw\":155.8415,\"gx\":0.0006673924,\"ay\":-0.00169191,\"gy\":-0.001168901,\"az\":0.9999825,\"mx\":-39.31952,\"roll\":359.6613},{\"pitch\":359.8816,\"gz\":0.002040521,\"my\":-17.93906,\"yaw\":154.9713,\"coreMotionRoll\":359.6729,\"mz\":-21.81145,\"ax\":0.005708294,\"coreMotionPitch\":359.9087,\"coreMotionYaw\":155.844,\"gx\":0.0028079,\"ay\":-0.001593561,\"gy\":-0.002222034,\"az\":0.9999824,\"mx\":-39.39455,\"roll\":359.6398},{\"pitch\":359.8874,\"gz\":-0.0001093571,\"my\":-18.08916,\"yaw\":154.9985,\"coreMotionRoll\":359.6671,\"mz\":-21.58682,\"ax\":0.005809215,\"coreMotionPitch\":359.9113,\"coreMotionYaw\":155.8464,\"gx\":0.001726137,\"ay\":-0.001548384,\"gy\":-0.0001169273,\"az\":0.9999819,\"mx\":-39.39519,\"roll\":359.6408},{\"pitch\":359.8742,\"gz\":0.0009871266,\"my\":-17.86417,\"yaw\":155.0607,\"coreMotionRoll\":359.6652,\"mz\":-21.58627,\"ax\":0.005843685,\"coreMotionPitch\":359.9178,\"coreMotionYaw\":155.8468,\"gx\":0.0006616887,\"ay\":-0.001435604,\"gy\":-0.00329125,\"az\":0.9999819,\"mx\":-39.39372,\"roll\":359.6176},{\"pitch\":359.8598,\"gz\":0.0009618782,\"my\":-17.86414,\"yaw\":155.1042,\"coreMotionRoll\":359.67,\"mz\":-21.66255,\"ax\":0.005758945,\"coreMotionPitch\":359.9113,\"coreMotionYaw\":155.8492,\"gx\":-0.001481076,\"ay\":-0.001548762,\"gy\":0.000955312,\"az\":0.9999822,\"mx\":-39.24384,\"roll\":359.6297},{\"pitch\":359.894,\"gz\":-0.0001161748,\"my\":-18.31416,\"yaw\":155.082,\"coreMotionRoll\":359.669,\"mz\":-21.58737,\"ax\":0.005776789,\"coreMotionPitch\":359.9093,\"coreMotionYaw\":155.8497,\"gx\":0.001716102,\"ay\":-0.001582565,\"gy\":-0.001177735,\"az\":0.9999821,\"mx\":-39.39665,\"roll\":359.639},{\"pitch\":359.9231,\"gz\":-0.001199222,\"my\":-18.4642,\"yaw\":155.0324,\"coreMotionRoll\":359.6638,\"mz\":-21.51338,\"ax\":0.00586744,\"coreMotionPitch\":359.9129,\"coreMotionYaw\":155.8518,\"gx\":0.0006439787,\"ay\":-0.001520748,\"gy\":0.001997693,\"az\":0.9999816,\"mx\":-39.32251,\"roll\":359.6658},{\"pitch\":359.9205,\"gz\":-0.001183938,\"my\":-18.08928,\"yaw\":155.0194,\"coreMotionRoll\":359.6635,\"mz\":-21.36565,\"ax\":0.005873903,\"coreMotionPitch\":359.9113,\"coreMotionYaw\":155.8521,\"gx\":-0.0004283994,\"ay\":-0.001548714,\"gy\":-0.0001248647,\"az\":0.9999815,\"mx\":-38.94485,\"roll\":359.6652},{\"pitch\":359.9661,\"gz\":-0.00134532,\"my\":-18.31432,\"yaw\":154.9901,\"coreMotionRoll\":359.6526,\"mz\":-21.28929,\"ax\":0.00606255,\"coreMotionPitch\":359.9108,\"coreMotionYaw\":155.8541,\"gx\":0.007046946,\"ay\":-0.001557158,\"gy\":0.01472897,\"az\":0.9999804,\"mx\":-39.1712,\"roll\":359.7451}]"; + NSData * sampleData = [sampleJSON dataUsingEncoding:NSUTF8StringEncoding]; + NSError * error; + NSArray * samples = (NSArray *)[NSJSONSerialization JSONObjectWithData:sampleData + options:0 + error:&error]; + MadgwickSensorFusion * madgwickSensorFusion = [[MadgwickSensorFusion alloc] initWithSampleFrequencyHz:10.0 + beta:0.6045997880780726f]; + for (NSDictionary * sample in samples) + { + [madgwickSensorFusion updateWithGyroscopeX:[sample[@"gx"] floatValue] + gyroscopeY:[sample[@"gy"] floatValue] + gyroscopeZ:[sample[@"gz"] floatValue] + accelerometerX:[sample[@"ax"] floatValue] + accelerometerY:[sample[@"ay"] floatValue] + accelerometerZ:[sample[@"az"] floatValue] + magnetometerX:[sample[@"mx"] floatValue] + magnetometerY:[sample[@"my"] floatValue] + magnetometerZ:[sample[@"mz"] floatValue]]; + } + + // Calculate roll, pitch, yaw. + float roll, pitch, yaw; + CalculateEulerAnglesFromQuaternion([madgwickSensorFusion q0], + [madgwickSensorFusion q1], + [madgwickSensorFusion q2], + [madgwickSensorFusion q3], + &roll, + &pitch, + &yaw); + roll = NormalizeAngle(RadiansToDegrees(roll)); + pitch = NormalizeAngle(RadiansToDegrees(pitch)); + yaw = NormalizeAngle(RadiansToDegrees(yaw)); + + // Test. + XCTAssertTrue((NSInteger)(roll * 100.0) == (NSInteger)([[samples lastObject][@"roll"] floatValue] * 100.0)); + XCTAssertTrue((NSInteger)(pitch * 100.0) == (NSInteger)([[samples lastObject][@"pitch"] floatValue] * 100.0)); + XCTAssertTrue((NSInteger)(yaw * 100.0) == (NSInteger)([[samples lastObject][@"yaw"] floatValue] * 100.0)); +} + +@end diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6d0049 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# MadgwickAHRS + +[![GitHub license](https://img.shields.io/aur/license/yaourt.svg)](https://raw.githubusercontent.com/softwarenerd/MadgwickAHRS/master/LICENSE.md) ![platforms](https://img.shields.io/badge/platforms-iOS%20-lightgrey.svg) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + +[MadgwickAHRS](http://www.x-io.co.uk/open-source-imu-and-ahrs-algorithms/) is a port of the [C implementation](http://www.x-io.co.uk/res/sw/madgwick_algorithm_c.zip) of MadgwickAHRS to an iOS Framework written in Objective-C. + +## Getting Started + +MadgwickAHRS should be used via [Carthage dependency manager](https://github.com/Carthage/Carthage). Follow the [Carthage build instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos) for building for iOS. Essentially: + +#### Add MadgwickAHRS to your Cartfile + +```github "Softwarenerd/MadgwickAHRS"``` + +#### Add MadgwickAHRS.framework to Linked Frameworks and Libraries + +![AddFramework](Documentation/AddFramework.png) + +#### Add Copy Frameworks Run Script + +Add a run script that uses the ```carthage copy-frameworks``` command to copy the ```MadgwickAHRS.framework``` + +![RunScript](Documentation/RunScript.png) + +## Usage + +See [CoreMotionMadgwickTestDriver](https://raw.githubusercontent.com/softwarenerd/MadgwickAHRS/master/MadgwickAHRS/CoreMotionMadgwickTestDriver.m) for a sample of how to use MadgwickAHRS. + +## RawIMU Sample App + +The [RawIMU](https://github.com/softwarenerd/RawIMU) project contains a sample app that uses MadgwickAHRS. + +#### Clone Raw IMU + +`~/Code git clone git@github.com:softwarenerd/RawIMU.git` + +#### Optionally, Build Carthage Dependencies + +```~/Code/RawIMU carthage bootstrap``` + +## Known Issues + +* Additional unit tests would be nice. + +## License + +MadgwickAHRS is released under the [GNU General Public License](LICENSE.md).