diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..aea4205 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.html linguist-language=Python \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e8dc72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,137 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +.idea/ +docs/images +build/ +.idea/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +html/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +!/html/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..44ac8ba --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +## telemetrix-uno-r4 + +# Telemetrix For The Arduino UNO R4 Minima and WIFI + + +This Python package allows you to monitor and control an UNO R4 Minima or +WIFI board by simply writing a Python script using one of the telemetrix--uno-r4 APIs. +APIs are provided for both synchronous and asyncio implementations. + +| Feature | Minima | WIFI | Notes | +|------------------------------------|:------:|:----:|:-------------------------------:| +| Analog Input | X | X | | +| Analog Output (PWM) | X | X | | +| Digital Input | X | X | | +| Digital Output | X | X | | +| i2c Primitives | X | X | | +| Servo Motor Control | X | X | Currently, BLE is not supported | +| DHT Temperature/Humidity Sensor | X | X | | +| HC-SR04 Sonar Distance Sensor | X | X | | +| SPI Primitives | X | X | | +| Scrolling Message Support | | X | | +| Integrated Debugging Aids Provided | X | X | | +| Examples ProvidedFor All Features | X | X | | + +For the Minima, communication with the Python client is supported by a USBSerial +transport. + +You may choose a WIFI, USBSerial or BLE transport for the WIFI board. +Please refer to the [User's Guide]() for further information, including installation +instructions and client APIs. + diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..2def0e8 --- /dev/null +++ b/license.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..8fb23c0 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,70 @@ +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json + +site_name: The Telemetrix User's Guide For The Raspberry Pi Pico W +nav: + - Introduction: index.md + - Checking If You Have The Correct Version Of Python Installed: python_3_verify.md + - Installation Procedures: + - Installing And Upgrading The Client API On Your PC: install_telemetrix.md + - Installing And Upgrading The Pico Server Application: install_pico_server.md + - Telemetrix Design Concepts: + - Pin Modes: about_the_apis.md + - Callbacks: callbacks.md + - The APIs: + - telemetrix_rpi_pico_w: + - Importing The Package And Instantiating The Library: importing.md + - Analog Input Methods: analog_input.md + - Digital Input Methods: digital_input.md + - Digital Output Methods: digital_output.md + - DHT Humidity/Temperature Sensor Methods: dht.md + - HC-SR04 Sonar Distance Sensor Methods: sonar.md + - I2C Methods: i2c.md + - NeoPixel Methods: neopixel.md + - PWM Methods: pwm.md + - Servo Methods: servo.md + - SPI Methods: spi.md + - Stepper Motor Methods: stepper.md + - Shutting Down: management.md + - Diagnostic Aids: debug.md + - telemetrix_rpi_pico_w_aio: + - Importing The Package And Instantiating The Library: importing2.md + - Analog Input Methods: analog_input2.md + - Digital Input Methods: digital_input2.md + - Digital Output Methods: digital_output2.md + - DHT Humidity/Temperature Sensor Methods: dht2.md + - HC-SR04 Sonar Distance Sensor Methods: sonar2.md + - I2C Methods: i2c2.md + - NeoPixel Methods: neopixel2.md + - PWM Methods: pwm2.md + - Servo Methods: servo2.md + - SPI Methods: spi2.md + - Stepper Motor Methods: stepper2.md + - Shutting Down: management2.md + - Diagnostic Aids: debug2.md + - Downloading And Running The Examples: examples.md + - Client APIs: + - telemetrix_uno_r4_minima: telemetrix_minima_reference.md + - telemetrix_uno_r4_minima_aio: telemetrix_minima_reference_aio.md + - telemetrix_uno_r4_wifi: telemetrix_wifi_reference.md + - telemetrix_uno_r4_wifi_aio: telemetrix_wifi_reference_aio.md + + - About: about.md + - License: license.md +# theme: windmill + +theme: + name: 'material' + + +plugins: + - search + - mkdocstrings: + handlers: + python: + paths: [ telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi, + telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio, + telemetrix_uno_r4/minima/telemetrix_uno_r4_minima, + telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio] + + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a9b1435 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages] +find = {} # Scan the project directory with the default parameters + +[project] +name = "telemetrix_uno_r4" +version = "1.0.0" +authors = [ + { name="Alan Yorinks", email="MisterYsLab@gmail.com" }, +] +description = "Telemetrix Clients Supporting The Arduino Uno R4 Minima and Wifi" +readme = "README.md" +requires-python = ">=3.8" +license = {text = "AGPL-3.0-or-later"} + +keywords=['telemetrix', 'Arduino', 'R4', 'Python'] +classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Other Environment', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Operating System :: OS Independent', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] + +dependencies = [ + "pyserial", "bleak" +] + + + + + diff --git a/telemetrix_uno_r4/__init__.py b/telemetrix_uno_r4/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telemetrix_uno_r4/minima/__init__.py b/telemetrix_uno_r4/minima/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/__init__.py b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/private_constants.py b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/private_constants.py new file mode 100644 index 0000000..bc9d68e --- /dev/null +++ b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/private_constants.py @@ -0,0 +1,148 @@ +""" + Copyright (c) 2015-2021 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + +class PrivateConstants: + """ + This class contains a set of constants for telemetrix internal use . + """ + + # commands + # send a loop back request - for debugging communications + LOOP_COMMAND = 0 + SET_PIN_MODE = 1 # set a pin to INPUT/OUTPUT/PWM/etc + DIGITAL_WRITE = 2 # set a single digital pin value instead of entire port + ANALOG_WRITE = 3 + MODIFY_REPORTING = 4 + GET_FIRMWARE_VERSION = 5 + ARE_U_THERE = 6 # Arduino ID query for auto-detect of telemetrix connected boards + SERVO_ATTACH = 7 + SERVO_WRITE = 8 + SERVO_DETACH = 9 + I2C_BEGIN = 10 + I2C_READ = 11 + I2C_WRITE = 12 + SONAR_NEW = 13 + DHT_NEW = 14 + STOP_ALL_REPORTS = 15 + SET_ANALOG_SCANNING_INTERVAL = 16 + ENABLE_ALL_REPORTS = 17 + RESET = 18 + SPI_INIT = 19 + SPI_WRITE_BLOCKING = 20 + SPI_READ_BLOCKING = 21 + SPI_SET_FORMAT = 22 + SPI_CS_CONTROL = 23 + ONE_WIRE_INIT = 24 + ONE_WIRE_RESET = 25 + ONE_WIRE_SELECT = 26 + ONE_WIRE_SKIP = 27 + ONE_WIRE_WRITE = 28 + ONE_WIRE_READ = 29 + ONE_WIRE_RESET_SEARCH = 30 + ONE_WIRE_SEARCH = 31 + ONE_WIRE_CRC8 = 32 + SET_PIN_MODE_STEPPER = 33 + STEPPER_MOVE_TO = 34 + STEPPER_MOVE = 35 + STEPPER_RUN = 36 + STEPPER_RUN_SPEED = 37 + STEPPER_SET_MAX_SPEED = 38 + STEPPER_SET_ACCELERATION = 39 + STEPPER_SET_SPEED = 40 + STEPPER_SET_CURRENT_POSITION = 41 + STEPPER_RUN_SPEED_TO_POSITION = 42 + STEPPER_STOP = 43 + STEPPER_DISABLE_OUTPUTS = 44 + STEPPER_ENABLE_OUTPUTS = 45 + STEPPER_SET_MINIMUM_PULSE_WIDTH = 46 + STEPPER_SET_ENABLE_PIN = 47 + STEPPER_SET_3_PINS_INVERTED = 48 + STEPPER_SET_4_PINS_INVERTED = 49 + STEPPER_IS_RUNNING = 50 + STEPPER_GET_CURRENT_POSITION = 51 + STEPPER_GET_DISTANCE_TO_GO = 52 + STEPPER_GET_TARGET_POSITION = 53 + GET_FEATURES = 54 + SONAR_DISABLE = 55 + SONAR_ENABLE = 56 + BOARD_HARD_RESET = 57 + + # reports + # debug data from Arduino + DIGITAL_REPORT = DIGITAL_WRITE + ANALOG_REPORT = ANALOG_WRITE + FIRMWARE_REPORT = GET_FIRMWARE_VERSION + I_AM_HERE_REPORT = ARE_U_THERE + SERVO_UNAVAILABLE = SERVO_ATTACH + I2C_TOO_FEW_BYTES_RCVD = 8 + I2C_TOO_MANY_BYTES_RCVD = 9 + I2C_READ_REPORT = 10 + SONAR_DISTANCE = 11 + DHT_REPORT = 12 + SPI_REPORT = 13 + ONE_WIRE_REPORT = 14 + STEPPER_DISTANCE_TO_GO = 15 + STEPPER_TARGET_POSITION = 16 + STEPPER_CURRENT_POSITION = 17 + STEPPER_RUNNING_REPORT = 18 + STEPPER_RUN_COMPLETE_REPORT = 19 + FEATURES = 20 + DEBUG_PRINT = 99 + + TELEMETRIX_VERSION = "1.00" + + # reporting control + REPORTING_DISABLE_ALL = 0 + REPORTING_ANALOG_ENABLE = 1 + REPORTING_DIGITAL_ENABLE = 2 + REPORTING_ANALOG_DISABLE = 3 + REPORTING_DIGITAL_DISABLE = 4 + + # Pin mode definitions + AT_INPUT = 0 + AT_OUTPUT = 1 + AT_INPUT_PULLUP = 2 + AT_ANALOG = 3 + AT_SERVO = 4 + AT_SONAR = 5 + AT_DHT = 6 + AT_MODE_NOT_SET = 255 + + # maximum number of digital pins supported + NUMBER_OF_DIGITAL_PINS = 100 + + # maximum number of analog pins supported + NUMBER_OF_ANALOG_PINS = 20 + + # maximum number of sonars allowed + MAX_SONARS = 6 + + # maximum number of DHT devices allowed + MAX_DHTS = 6 + + # DHT Report sub-types + DHT_DATA = 0 + DHT_ERROR = 1 + + # feature masks + ONEWIRE_FEATURE = 0x01 + DHT_FEATURE = 0x02 + STEPPERS_FEATURE = 0x04 + SPI_FEATURE = 0x08 + SERVO_FEATURE = 0x10 + SONAR_FEATURE = 0x20 diff --git a/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/telemetrix_uno_r4_minima.py b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/telemetrix_uno_r4_minima.py new file mode 100644 index 0000000..d0769f4 --- /dev/null +++ b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima/telemetrix_uno_r4_minima.py @@ -0,0 +1,2554 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import sys +import threading +import time +from collections import deque + +import serial +# noinspection PyPackageRequirementscd +from serial.serialutil import SerialException +# noinspection PyPackageRequirements +from serial.tools import list_ports + +# noinspection PyUnresolvedReferences +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima.private_constants import ( + PrivateConstants) + + +# noinspection PyPep8,PyMethodMayBeStatic,GrazieInspection,PyBroadException,PyCallingNonCallable +class TelemetrixUnoR4Minima(threading.Thread): + """ + This class exposes and implements the telemetrix API. + It uses threading to accommodate concurrency. + It includes the public API methods as well as + a set of private methods. + + """ + + # noinspection PyPep8,PyPep8,PyPep8 + def __init__(self, com_port=None, arduino_instance_id=1, + arduino_wait=1, sleep_tune=0.000001, + shutdown_on_exception=True, hard_reset_on_shutdown=True): + + """ + + :param com_port: e.g. COM3 or /dev/ttyACM0. + Only use if you wish to bypass auto com port + detection. + + :param arduino_instance_id: Match with the value installed on the + arduino-telemetrix sketch. + + :param arduino_wait: Amount of time to wait for an Arduino to + fully reset itself. + + :param sleep_tune: A tuning parameter (typically not changed by user) + + :param shutdown_on_exception: call shutdown before raising + a RunTimeError exception, or + receiving a KeyboardInterrupt exception + + :param hard_reset_on_shutdown: reset the board on shutdown + + """ + + # initialize threading parent + threading.Thread.__init__(self) + + # create the threads and set them as daemons so + # that they stop when the program is closed + + # create a thread to interpret received serial data + self.the_reporter_thread = threading.Thread(target=self._reporter) + self.the_reporter_thread.daemon = True + + self.the_data_receive_thread = threading.Thread(target=self._serial_receiver) + + self.the_data_receive_thread.daemon = True + + # flag to allow the reporter and receive threads to run. + self.run_event = threading.Event() + + # check to make sure that Python interpreter is version 3.7 or greater + python_version = sys.version_info + if python_version[0] >= 3: + if python_version[1] >= 7: + pass + else: + raise RuntimeError("ERROR: Python 3.7 or greater is " + "required for use of this program.") + + # save input parameters as instance variables + self.com_port = com_port + self.arduino_instance_id = arduino_instance_id + self.arduino_wait = arduino_wait + self.sleep_tune = sleep_tune + self.shutdown_on_exception = shutdown_on_exception + self.hard_reset_on_shutdown = hard_reset_on_shutdown + + # create a deque to receive and process data from the arduino + self.the_deque = deque() + + # The report_dispatch dictionary is used to process + # incoming report messages by looking up the report message + # and executing its associated processing method. + + self.report_dispatch = {} + + # To add a command to the command dispatch table, append here. + self.report_dispatch.update( + {PrivateConstants.LOOP_COMMAND: self._report_loop_data}) + self.report_dispatch.update( + {PrivateConstants.DEBUG_PRINT: self._report_debug_data}) + self.report_dispatch.update( + {PrivateConstants.DIGITAL_REPORT: self._digital_message}) + self.report_dispatch.update( + {PrivateConstants.ANALOG_REPORT: self._analog_message}) + self.report_dispatch.update( + {PrivateConstants.FIRMWARE_REPORT: self._firmware_message}) + self.report_dispatch.update({PrivateConstants.I_AM_HERE_REPORT: self._i_am_here}) + self.report_dispatch.update( + {PrivateConstants.SERVO_UNAVAILABLE: self._servo_unavailable}) + self.report_dispatch.update( + {PrivateConstants.I2C_READ_REPORT: self._i2c_read_report}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_FEW_BYTES_RCVD: self._i2c_too_few}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_MANY_BYTES_RCVD: self._i2c_too_many}) + self.report_dispatch.update( + {PrivateConstants.SONAR_DISTANCE: self._sonar_distance_report}) + self.report_dispatch.update({PrivateConstants.DHT_REPORT: self._dht_report}) + self.report_dispatch.update( + {PrivateConstants.SPI_REPORT: self._spi_report}) + self.report_dispatch.update( + {PrivateConstants.ONE_WIRE_REPORT: self._onewire_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_CURRENT_POSITION: + self._stepper_current_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUNNING_REPORT: + self._stepper_is_running_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUN_COMPLETE_REPORT: + self._stepper_run_complete_report}) + + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.FEATURES: + self._features_report}) + + # dictionaries to store the callbacks for each pin + self.analog_callbacks = {} + + self.digital_callbacks = {} + + self.i2c_callback = None + self.i2c_callback2 = None + + self.i2c_1_active = False + self.i2c_2_active = False + + self.spi_callback = None + + self.onewire_callback = None + + self.cs_pins_enabled = [] + + # the trigger pin will be the key to retrieve + # the callback for a specific HC-SR04 + self.sonar_callbacks = {} + + self.sonar_count = 0 + + self.dht_callbacks = {} + + self.dht_count = 0 + + # serial port in use + self.serial_port = None + + # flag to indicate we are in shutdown mode + self.shutdown_flag = False + + # debug loopback callback method + self.loop_back_callback = None + + # flag to indicate the start of a new report + # self.new_report_start = True + + # firmware version to be stored here + self.firmware_version = [] + + # reported arduino instance id + self.reported_arduino_id = [] + + # reported features + self.reported_features = 0 + + # flag to indicate if i2c was previously enabled + self.i2c_enabled = False + + # flag to indicate if spi is initialized + self.spi_enabled = False + + # flag to indicate if onewire is initialized + self.onewire_enabled = False + + # # stepper motor variables + # + # # updated when a new motor is added + # self.next_stepper_assigned = 0 + # + # # valid list of stepper motor interface types + # self.valid_stepper_interfaces = [1, 2, 3, 4, 6, 8] + # + # # maximum number of steppers supported + # self.max_number_of_steppers = 4 + # + # # number of steppers created - not to exceed the maximum + # self.number_of_steppers = 0 + # + # # dictionary to hold stepper motor information + # self.stepper_info = {'instance': False, 'is_running': None, + # 'maximum_speed': 1, 'speed': 0, 'acceleration': 0, + # 'distance_to_go_callback': None, + # 'target_position_callback': None, + # 'current_position_callback': None, + # 'is_running_callback': None, + # 'motion_complete_callback': None, + # 'acceleration_callback': None} + # + # # build a list of stepper motor info items + # self.stepper_info_list = [] + # # a list of dictionaries to hold stepper information + # for motor in range(self.max_number_of_steppers): + # self.stepper_info_list.append(self.stepper_info) + + self.the_reporter_thread.start() + self.the_data_receive_thread.start() + + print(f"telemetrix_uno_r4_minima: Version" + f" {PrivateConstants.TELEMETRIX_VERSION}\n\n" + f"Copyright (c) 2023 Alan Yorinks All Rights Reserved.\n") + + # using the serial link + if not self.com_port: + # user did not specify a com_port + try: + self._find_arduino() + except KeyboardInterrupt: + if self.shutdown_on_exception: + self.shutdown() + else: + # com_port specified - set com_port and baud rate + try: + self._manual_open() + except KeyboardInterrupt: + if self.shutdown_on_exception: + self.shutdown() + + if self.serial_port: + print( + f"Arduino compatible device found and connected to {self.serial_port.port}") + + self.serial_port.reset_input_buffer() + self.serial_port.reset_output_buffer() + + # no com_port found - raise a runtime exception + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('No Arduino Found or User Aborted Program') + + # allow the threads to run + self._run_threads() + print(f'Waiting for Arduino to reset') + print(f'Reset Complete') + + # get telemetrix firmware version and print it + print('\nRetrieving Telemetrix4UnoR4Minima firmware ID...') + self._get_firmware_version() + if not self.firmware_version: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'Telemetrix4UnoR4Minima firmware version') + + else: + + print(f'Telemetrix4UnoR4Minima firmware version: {self.firmware_version[0]}.' + f'{self.firmware_version[1]}.{self.firmware_version[2]}') + command = [PrivateConstants.ENABLE_ALL_REPORTS] + self._send_command(command) + + # get the features list + command = [PrivateConstants.GET_FEATURES] + self._send_command(command) + time.sleep(.2) + + # Have the server reset its data structures + command = [PrivateConstants.RESET] + self._send_command(command) + + def _find_arduino(self): + """ + This method will search all potential serial ports for an Arduino + containing a sketch that has a matching arduino_instance_id as + specified in the input parameters of this class. + + This is used explicitly with the Telemetrix4Arduino sketch. + """ + + # a list of serial ports to be checked + serial_ports = [] + + print('Opening all potential serial ports...') + the_ports_list = list_ports.comports() + for port in the_ports_list: + if port.pid is None: + continue + try: + self.serial_port = serial.Serial(port.device, 115200, + timeout=1, writeTimeout=0) + except SerialException: + continue + # create a list of serial ports that we opened + serial_ports.append(self.serial_port) + + # display to the user + print('\t' + port.device) + + # clear out any possible data in the input buffer + # wait for arduino to reset + print( + f'\nWaiting {self.arduino_wait} seconds(arduino_wait) for Arduino devices to ' + 'reset...') + # temporary for testing + time.sleep(self.arduino_wait) + self._run_threads() + + for serial_port in serial_ports: + self.serial_port = serial_port + + self._get_arduino_id() + if self.reported_arduino_id != self.arduino_instance_id: + continue + else: + print('Valid Arduino ID Found.') + self.serial_port.reset_input_buffer() + self.serial_port.reset_output_buffer() + return + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'Incorrect Arduino ID: {self.reported_arduino_id}') + + def _manual_open(self): + """ + Com port was specified by the user - try to open up that port + + """ + # if port is not found, a serial exception will be thrown + try: + print(f'Opening {self.com_port}...') + self.serial_port = serial.Serial(self.com_port, 115200, + timeout=1, writeTimeout=0) + + print( + f'\nWaiting {self.arduino_wait} seconds(arduino_wait) for Arduino devices to ' + 'reset...') + self._run_threads() + time.sleep(self.arduino_wait) + + self._get_arduino_id() + + if self.reported_arduino_id != self.arduino_instance_id: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'Incorrect Arduino ID: {self.reported_arduino_id}') + print('Valid Arduino ID Found.') + # get arduino firmware version and print it + print('\nRetrieving Telemetrix4Arduino firmware ID...') + self._get_firmware_version() + + if not self.firmware_version: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Telemetrix4Arduino Sketch Firmware Version Not Found') + + else: + print(f'Telemetrix4UnoR4 firmware version: {self.firmware_version[0]}.' + f'{self.firmware_version[1]}') + except KeyboardInterrupt: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('User Hit Control-C') + + def analog_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (maximum 16 bits) + + """ + value_msb = value >> 8 + value_lsb = value & 0xff + command = [PrivateConstants.ANALOG_WRITE, pin, value_msb, value_lsb] + self._send_command(command) + + def digital_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (1 or 0) + + """ + + command = [PrivateConstants.DIGITAL_WRITE, pin, value] + self._send_command(command) + + def disable_all_reporting(self): + """ + Disable reporting for all digital and analog input pins + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DISABLE_ALL, 0] + self._send_command(command) + + def disable_analog_reporting(self, pin): + """ + Disables analog reporting for a single analog pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_DISABLE, pin] + self._send_command(command) + + def disable_digital_reporting(self, pin): + """ + Disables digital reporting for a single digital input. + + :param pin: Pin number. + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_DISABLE, pin] + self._send_command(command) + + def enable_analog_reporting(self, pin): + """ + Enables analog reporting for the specified pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_ENABLE, pin] + self._send_command(command) + + def enable_digital_reporting(self, pin): + """ + Enable reporting on the specified digital pin. + + :param pin: Pin number. + """ + + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_ENABLE, pin] + self._send_command(command) + + def _get_arduino_id(self): + """ + Retrieve arduino-telemetrix arduino id + + """ + command = [PrivateConstants.ARE_U_THERE] + self._send_command(command) + # provide time for the reply + time.sleep(.5) + + def _get_firmware_version(self): + """ + This method retrieves the + arduino-telemetrix firmware version + + """ + command = [PrivateConstants.GET_FIRMWARE_VERSION] + self._send_command(command) + # provide time for the reply + time.sleep(.5) + + def i2c_read(self, address, register, number_of_bytes, + callback=None, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the + specified register for the i2c device. + + + :param address: i2c device address + + :param register: i2c register (or None if no register + selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report + i2c data as a result of read command + + :param i2c_port: 0 = default, 1 = secondary + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + + self._i2c_read_request(address, register, number_of_bytes, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + def i2c_read_restart_transmission(self, address, register, + number_of_bytes, + callback=None, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the specified + register for the i2c device. This restarts the transmission + after the read. It is required for some i2c devices such as the MMA8452Q + accelerometer. + + + :param address: i2c device address + + :param register: i2c register (or None if no register + selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report i2c + data as a result of read command + + :param i2c_port: 0 = default 1 = secondary + + :param write_register: If True, the register is written before read + Else, the write is suppressed + + + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + + self._i2c_read_request(address, register, number_of_bytes, + stop_transmission=False, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + def _i2c_read_request(self, address, register, number_of_bytes, + stop_transmission=True, callback=None, i2c_port=0, + write_register=True): + """ + This method requests the read of an i2c device. Results are retrieved + via callback. + + :param address: i2c device address + + :param register: register number (or None if no register selection is needed) + + :param number_of_bytes: number of bytes expected to be returned + + :param stop_transmission: stop transmission after read + + :param callback: Required callback function to report i2c data as a + result of read command. + + :param write_register: If True, the register is written before read + Else, the write is suppressed + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 2.') + + if not callback: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('I2C Read: A callback function must be specified.') + + if not i2c_port: + self.i2c_callback = callback + else: + self.i2c_callback2 = callback + + if not register: + register = 0 + + if write_register: + write_register = 1 + else: + write_register = 0 + + # message contains: + # 1. address + # 2. register + # 3. number of bytes + # 4. restart_transmission - True or False + # 5. i2c port + # 6. suppress write flag + + command = [PrivateConstants.I2C_READ, address, register, number_of_bytes, + stop_transmission, i2c_port, write_register] + self._send_command(command) + + def i2c_write(self, address, args, i2c_port=0): + """ + Write data to an i2c device. + + :param address: i2c device address + + :param i2c_port: 0= port 1, 1 = port 2 + + :param args: A variable number of bytes to be sent to the device + passed in as a list + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 2.') + + command = [PrivateConstants.I2C_WRITE, len(args), address, i2c_port] + + for item in args: + command.append(item) + + self._send_command(command) + + def loop_back(self, start_character, callback=None): + """ + This is a debugging method to send a character to the + Arduino device, and have the device loop it back. + + :param start_character: The character to loop back. It should be + an integer. + + :param callback: Looped back character will appear in the callback method + + """ + command = [PrivateConstants.LOOP_COMMAND, ord(start_character)] + self.loop_back_callback = callback + self._send_command(command) + + def set_analog_scan_interval(self, interval): + """ + Set the analog scanning interval. + + :param interval: value of 0 - 255 - milliseconds + """ + + if 0 <= interval <= 255: + command = [PrivateConstants.SET_ANALOG_SCANNING_INTERVAL, interval] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('Analog interval must be between 0 and 255') + + def set_pin_mode_analog_output(self, pin_number): + """ + Set a pin as a pwm (analog output) pin. + + :param pin_number:arduino pin number + + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT) + + def set_pin_mode_analog_input(self, pin_number, differential=0, callback=None): + """ + Set a pin as an analog input. + + :param pin_number: arduino pin number + + :param differential: difference in previous to current value before + report will be generated + + :param callback: callback function + + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for analog input pins = 3 + + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_ANALOG, differential, + callback) + + def set_pin_mode_digital_input(self, pin_number, callback=None): + """ + Set a pin as a digital input. + + :param pin_number: arduino pin number + + :param callback: callback function + + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT, callback=callback) + + def set_pin_mode_digital_input_pullup(self, pin_number, callback=None): + """ + Set a pin as a digital input with pullup enabled. + + :param pin_number: arduino pin number + + :param callback: callback function + + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT_PULLUP, + callback=callback) + + def set_pin_mode_digital_output(self, pin_number): + """ + Set a pin as a digital output pin. + + :param pin_number: arduino pin number + """ + + self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT) + + def set_pin_mode_i2c(self, i2c_port=0): + """ + Establish the standard Arduino i2c pins for i2c utilization. + + :param i2c_port: 0 = i2c1, 1 = i2c2 + + NOTES: 1. THIS METHOD MUST BE CALLED BEFORE ANY I2C REQUEST IS MADE + 2. Callbacks are set within the individual i2c read methods of this + API. + + See i2c_read, or i2c_read_restart_transmission. + + """ + # test for i2c port 2 + if i2c_port: + # if not previously activated set it to activated + # and the send a begin message for this port + if not self.i2c_2_active: + self.i2c_2_active = True + else: + return + # port 1 + else: + if not self.i2c_1_active: + self.i2c_1_active = True + else: + return + + command = [PrivateConstants.I2C_BEGIN, i2c_port] + self._send_command(command) + + def set_pin_mode_dht(self, pin, callback=None, dht_type=22): + """ + + :param pin: connection pin + + :param callback: callback function + + :param dht_type: either 22 for DHT22 or 11 for DHT11 + + Error Callback: [DHT REPORT Type, DHT_ERROR_NUMBER, PIN, DHT_TYPE, Time] + + Valid Data Callback: DHT REPORT Type, DHT_DATA=, PIN, DHT_TYPE, Humidity, + Temperature, + Time] + + DHT_REPORT_TYPE = 12 + """ + if self.reported_features & PrivateConstants.DHT_FEATURE: + if not callback: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('set_pin_mode_dht: A Callback must be specified') + + if self.dht_count < PrivateConstants.MAX_DHTS - 1: + self.dht_callbacks[pin] = callback + self.dht_count += 1 + + if dht_type != 22 and dht_type != 11: + dht_type = 22 + + command = [PrivateConstants.DHT_NEW, pin, dht_type] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Maximum Number Of DHTs Exceeded - set_pin_mode_dht fails for pin {pin}') + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The DHT feature is disabled in the server.') + + # noinspection PyRedundantParentheses + def set_pin_mode_servo(self, pin_number, min_pulse=544, max_pulse=2400): + """ + + Attach a pin to a servo motor + + :param pin_number: pin + + :param min_pulse: minimum pulse width + + :param max_pulse: maximum pulse width + + """ + if self.reported_features & PrivateConstants.SERVO_FEATURE: + + minv = (min_pulse).to_bytes(2, byteorder="big") + maxv = (max_pulse).to_bytes(2, byteorder="big") + + command = [PrivateConstants.SERVO_ATTACH, pin_number, + minv[0], minv[1], maxv[0], maxv[1]] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The SERVO feature is disabled in the server.') + + def set_pin_mode_sonar(self, trigger_pin, echo_pin, + callback=None): + """ + + :param trigger_pin: + + :param echo_pin: + + :param callback: callback + + callback data: [PrivateConstants.SONAR_DISTANCE, trigger_pin, distance_value, time_stamp] + + """ + if self.reported_features & PrivateConstants.SONAR_FEATURE: + + if not callback: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('set_pin_mode_sonar: A Callback must be specified') + + if self.sonar_count < PrivateConstants.MAX_SONARS - 1: + self.sonar_callbacks[trigger_pin] = callback + self.sonar_count += 1 + + command = [PrivateConstants.SONAR_NEW, trigger_pin, echo_pin] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Maximum Number Of Sonars Exceeded - set_pin_mode_sonar fails for pin {trigger_pin}') + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The SONAR feature is disabled in the server.') + + def set_pin_mode_spi(self, chip_select_list=None): + """ + Specify the list of chip select pins. + + Standard Arduino MISO, MOSI and CLK pins are used for the board in use. + + Chip Select is any digital output capable pin. + + :param chip_select_list: this is a list of pins to be used for chip select. + The pins will be configured as output, and set to high + ready to be used for chip select. + NOTE: You must specify the chips select pins here! + + + command message: [command, [cs pins...]] + """ + + if self.reported_features & PrivateConstants.SPI_FEATURE: + if type(chip_select_list) != list: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('chip_select_list must be in the form of a list') + if not chip_select_list: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('Chip select pins were not specified') + + self.spi_enabled = True + + command = [PrivateConstants.SPI_INIT, len(chip_select_list)] + + for pin in chip_select_list: + command.append(pin) + self.cs_pins_enabled.append(pin) + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The SPI feature is disabled in the server.') + + # def set_pin_mode_stepper(self, interface=1, pin1=2, pin2=3, pin3=4, + # pin4=5, enable=True): + # """ + # Stepper motor support is implemented as a proxy for the + # the AccelStepper library for the Arduino. + # + # This feature is compatible with the TB6600 Motor Driver + # + # Note: It may not work for other driver types! + # + # https://github.com/waspinator/AccelStepper + # + # Instantiate a stepper motor. + # + # Initialize the interface and pins for a stepper motor. + # + # :param interface: Motor Interface Type: + # + # 1 = Stepper Driver, 2 driver pins required + # + # 2 = FULL2WIRE 2 wire stepper, 2 motor pins required + # + # 3 = FULL3WIRE 3 wire stepper, such as HDD spindle, + # 3 motor pins required + # + # 4 = FULL4WIRE, 4 wire full stepper, 4 motor pins + # required + # + # 6 = HALF3WIRE, 3 wire half stepper, such as HDD spindle, + # 3 motor pins required + # + # 8 = HALF4WIRE, 4 wire half stepper, 4 motor pins required + # + # :param pin1: Arduino digital pin number for motor pin 1 + # + # :param pin2: Arduino digital pin number for motor pin 2 + # + # :param pin3: Arduino digital pin number for motor pin 3 + # + # :param pin4: Arduino digital pin number for motor pin 4 + # + # :param enable: If this is true, the output pins at construction time. + # + # :return: Motor Reference number + # """ + # if self.reported_features & PrivateConstants.STEPPERS_FEATURE: + # + # if self.number_of_steppers == self.max_number_of_steppers: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('Maximum number of steppers has already been assigned') + # + # if interface not in self.valid_stepper_interfaces: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('Invalid stepper interface') + # + # self.number_of_steppers += 1 + # + # motor_id = self.next_stepper_assigned + # self.next_stepper_assigned += 1 + # self.stepper_info_list[motor_id]['instance'] = True + # + # # build message and send message to server + # command = [PrivateConstants.SET_PIN_MODE_STEPPER, motor_id, interface, pin1, + # pin2, pin3, pin4, enable] + # self._send_command(command) + # + # # return motor id + # return motor_id + # else: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'The Stepper feature is disabled in the server.') + + def servo_write(self, pin_number, angle): + """ + + Set a servo attached to a pin to a given angle. + + :param pin_number: pin + + :param angle: angle (0-180) + + """ + command = [PrivateConstants.SERVO_WRITE, pin_number, angle] + self._send_command(command) + + def servo_detach(self, pin_number): + """ + Detach a servo for reuse + + :param pin_number: attached pin + + """ + command = [PrivateConstants.SERVO_DETACH, pin_number] + self._send_command(command) + + # def stepper_move_to(self, motor_id, position): + # """ + # Set an absolution target position. If position is positive, the movement is + # clockwise, else it is counter-clockwise. + # + # The run() function (below) will try to move the motor (at most one step per call) + # from the current position to the target position set by the most + # recent call to this function. Caution: moveTo() also recalculates the + # speed for the next step. + # If you are trying to use constant speed movements, you should call setSpeed() + # after calling moveTo(). + # + # :param motor_id: motor id: 0 - 3 + # + # :param position: target position. Maximum value is 32 bits. + # """ + # if position < 0: + # polarity = 1 + # else: + # polarity = 0 + # position = abs(position) + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_move_to: Invalid motor_id.') + # + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE_TO, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # self._send_command(command) + # + # def stepper_move(self, motor_id, relative_position): + # """ + # Set the target position relative to the current position. + # + # :param motor_id: motor id: 0 - 3 + # + # :param relative_position: The desired position relative to the current + # position. Negative is anticlockwise from + # the current position. Maximum value is 32 bits. + # """ + # if relative_position < 0: + # polarity = 1 + # else: + # polarity = 0 + # + # relative_position = abs(relative_position) + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_move: Invalid motor_id.') + # + # position_bytes = list(relative_position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # self._send_command(command) + # + # def stepper_run(self, motor_id, completion_callback=None): + # """ + # This method steps the selected motor based on the current speed. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run: A motion complete callback must be ' + # 'specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN, motor_id] + # self._send_command(command) + # + # def stepper_run_speed(self, motor_id): + # """ + # This method steps the selected motor based at a constant speed as set by the most + # recent call to stepper_set_max_speed(). The motor will run continuously. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run_speed: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_RUN_SPEED, motor_id] + # self._send_command(command) + # + # def stepper_set_max_speed(self, motor_id, max_speed): + # """ + # Sets the maximum permitted speed. The stepper_run() function will accelerate + # up to the speed set by this function. + # + # Caution: the maximum speed achievable depends on your processor and clock speed. + # The default maxSpeed is 1 step per second. + # + # Caution: Speeds that exceed the maximum speed supported by the processor may + # result in non-linear accelerations and decelerations. + # + # :param motor_id: 0 - 3 + # + # :param max_speed: 1 - 1000 + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Invalid motor_id.') + # + # if not 1 < max_speed <= 1000: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Speed range is 1 - 1000.') + # + # self.stepper_info_list[motor_id]['max_speed'] = max_speed + # max_speed_msb = (max_speed & 0xff00) >> 8 + # max_speed_lsb = max_speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MAX_SPEED, motor_id, max_speed_msb, + # max_speed_lsb] + # self._send_command(command) + # + # def stepper_get_max_speed(self, motor_id): + # """ + # Returns the maximum speed configured for this stepper + # that was previously set by stepper_set_max_speed() + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # :return: The currently configured maximum speed. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_max_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['max_speed'] + # + # def stepper_set_acceleration(self, motor_id, acceleration): + # """ + # Sets the acceleration/deceleration rate. + # + # :param motor_id: 0 - 3 + # + # :param acceleration: The desired acceleration in steps per second + # per second. Must be > 0.0. This is an + # expensive call since it requires a square + # root to be calculated on the server. + # Dont call more often than needed. + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Invalid motor_id.') + # + # if not 1 < acceleration <= 1000: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Acceleration range is 1 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['acceleration'] = acceleration + # + # max_accel_msb = acceleration >> 8 + # max_accel_lsb = acceleration & 0xff + # + # command = [PrivateConstants.STEPPER_SET_ACCELERATION, motor_id, max_accel_msb, + # max_accel_lsb] + # self._send_command(command) + # + # def stepper_set_speed(self, motor_id, speed): + # """ + # Sets the desired constant speed for use with stepper_run_speed(). + # + # :param motor_id: 0 - 3 + # + # :param speed: 0 - 1000 The desired constant speed in steps per + # second. Positive is clockwise. Speeds of more than 1000 steps per + # second are unreliable. Speed accuracy depends on the Arduino + # crystal. Jitter depends on how frequently you call the + # stepper_run_speed() method. + # The speed will be limited by the current value of + # stepper_set_max_speed(). + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_speed: Invalid motor_id.') + # + # if not 0 < speed <= 1000: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_speed: Speed range is 0 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['speed'] = speed + # + # speed_msb = speed >> 8 + # speed_lsb = speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_SPEED, motor_id, speed_msb, speed_lsb] + # self._send_command(command) + # + # def stepper_get_speed(self, motor_id): + # """ + # Returns the most recently set speed. + # that was previously set by stepper_set_speed(); + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['speed'] + # + # def stepper_get_distance_to_go(self, motor_id, distance_to_go_callback): + # """ + # Request the distance from the current position to the target position + # from the server. + # + # :param motor_id: 0 - 3 + # + # :param distance_to_go_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=15, motor_id, distance in steps, time_stamp] + # + # A positive distance is clockwise from the current position. + # + # """ + # if not distance_to_go_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go: Invalid motor_id.') + # self.stepper_info_list[motor_id][ + # 'distance_to_go_callback'] = distance_to_go_callback + # command = [PrivateConstants.STEPPER_GET_DISTANCE_TO_GO, motor_id] + # self._send_command(command) + # + # def stepper_get_target_position(self, motor_id, target_callback): + # """ + # Request the most recently set target position from the server. + # + # :param motor_id: 0 - 3 + # + # :param target_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=16, motor_id, target position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # + # """ + # if not target_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError( + # 'stepper_get_target_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_target_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id][ + # 'target_position_callback'] = target_callback + # + # command = [PrivateConstants.STEPPER_GET_TARGET_POSITION, motor_id] + # self._send_command(command) + # + # def stepper_get_current_position(self, motor_id, current_position_callback): + # """ + # Request the current motor position from the server. + # + # :param motor_id: 0 - 3 + # + # :param current_position_callback: required callback function to receive report + # + # :return: The current motor position returned via the callback as a list: + # + # [REPORT_TYPE=17, motor_id, current position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # """ + # if not current_position_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError( + # 'stepper_get_current_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_current_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['current_position_callback'] = current_position_callback + # + # command = [PrivateConstants.STEPPER_GET_CURRENT_POSITION, motor_id] + # self._send_command(command) + # + # def stepper_set_current_position(self, motor_id, position): + # """ + # Resets the current position of the motor, so that wherever the motor + # happens to be right now is considered to be the new 0 position. Useful + # for setting a zero position on a stepper after an initial hardware + # positioning move. + # + # Has the side effect of setting the current motor speed to 0. + # + # :param motor_id: 0 - 3 + # + # :param position: Position in steps. This is a 32 bit value + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_current_position: Invalid motor_id.') + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_SET_CURRENT_POSITION, motor_id] + # for value in position_bytes: + # command.append(value) + # self._send_command(command) + # + # def stepper_run_speed_to_position(self, motor_id, completion_callback=None): + # """ + # Runs the motor at the currently selected speed until the target position is + # reached. + # + # Does not implement accelerations. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: A motion complete ' + # 'callback must be ' + # 'specified.') + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN_SPEED_TO_POSITION, motor_id] + # self._send_command(command) + # + # def stepper_stop(self, motor_id): + # """ + # Sets a new target position that causes the stepper + # to stop as quickly as possible, using the current speed and + # acceleration parameters. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_stop: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_STOP, motor_id] + # self._send_command(command) + # + # def stepper_disable_outputs(self, motor_id): + # """ + # Disable motor pin outputs by setting them all LOW. + # + # Depending on the design of your electronics this may turn off + # the power to the motor coils, saving power. + # + # This is useful to support Arduino low power modes: disable the outputs + # during sleep and then re-enable with enableOutputs() before stepping + # again. + # + # If the enable Pin is defined, sets it to OUTPUT mode and clears + # the pin to disabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_disable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_DISABLE_OUTPUTS, motor_id] + # self._send_command(command) + # + # def stepper_enable_outputs(self, motor_id): + # """ + # Enable motor pin outputs by setting the motor pins to OUTPUT + # mode. + # + # If the enable Pin is defined, sets it to OUTPUT mode and sets + # the pin to enabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_enable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_ENABLE_OUTPUTS, motor_id] + # self._send_command(command) + # + # def stepper_set_min_pulse_width(self, motor_id, minimum_width): + # """ + # Sets the minimum pulse width allowed by the stepper driver. + # + # The minimum practical pulse width is approximately 20 microseconds. + # + # Times less than 20 microseconds will usually result in 20 microseconds or so. + # + # :param motor_id: 0 -3 + # + # :param minimum_width: A 16 bit unsigned value expressed in microseconds. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Invalid motor_id.') + # + # if not 0 < minimum_width <= 0xff: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Pulse width range = ' + # '0-0xffff.') + # + # width_msb = minimum_width >> 8 + # width_lsb = minimum_width & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MINIMUM_PULSE_WIDTH, motor_id, width_msb, + # width_lsb] + # self._send_command(command) + # + # def stepper_set_enable_pin(self, motor_id, pin=0xff): + # """ + # Sets the enable pin number for stepper drivers. + # 0xFF indicates unused (default). + # + # Otherwise, if a pin is set, the pin will be turned on when + # enableOutputs() is called and switched off when disableOutputs() + # is called. + # + # :param motor_id: 0 - 4 + # :param pin: 0-0xff + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Invalid motor_id.') + # + # if not 0 < pin <= 0xff: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Pulse width range = ' + # '0-0xff.') + # command = [PrivateConstants.STEPPER_SET_ENABLE_PIN, motor_id, pin] + # + # self._send_command(command) + # + # def stepper_set_3_pins_inverted(self, motor_id, direction=False, step=False, + # enable=False): + # """ + # Sets the inversion for stepper driver pins. + # + # :param motor_id: 0 - 3 + # + # :param direction: True=inverted or False + # + # :param step: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_3_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_3_PINS_INVERTED, motor_id, direction, + # step, enable] + # + # self._send_command(command) + # + # def stepper_set_4_pins_inverted(self, motor_id, pin1_invert=False, pin2_invert=False, + # pin3_invert=False, pin4_invert=False, enable=False): + # """ + # Sets the inversion for 2, 3 and 4 wire stepper pins + # + # :param motor_id: 0 - 3 + # + # :param pin1_invert: True=inverted or False + # + # :param pin2_invert: True=inverted or False + # + # :param pin3_invert: True=inverted or False + # + # :param pin4_invert: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_4_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_4_PINS_INVERTED, motor_id, pin1_invert, + # pin2_invert, pin3_invert, pin4_invert, enable] + # + # self._send_command(command) + # + # def stepper_is_running(self, motor_id, callback): + # """ + # Checks to see if the motor is currently running to a target. + # + # Callback return True if the speed is not zero or not at the target position. + # + # :param motor_id: 0-4 + # + # :param callback: required callback function to receive report + # + # :return: The current running state returned via the callback as a list: + # + # [REPORT_TYPE=18, motor_id, True or False for running state, time_stamp] + # """ + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError( + # 'stepper_is_running: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_is_running: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['is_running_callback'] = callback + # + # command = [PrivateConstants.STEPPER_IS_RUNNING, motor_id] + # self._send_command(command) + + def _set_pin_mode(self, pin_number, pin_state, differential=0, callback=None): + """ + A private method to set the various pin modes. + + :param pin_number: arduino pin number + + :param pin_state: INPUT/OUTPUT/ANALOG/PWM/PULLUP + For SERVO use: set_pin_mode_servo + For DHT use: set_pin_mode_dht + + :param differential: for analog inputs - threshold + value to be achieved for report to + be generated + + :param callback: A reference to a call back function to be + called when pin data value changes + + """ + if callback: + if pin_state == PrivateConstants.AT_INPUT: + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_INPUT_PULLUP: + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_ANALOG: + self.analog_callbacks[pin_number] = callback + else: + print('{} {}'.format('set_pin_mode: callback ignored for ' + 'pin state:', pin_state)) + + if pin_state == PrivateConstants.AT_INPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT, 1] + + elif pin_state == PrivateConstants.AT_INPUT_PULLUP: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT_PULLUP, 1] + + elif pin_state == PrivateConstants.AT_OUTPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_OUTPUT] + + elif pin_state == PrivateConstants.AT_ANALOG: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_ANALOG, + differential >> 8, differential & 0xff, 1] + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('Unknown pin state') + + if command: + self._send_command(command) + + def shutdown(self): + """ + This method attempts an orderly shutdown + If any exceptions are thrown, they are ignored. + """ + self.shutdown_flag = True + + self._stop_threads() + + try: + command = [PrivateConstants.STOP_ALL_REPORTS] + self._send_command(command) + time.sleep(.5) + + if self.hard_reset_on_shutdown: + self.r4_hard_reset() + else: + try: + self.serial_port.reset_input_buffer() + self.serial_port.reset_output_buffer() + + self.serial_port.close() + + except (RuntimeError, SerialException, OSError): + # ignore error on shutdown + pass + except Exception: + # raise RuntimeError('Shutdown failed - could not send stop streaming + # message') + pass + + def sonar_disable(self): + """ + Disable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_DISABLE] + self._send_command(command) + + def sonar_enable(self): + """ + Enable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_ENABLE] + self._send_command(command) + + def spi_cs_control(self, chip_select_pin, select): + """ + Control an SPI chip select line + :param chip_select_pin: pin connected to CS + + :param select: 0=select, 1=deselect + """ + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_cs_control: SPI interface is not enabled.') + + if chip_select_pin not in self.cs_pins_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_cs_control: chip select pin never enabled.') + command = [PrivateConstants.SPI_CS_CONTROL, chip_select_pin, select] + self._send_command(command) + + def spi_read_blocking(self, chip_select, register_selection, number_of_bytes_to_read, + call_back=None): + """ + Read the specified number of bytes from the specified SPI port and + call the callback function with the reported data. + + :param chip_select: chip select pin + + :param register_selection: Register to be selected for read. + + :param number_of_bytes_to_read: Number of bytes to read + + :param call_back: Required callback function to report spi data as a + result of read command + + + callback returns a data list: + [SPI_READ_REPORT, chip select pin, SPI Register, count of data bytes read, + data bytes, time-stamp] + + SPI_READ_REPORT = 13 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_read_blocking: SPI interface is not enabled.') + + if not call_back: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('spi_read_blocking: A Callback must be specified') + + self.spi_callback = call_back + + command = [PrivateConstants.SPI_READ_BLOCKING, chip_select, + number_of_bytes_to_read, + register_selection] + + self._send_command(command) + + def spi_set_format(self, clock_divisor, bit_order, data_mode): + """ + Configure how the SPI serializes and de-serializes data on the wire. + + See Arduino SPI reference materials for details. + + :param clock_divisor: 1 - 255 + + :param bit_order: + + LSBFIRST = 0 + + MSBFIRST = 1 (default) + + :param data_mode: + + SPI_MODE0 = 0x00 (default) + + SPI_MODE1 = 1 + + SPI_MODE2 = 2 + + SPI_MODE3 = 3 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_set_format: SPI interface is not enabled.') + + if not 0 < clock_divisor <= 255: + raise RuntimeError(f'spi_set_format: illegal clock divisor selected.') + if bit_order not in [0, 1]: + raise RuntimeError(f'spi_set_format: illegal bit_order selected.') + if data_mode not in [0, 1, 2, 3]: + raise RuntimeError(f'spi_set_format: illegal data_order selected.') + + command = [PrivateConstants.SPI_SET_FORMAT, clock_divisor, bit_order, + data_mode] + self._send_command(command) + + def spi_write_blocking(self, chip_select, bytes_to_write): + """ + Write a list of bytes to the SPI device. + + :param chip_select: chip select pin + + :param bytes_to_write: A list of bytes to write. This must + be in the form of a list. + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_write_blocking: SPI interface is not enabled.') + + if type(bytes_to_write) is not list: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('spi_write_blocking: bytes_to_write must be a list.') + + command = [PrivateConstants.SPI_WRITE_BLOCKING, chip_select, len(bytes_to_write)] + + for data in bytes_to_write: + command.append(data) + + self._send_command(command) + + # def set_pin_mode_one_wire(self, pin): + # """ + # Initialize the one wire serial bus. + # + # :param pin: Data pin connected to the OneWire device + # """ + # if self.reported_features & PrivateConstants.ONEWIRE_FEATURE: + # self.onewire_enabled = True + # command = [PrivateConstants.ONE_WIRE_INIT, pin] + # self._send_command(command) + # else: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'The OneWire feature is disabled in the server.') + # + # def onewire_reset(self, callback=None): + # """ + # Reset the onewire device + # + # :param callback: required function to report reset result + # + # callback returns a list: + # [ReportType = 14, Report Subtype = 25, reset result byte, + # timestamp] + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_reset: OneWire interface is not enabled.') + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_reset: A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_RESET] + # self._send_command(command) + # + # def onewire_select(self, device_address): + # """ + # Select a device based on its address + # :param device_address: A bytearray of 8 bytes + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_select: OneWire interface is not enabled.') + # + # if type(device_address) is not list: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # + # if len(device_address) != 8: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # command = [PrivateConstants.ONE_WIRE_SELECT] + # for data in device_address: + # command.append(data) + # self._send_command(command) + # + # def onewire_skip(self): + # """ + # Skip the device selection. This only works if you have a + # single device, but you can avoid searching and use this to + # immediately access your device. + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_skip: OneWire interface is not enabled.') + # + # command = [PrivateConstants.ONE_WIRE_SKIP] + # self._send_command(command) + # + # def onewire_write(self, data, power=0): + # """ + # Write a byte to the onewire device. If 'power' is one + # then the wire is held high at the end for + # parasitically powered devices. You + # are responsible for eventually de-powering it by calling + # another read or write. + # + # :param data: byte to write. + # :param power: power control (see above) + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_write: OneWire interface is not enabled.') + # if 0 < data < 255: + # command = [PrivateConstants.ONE_WIRE_WRITE, data, power] + # self._send_command(command) + # else: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_write: Data must be no larger than 255') + # + # def onewire_read(self, callback=None): + # """ + # Read a byte from the onewire device + # :param callback: required function to report onewire data as a + # result of read command + # + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_READ=29, data byte, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_read: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_READ] + # self._send_command(command) + # + # def onewire_reset_search(self): + # """ + # Begin a new search. The next use of search will begin at the first device + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_reset_search: OneWire interface is not ' + # f'enabled.') + # else: + # command = [PrivateConstants.ONE_WIRE_RESET_SEARCH] + # self._send_command(command) + # + # def onewire_search(self, callback=None): + # """ + # Search for the next device. The device address will returned in the callback. + # If a device is found, the 8 byte address is contained in the callback. + # If no more devices are found, the address returned contains all elements set + # to 0xff. + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_SEARCH=31, 8 byte address, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_search: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_SEARCH] + # self._send_command(command) + # + # def onewire_crc8(self, address_list, callback=None): + # """ + # Compute a CRC check on an array of data. + # :param address_list: + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_CRC8=32, CRC, time-stamp] + # + # ONEWIRE_REPORT = 14 + # + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_crc8: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_crc8 A Callback must be specified') + # + # if type(address_list) is not list: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_crc8: address list must be a list.') + # + # self.onewire_callback = callback + # + # address_length = len(address_list) + # + # command = [PrivateConstants.ONE_WIRE_CRC8, address_length - 1] + # + # for data in address_list: + # command.append(data) + # + # self._send_command(command) + + def r4_hard_reset(self): + """ + Place the r4 into hard reset + """ + command = [PrivateConstants.RESET, 1] + self._send_command(command) + time.sleep(.5) + command = [PrivateConstants.BOARD_HARD_RESET, 1] + self._send_command(command) + ''' + report message handlers + ''' + + def _analog_message(self, data): + """ + This is a private message handler method. + It is a message handler for analog messages. + + :param data: message data + + """ + pin = data[0] + value = (data[1] << 8) + data[2] + # set the current value in the pin structure + time_stamp = time.time() + # self.digital_pins[pin].event_time = time_stamp + if self.analog_callbacks[pin]: + message = [PrivateConstants.ANALOG_REPORT, pin, value, time_stamp] + try: + self.analog_callbacks[pin](message) + except KeyError: + pass + + def _dht_report(self, data): + """ + This is the dht report handler method. + + :param data: data[0] = report error return + No Errors = 0 + + Checksum Error = 1 + + Timeout Error = 2 + + Invalid Value = 999 + + data[1] = pin number + + data[2] = dht type 11 or 22 + + data[3] = humidity positivity flag + + data[4] = temperature positivity value + + data[5] = humidity integer + + data[6] = humidity fractional value + + data[7] = temperature integer + + data[8] = temperature fractional value + + + """ + if data[0]: # DHT_ERROR + # error report + # data[0] = report sub type, data[1] = pin, data[2] = error message + if self.dht_callbacks[data[1]]: + # Callback 0=DHT REPORT, DHT_ERROR, PIN, Time + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + time.time()] + try: + self.dht_callbacks[data[1]](message) + except KeyError: + pass + else: + # got valid data DHT_DATA + f_humidity = float(data[5] + data[6] / 100) + if data[3]: + f_humidity *= -1.0 + f_temperature = float(data[7] + data[8] / 100) + if data[4]: + f_temperature *= -1.0 + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + f_humidity, f_temperature, time.time()] + + try: + self.dht_callbacks[data[1]](message) + except KeyError: + pass + + def _digital_message(self, data): + """ + This is a private message handler method. + It is a message handler for Digital Messages. + + :param data: digital message + + """ + pin = data[0] + value = data[1] + + time_stamp = time.time() + if self.digital_callbacks[pin]: + message = [PrivateConstants.DIGITAL_REPORT, pin, value, time_stamp] + self.digital_callbacks[pin](message) + + def _firmware_message(self, data): + """ + Telemetrix4Arduino firmware version message + + :param data: data[0] = major number, data[1] = minor number. + + data[2] = patch number + """ + + self.firmware_version = [data[0], data[1], data[2]] + + def _i2c_read_report(self, data): + """ + Execute callback for i2c reads. + + :param data: [I2C_READ_REPORT, i2c_port, number of bytes read, address, register, bytes read..., time-stamp] + """ + + # we receive [# data bytes, address, register, data bytes] + # number of bytes of data returned + + # data[0] = number of bytes + # data[1] = i2c_port + # data[2] = number of bytes returned + # data[3] = address + # data[4] = register + # data[5] ... all the data bytes + + cb_list = [PrivateConstants.I2C_READ_REPORT, data[0], data[1]] + data[2:] + cb_list.append(time.time()) + + if cb_list[1]: + self.i2c_callback2(cb_list) + else: + self.i2c_callback(cb_list) + + def _i2c_too_few(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'i2c too few bytes received from i2c port {data[0]} i2c address {data[1]}') + + def _i2c_too_many(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'i2c too many bytes received from i2c port {data[0]} i2c address {data[1]}') + + def _i_am_here(self, data): + """ + Reply to are_u_there message + :param data: arduino id + """ + self.reported_arduino_id = data[0] + + def _spi_report(self, report): + + cb_list = [PrivateConstants.SPI_REPORT, report[0]] + report[1:] + + cb_list.append(time.time()) + + self.spi_callback(cb_list) + + def _onewire_report(self, report): + cb_list = [PrivateConstants.ONE_WIRE_REPORT, report[0]] + report[1:] + cb_list.append(time.time()) + self.onewire_callback(cb_list) + + def _report_debug_data(self, data): + """ + Print debug data sent from Arduino + :param data: data[0] is a byte followed by 2 + bytes that comprise an integer + :return: + """ + value = (data[1] << 8) + data[2] + print(f'DEBUG ID: {data[0]} Value: {value}') + + def _report_loop_data(self, data): + """ + Print data that was looped back + :param data: byte of loop back data + :return: + """ + if self.loop_back_callback: + self.loop_back_callback(data) + + def _send_command(self, command): + """ + This is a private utility method. + + + :param command: command data in the form of a list + + """ + # the length of the list is added at the head + command.insert(0, len(command)) + send_message = bytes(command) + + if self.serial_port: + try: + self.serial_port.write(send_message) + except SerialException: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('write fail in _send_command') + else: + raise RuntimeError('No serial port set.') + + def _servo_unavailable(self, report): + """ + Message if no servos are available for use. + :param report: pin number + """ + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Servo Attach For Pin {report[0]} Failed: No Available Servos') + + def _sonar_distance_report(self, report): + """ + + :param report: data[0] = trigger pin, data[1] and data[2] = distance + + callback report format: [PrivateConstants.SONAR_DISTANCE, trigger_pin, distance_value, time_stamp] + """ + + # get callback from pin number + cb = self.sonar_callbacks[report[0]] + + # build report data + cb_list = [PrivateConstants.SONAR_DISTANCE, report[0], + ((report[1] << 8) + report[2]), time.time()] + + cb(cb_list) + + def _stepper_distance_to_go_report(self, report): + return # for now + # """ + # Report stepper distance to go. + # + # :param report: data[0] = motor_id, data[1] = steps MSB, data[2] = steps byte 1, + # data[3] = steps bytes 2, data[4] = steps LSB + # + # callback report format: [PrivateConstants.STEPPER_DISTANCE_TO_GO, motor_id + # steps, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['distance_to_go_callback'] + # + # # isolate the steps bytes and covert list to bytes + # steps = bytes(report[1:]) + # + # # get value from steps + # num_steps = int.from_bytes(steps, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_DISTANCE_TO_GO, report[0], num_steps, + # time.time()] + # + # cb(cb_list) + # + + def _stepper_target_position_report(self, report): + return # for now + # """ + # Report stepper target position to go. + # + # :param report: data[0] = motor_id, data[1] = target position MSB, + # data[2] = target position byte MSB+1 + # data[3] = target position byte MSB+2 + # data[4] = target position LSB + # + # callback report format: [PrivateConstants.STEPPER_TARGET_POSITION, motor_id + # target_position, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['target_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # target = bytes(report[1:]) + # + # # get value from steps + # target_position = int.from_bytes(target, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_TARGET_POSITION, report[0], target_position, + # time.time()] + # + # cb(cb_list) + # + + def _stepper_current_position_report(self, report): + return # for now + # """ + # Report stepper current position. + # + # :param report: data[0] = motor_id, data[1] = current position MSB, + # data[2] = current position byte MSB+1 + # data[3] = current position byte MSB+2 + # data[4] = current position LSB + # + # callback report format: [PrivateConstants.STEPPER_CURRENT_POSITION, motor_id + # current_position, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['current_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # position = bytes(report[1:]) + # + # # get value from steps + # current_position = int.from_bytes(position, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_CURRENT_POSITION, report[0], current_position, + # time.time()] + # + # cb(cb_list) + # + + def _stepper_is_running_report(self, report): + return # for now + # """ + # Report if the motor is currently running + # + # :param report: data[0] = motor_id, True if motor is running or False if it is not. + # + # callback report format: [18, motor_id, + # running_state, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['is_running_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUNNING_REPORT, report[0], time.time()] + # + # cb(cb_list) + # + + def _stepper_run_complete_report(self, report): + return # for now + # """ + # The motor completed it motion + # + # :param report: data[0] = motor_id + # + # callback report format: [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, motor_id, + # time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['motion_complete_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, report[0], + # time.time()] + # + # cb(cb_list) + + def _features_report(self, report): + self.reported_features = report[0] + + def _run_threads(self): + self.run_event.set() + + def _is_running(self): + return self.run_event.is_set() + + def _stop_threads(self): + self.run_event.clear() + + def _reporter(self): + """ + This is the reporter thread. It continuously pulls data from + the deque. When a full message is detected, that message is + processed. + """ + self.run_event.wait() + + while self._is_running() and not self.shutdown_flag: + if len(self.the_deque): + # response_data will be populated with the received data for the report + response_data = [] + packet_length = self.the_deque.popleft() + if packet_length: + # get all the data for the report and place it into response_data + for i in range(packet_length): + while not len(self.the_deque): + time.sleep(self.sleep_tune) + data = self.the_deque.popleft() + response_data.append(data) + + # print(f'response_data {response_data}') + + # get the report type and look up its dispatch method + # here we pop the report type off of response_data + report_type = response_data.pop(0) + # print(f' reported type {report_type}') + + # retrieve the report handler from the dispatch table + dispatch_entry = self.report_dispatch.get(report_type) + + # if there is additional data for the report, + # it will be contained in response_data + # noinspection PyArgumentList + dispatch_entry(response_data) + continue + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'A report with a packet length of zero was received.') + else: + time.sleep(self.sleep_tune) + + def _serial_receiver(self): + """ + Thread to continuously check for incoming data. + When a byte comes in, place it onto the deque. + """ + self.run_event.wait() + + # Don't start this thread if using a tcp/ip transport + + while self._is_running() and not self.shutdown_flag: + # we can get an OSError: [Errno9] Bad file descriptor when shutting down + # just ignore it + try: + if self.serial_port.inWaiting(): + c = self.serial_port.read() + self.the_deque.append(ord(c)) + # print(ord(c)) + else: + time.sleep(self.sleep_tune) + # continue + except OSError: + pass diff --git a/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/__init__.py b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/private_constants.py b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/private_constants.py new file mode 100644 index 0000000..c4b7f48 --- /dev/null +++ b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/private_constants.py @@ -0,0 +1,149 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + +class PrivateConstants: + """ + This class contains a set of constants for telemetrix internal use . + """ + + # commands + # send a loop back request - for debugging communications + LOOP_COMMAND = 0 + SET_PIN_MODE = 1 # set a pin to INPUT/OUTPUT/PWM/etc + DIGITAL_WRITE = 2 # set a single digital pin value instead of entire port + ANALOG_WRITE = 3 + MODIFY_REPORTING = 4 + GET_FIRMWARE_VERSION = 5 + ARE_U_THERE = 6 # Arduino ID query for auto-detect of telemetrix connected boards + SERVO_ATTACH = 7 + SERVO_WRITE = 8 + SERVO_DETACH = 9 + I2C_BEGIN = 10 + I2C_READ = 11 + I2C_WRITE = 12 + SONAR_NEW = 13 + DHT_NEW = 14 + STOP_ALL_REPORTS = 15 + SET_ANALOG_SCANNING_INTERVAL = 16 + ENABLE_ALL_REPORTS = 17 + RESET = 18 + SPI_INIT = 19 + SPI_WRITE_BLOCKING = 20 + SPI_READ_BLOCKING = 21 + SPI_SET_FORMAT = 22 + SPI_CS_CONTROL = 23 + ONE_WIRE_INIT = 24 + ONE_WIRE_RESET = 25 + ONE_WIRE_SELECT = 26 + ONE_WIRE_SKIP = 27 + ONE_WIRE_WRITE = 28 + ONE_WIRE_READ = 29 + ONE_WIRE_RESET_SEARCH = 30 + ONE_WIRE_SEARCH = 31 + ONE_WIRE_CRC8 = 32 + SET_PIN_MODE_STEPPER = 33 + STEPPER_MOVE_TO = 34 + STEPPER_MOVE = 35 + STEPPER_RUN = 36 + STEPPER_RUN_SPEED = 37 + STEPPER_SET_MAX_SPEED = 38 + STEPPER_SET_ACCELERATION = 39 + STEPPER_SET_SPEED = 40 + STEPPER_SET_CURRENT_POSITION = 41 + STEPPER_RUN_SPEED_TO_POSITION = 42 + STEPPER_STOP = 43 + STEPPER_DISABLE_OUTPUTS = 44 + STEPPER_ENABLE_OUTPUTS = 45 + STEPPER_SET_MINIMUM_PULSE_WIDTH = 46 + STEPPER_SET_ENABLE_PIN = 47 + STEPPER_SET_3_PINS_INVERTED = 48 + STEPPER_SET_4_PINS_INVERTED = 49 + STEPPER_IS_RUNNING = 50 + STEPPER_GET_CURRENT_POSITION = 51 + STEPPER_GET_DISTANCE_TO_GO = 52 + STEPPER_GET_TARGET_POSITION = 53 + GET_FEATURES = 54 + SONAR_DISABLE = 55 + SONAR_ENABLE = 56 + BOARD_HARD_RESET = 57 + + # reports + # debug data from Arduino + DIGITAL_REPORT = DIGITAL_WRITE + ANALOG_REPORT = ANALOG_WRITE + FIRMWARE_REPORT = GET_FIRMWARE_VERSION + I_AM_HERE_REPORT = ARE_U_THERE + SERVO_UNAVAILABLE = SERVO_ATTACH + I2C_TOO_FEW_BYTES_RCVD = 8 + I2C_TOO_MANY_BYTES_RCVD = 9 + I2C_READ_REPORT = 10 + SONAR_DISTANCE = 11 + DHT_REPORT = 12 + SPI_REPORT = 13 + ONE_WIRE_REPORT = 14 + STEPPER_DISTANCE_TO_GO = 15 + STEPPER_TARGET_POSITION = 16 + STEPPER_CURRENT_POSITION = 17 + STEPPER_RUNNING_REPORT = 18 + STEPPER_RUN_COMPLETE_REPORT = 19 + FEATURES = 20 + + DEBUG_PRINT = 99 + + TELEMETRIX_AIO_VERSION = "1.0.0" + + # reporting control + REPORTING_DISABLE_ALL = 0 + REPORTING_ANALOG_ENABLE = 1 + REPORTING_DIGITAL_ENABLE = 2 + REPORTING_ANALOG_DISABLE = 3 + REPORTING_DIGITAL_DISABLE = 4 + + # Pin mode definitions + AT_INPUT = 0 + AT_OUTPUT = 1 + AT_INPUT_PULLUP = 2 + AT_ANALOG = 3 + AT_SERVO = 4 + AT_SONAR = 5 + AT_DHT = 6 + AT_MODE_NOT_SET = 255 + + # maximum number of digital pins supported + NUMBER_OF_DIGITAL_PINS = 100 + + # maximum number of analog pins supported + NUMBER_OF_ANALOG_PINS = 20 + + # maximum number of sonars allowed + MAX_SONARS = 6 + + # maximum number of DHT devices allowed + MAX_DHTS = 6 + + # DHT Report sub-types + DHT_DATA = 0 + DHT_ERROR = 1 + +# feature masks + ONEWIRE_FEATURE = 0x01 + DHT_FEATURE = 0x02 + STEPPERS_FEATURE = 0x04 + SPI_FEATURE = 0x08 + SERVO_FEATURE = 0x10 + SONAR_FEATURE = 0x20 \ No newline at end of file diff --git a/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/telemetrix_aio_serial.py b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/telemetrix_aio_serial.py new file mode 100644 index 0000000..2fa6e05 --- /dev/null +++ b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/telemetrix_aio_serial.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +""" + Copyright (c) 2015-2020 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import serial +import time + +LF = 0x0a + + +# noinspection PyStatementEffect,PyUnresolvedReferences,PyUnresolvedReferences +class TelemetrixAioSerial: + """ + This class encapsulates management of the serial port that communicates + with the Arduino Firmata + It provides a 'futures' interface to make Pyserial compatible with asyncio + """ + + def __init__(self, com_port='/dev/ttyACM0', baud_rate=115200, sleep_tune=.0001, + telemetrix_aio_instance=None, close_loop_on_error=True): + + """ + This is the constructor for the aio serial handler + + :param com_port: Com port designator + + :param baud_rate: UART baud rate + + :param telemetrix_aio_instance: reference to caller + + :return: None + """ + # print('Initializing Arduino - Please wait...', end=" ") + sys.stdout.flush() + self.my_serial = serial.Serial(com_port, baud_rate, timeout=1, + writeTimeout=1) + + self.com_port = com_port + self.sleep_tune = sleep_tune + self.telemetrix_aio_instance = telemetrix_aio_instance + self.close_loop_on_error = close_loop_on_error + + # used by read_until + self.start_time = None + + async def get_serial(self): + """ + This method returns a reference to the serial port in case the + user wants to call pyserial methods directly + + :return: pyserial instance + """ + return self.my_serial + + async def write(self, data): + """ + This is an asyncio adapted version of pyserial write. It provides a + non-blocking write and returns the number of bytes written upon + completion + + :param data: Data to be written + :return: Number of bytes written + """ + # the secret sauce - it is in your future + future = asyncio.Future() + result = None + try: + # result = self.my_serial.write(bytes([ord(data)])) + result = self.my_serial.write(bytes(data)) + + except serial.SerialException: + # noinspection PyBroadException + loop = None + await self.close() + future.cancel() + if self.close_loop_on_error: + loop = asyncio.get_event_loop() + loop.stop() + + if self.telemetrix_aio_instance.the_task: + self.telemetrix_aio_instance.the_task.cancel() + await asyncio.sleep(1) + if self.close_loop_on_error: + loop.close() + + if result: + future.set_result(result) + while True: + if not future.done(): + # spin our asyncio wheels until future completes + await asyncio.sleep(self.sleep_tune) + + else: + return future.result() + + async def read(self, size=1): + """ + This is an asyncio adapted version of pyserial read + that provides non-blocking read. + + :return: One character + """ + + # create an asyncio Future + future = asyncio.Future() + + # create a flag to indicate when data becomes available + data_available = False + + # wait for a character to become available and read from + # the serial port + while True: + if not data_available: + # test to see if a character is waiting to be read. + # if not, relinquish control back to the event loop through the + # short sleep + if not self.my_serial.in_waiting: + await asyncio.sleep(self.sleep_tune*2) + + # data is available. + # set the flag to true so that the future can "wait" until the + # read is completed. + else: + data_available = True + data = self.my_serial.read(size) + # set future result to make the character available + if size == 1: + future.set_result(ord(data)) + else: + future.set_result(list(data)) + else: + # wait for the future to complete + if not future.done(): + await asyncio.sleep(self.sleep_tune) + else: + # future is done, so return the character + return future.result() + + async def read_until(self, expected=LF, size=None, timeout=1): + """ + This is an asyncio adapted version of pyserial read + that provides non-blocking read. + + :return: Data delimited by expected + """ + + expected = str(expected).encode() + # create an asyncio Future + future = asyncio.Future() + + # create a flag to indicate when data becomes available + data_available = False + + if timeout: + self.start_time = time.time() + + # wait for a character to become available and read from + # the serial port + while True: + if not data_available: + # test to see if a character is waiting to be read. + # if not, relinquish control back to the event loop through the + # short sleep + if not self.my_serial.in_waiting: + if timeout: + elapsed_time = time.time() - self.start_time + if elapsed_time > timeout: + return None + await asyncio.sleep(self.sleep_tune) + # data is available. + # set the flag to true so that the future can "wait" until the + # read is completed. + else: + data_available = True + data = self.my_serial.read_until(expected, size) + # set future result to make the character available + return_value = list(data) + future.set_result(return_value) + else: + # wait for the future to complete + if not future.done(): + await asyncio.sleep(self.sleep_tune) + else: + # future is done, so return the character + return future.result() + + async def reset_input_buffer(self): + """ + Reset the input buffer + """ + self.my_serial.reset_input_buffer() + + async def close(self): + """ + Close the serial port + """ + if self.my_serial: + self.my_serial.close() diff --git a/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/telemetrix_uno_r4_minima_aio.py b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/telemetrix_uno_r4_minima_aio.py new file mode 100644 index 0000000..d550c0c --- /dev/null +++ b/telemetrix_uno_r4/minima/telemetrix_uno_r4_minima_aio/telemetrix_uno_r4_minima_aio.py @@ -0,0 +1,2500 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +# noinspection PyPackageRequirementscd +from serial.serialutil import SerialException +# noinspection PyPackageRequirements +from serial.tools import list_ports + +# noinspection PyUnresolvedReferences +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio.private_constants import PrivateConstants +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio.telemetrix_aio_serial import TelemetrixAioSerial + + +# noinspection GrazieInspection,PyArgumentList,PyMethodMayBeStatic,PyRedundantParentheses +class TelemetrixUnoR4MinimaAio: + """ + This class exposes and implements the TelemetrixAIO API. + It includes the public API methods as well as + a set of private methods. This is an asyncio API. + + """ + + # noinspection PyPep8,PyPep8 + def __init__(self, com_port=None, + arduino_instance_id=1, arduino_wait=1, + sleep_tune=0.0001, autostart=True, + loop=None, shutdown_on_exception=True, + close_loop_on_shutdown=True, hard_reset_on_shutdown=True): + + """ + If you have a single Arduino connected to your computer, + then you may accept all the default values. + + Otherwise, specify a unique arduino_instance id for each board in use. + + :param com_port: e.g. COM3 or /dev/ttyACM0. + + :param arduino_instance_id: Must match value in the Telemetrix4Arduino sketch + + :param arduino_wait: Amount of time to wait for an Arduino to + fully reset itself. + + :param sleep_tune: A tuning parameter (typically not changed by user) + + :param autostart: If you wish to call the start method within + your application, then set this to False. + + :param loop: optional user provided event loop + + :param shutdown_on_exception: call shutdown before raising + a RunTimeError exception, or + receiving a KeyboardInterrupt exception + + :param close_loop_on_shutdown: stop and close the event loop loop + when a shutdown is called or a serial + error occurs + + :param hard_reset_on_shutdown: reset the board on shutdown + + """ + # check to make sure that Python interpreter is version 3.8.3 or greater + python_version = sys.version_info + if python_version[0] >= 3: + if python_version[1] >= 8: + if python_version[2] >= 3: + pass + else: + raise RuntimeError("ERROR: Python 3.7 or greater is " + "required for use of this program.") + + # save input parameters + self.com_port = com_port + self.arduino_instance_id = arduino_instance_id + self.arduino_wait = arduino_wait + self.sleep_tune = sleep_tune + self.autostart = autostart + self.hard_reset_on_shutdown = hard_reset_on_shutdown + + # set the event loop + if loop is None: + self.loop = asyncio.get_event_loop() + else: + self.loop = loop + + self.shutdown_on_exception = shutdown_on_exception + self.close_loop_on_shutdown = close_loop_on_shutdown + + # dictionaries to store the callbacks for each pin + self.analog_callbacks = {} + + self.digital_callbacks = {} + + self.i2c_callback = None + self.i2c_callback2 = None + + self.i2c_1_active = False + self.i2c_2_active = False + + self.spi_callback = None + + self.onewire_callback = None + + # debug loopback callback method + self.loop_back_callback = None + + # the trigger pin will be the key to retrieve + # the callback for a specific HC-SR04 + self.sonar_callbacks = {} + + self.sonar_count = 0 + + self.dht_callbacks = {} + + self.dht_count = 0 + + # serial port in use + self.serial_port = None + + # generic asyncio task holder + self.the_task = None + + # flag to indicate we are in shutdown mode + self.shutdown_flag = False + + self.report_dispatch = {} + + # reported features + self.reported_features = 0 + + # To add a command to the command dispatch table, append here. + self.report_dispatch.update( + {PrivateConstants.LOOP_COMMAND: self._report_loop_data}) + self.report_dispatch.update( + {PrivateConstants.DEBUG_PRINT: self._report_debug_data}) + self.report_dispatch.update( + {PrivateConstants.DIGITAL_REPORT: self._digital_message}) + self.report_dispatch.update( + {PrivateConstants.ANALOG_REPORT: self._analog_message}) + self.report_dispatch.update( + {PrivateConstants.SERVO_UNAVAILABLE: self._servo_unavailable}) + self.report_dispatch.update( + {PrivateConstants.I2C_READ_REPORT: self._i2c_read_report}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_FEW_BYTES_RCVD: self._i2c_too_few}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_MANY_BYTES_RCVD: self._i2c_too_many}) + self.report_dispatch.update( + {PrivateConstants.SONAR_DISTANCE: self._sonar_distance_report}) + self.report_dispatch.update({PrivateConstants.DHT_REPORT: self._dht_report}) + self.report_dispatch.update( + {PrivateConstants.SPI_REPORT: self._spi_report}) + self.report_dispatch.update( + {PrivateConstants.ONE_WIRE_REPORT: self._onewire_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_CURRENT_POSITION: + self._stepper_current_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUNNING_REPORT: + self._stepper_is_running_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUN_COMPLETE_REPORT: + self._stepper_run_complete_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.FEATURES: + self._features_report}) + + # dictionaries to store the callbacks for each pin + self.analog_callbacks = {} + + self.digital_callbacks = {} + + self.i2c_callback = None + self.i2c_callback2 = None + + self.i2c_1_active = False + self.i2c_2_active = False + + self.spi_callback = None + + self.onewire_callback = None + + self.cs_pins_enabled = [] + + # flag to indicate if spi is initialized + self.spi_enabled = False + + # flag to indicate if onewire is initialized + self.onewire_enabled = False + + # the trigger pin will be the key to retrieve + # the callback for a specific HC-SR04 + self.sonar_callbacks = {} + + self.sonar_count = 0 + + self.dht_callbacks = {} + + # # stepper motor variables + # + # # updated when a new motor is added + # self.next_stepper_assigned = 0 + # + # # valid list of stepper motor interface types + # self.valid_stepper_interfaces = [1, 2, 3, 4, 6, 8] + # + # # maximum number of steppers supported + # self.max_number_of_steppers = 4 + # + # # number of steppers created - not to exceed the maximum + # self.number_of_steppers = 0 + # + # # dictionary to hold stepper motor information + # self.stepper_info = {'instance': False, 'is_running': None, + # 'maximum_speed': 1, 'speed': 0, 'acceleration': 0, + # 'distance_to_go_callback': None, + # 'target_position_callback': None, + # 'current_position_callback': None, + # 'is_running_callback': None, + # 'motion_complete_callback': None, + # 'acceleration_callback': None} + # + # # build a list of stepper motor info items + # self.stepper_info_list = [] + # # a list of dictionaries to hold stepper information + # for motor in range(self.max_number_of_steppers): + # self.stepper_info_list.append(self.stepper_info) + + print(f'telemetrix_uno_r4_minima_aio Version:' + f' {PrivateConstants.TELEMETRIX_AIO_VERSION}') + print(f'Copyright (c) 2023 Alan Yorinks All rights reserved.\n') + + if autostart: + self.loop.run_until_complete(self.start_aio()) + + async def start_aio(self): + """ + This method may be called directly, if the autostart + parameter in __init__ is set to false. + + This method instantiates the serial interface and then performs auto pin + discovery if using a serial interface, or creates and connects to + a TCP/IP enabled device running StandardFirmataWiFi. + + Use this method if you wish to start TelemetrixAIO manually from + an asyncio function. + """ + + if not self.com_port: + # user did not specify a com_port + try: + await self._find_arduino() + except KeyboardInterrupt: + if self.shutdown_on_exception: + await self.shutdown() + else: + # com_port specified - set com_port and baud rate + try: + await self._manual_open() + except KeyboardInterrupt: + if self.shutdown_on_exception: + await self.shutdown() + + if self.com_port: + print(f'Telemetrix4UnoR4 found and connected to {self.com_port}') + + # no com_port found - raise a runtime exception + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('No Arduino Found or User Aborted Program') + + # get arduino firmware version and print it + firmware_version = await self._get_firmware_version() + if not firmware_version: + print('*** Firmware Version retrieval timed out. ***') + print('\nDo you have Arduino connectivity and do you have the ') + print('Telemetrix4UnoR4 sketch uploaded to the board and are connected') + print('to the correct serial port.\n') + print('To see a list of serial ports, type: ' + '"list_serial_ports" in your console.') + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError + else: + + print(f'Telemetrix4UnoR4 Version Number: {firmware_version[2]}.' + f'{firmware_version[3]}.{firmware_version[4]}') + # start the command dispatcher loop + command = [PrivateConstants.ENABLE_ALL_REPORTS] + await self._send_command(command) + if not self.loop: + self.loop = asyncio.get_event_loop() + self.the_task = self.loop.create_task(self._arduino_report_dispatcher()) + + # get the features list + command = [PrivateConstants.GET_FEATURES] + await self._send_command(command) + await asyncio.sleep(.5) + + # Have the server reset its data structures + command = [PrivateConstants.RESET] + await self._send_command(command) + + async def get_event_loop(self): + """ + Return the currently active asyncio event loop + + :return: Active event loop + + """ + return self.loop + + async def _find_arduino(self): + """ + This method will search all potential serial ports for an Arduino + containing a sketch that has a matching arduino_instance_id as + specified in the input parameters of this class. + + This is used explicitly with the FirmataExpress sketch. + """ + + # a list of serial ports to be checked + serial_ports = [] + + print('Opening all potential serial ports...') + the_ports_list = list_ports.comports() + for port in the_ports_list: + if port.pid is None: + continue + print('\nChecking {}'.format(port.device)) + try: + self.serial_port = TelemetrixAioSerial(port.device, 115200, + telemetrix_aio_instance=self, + close_loop_on_error=self.close_loop_on_shutdown) + except SerialException: + continue + # create a list of serial ports that we opened + serial_ports.append(self.serial_port) + + # display to the user + print('\t' + port.device) + + # clear out any possible data in the input buffer + await self.serial_port.reset_input_buffer() + + # wait for arduino to reset + print('\nWaiting {} seconds(arduino_wait) for Arduino devices to ' + 'reset...'.format(self.arduino_wait)) + await asyncio.sleep(self.arduino_wait) + + print('\nSearching for an Arduino configured with an arduino_instance = ', + self.arduino_instance_id) + + for serial_port in serial_ports: + self.serial_port = serial_port + + command = [PrivateConstants.ARE_U_THERE] + await self._send_command(command) + # provide time for the reply + await asyncio.sleep(.1) + + i_am_here = await self.serial_port.read(3) + + if not i_am_here: + continue + + # got an I am here message - is it the correct ID? + if i_am_here[2] == self.arduino_instance_id: + self.com_port = serial_port.com_port + return + + async def _manual_open(self): + """ + Com port was specified by the user - try to open up that port + + """ + # if port is not found, a serial exception will be thrown + print('Opening {} ...'.format(self.com_port)) + self.serial_port = TelemetrixAioSerial(self.com_port, 115200, + telemetrix_aio_instance=self, + close_loop_on_error=self.close_loop_on_shutdown) + + print('Waiting {} seconds for the Arduino To Reset.' + .format(self.arduino_wait)) + await asyncio.sleep(self.arduino_wait) + command = [PrivateConstants.ARE_U_THERE] + await self._send_command(command) + # provide time for the reply + await asyncio.sleep(.1) + + print(f'Searching for correct arduino_instance_id: {self.arduino_instance_id}') + i_am_here = await self.serial_port.read(3) + + if not i_am_here: + print(f'ERROR: correct arduino_instance_id not found') + + print('Correct arduino_instance_id found') + + async def _get_firmware_version(self): + """ + This method retrieves the Arduino4Telemetrix firmware version + + :returns: Firmata firmware version + """ + command = [PrivateConstants.GET_FIRMWARE_VERSION] + await self._send_command(command) + # provide time for the reply + await asyncio.sleep(.1) + firmware_version = await self.serial_port.read(5) + + return firmware_version + + async def analog_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (maximum 16 bits) + + """ + value_msb = value >> 8 + value_lsb = value & 0xff + command = [PrivateConstants.ANALOG_WRITE, pin, value_msb, value_lsb] + await self._send_command(command) + + async def digital_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (1 or 0) + + """ + command = [PrivateConstants.DIGITAL_WRITE, pin, value] + await self._send_command(command) + + async def i2c_read(self, address, register, number_of_bytes, + callback, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the specified register for + the i2c device. + + + :param address: i2c device address + + :param register: i2c register (or None if no register selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report i2c data as a + result of read command + + :param i2c_port: select the default port (0) or secondary port (1) + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('i2c_read: A Callback must be specified') + + await self._i2c_read_request(address, register, number_of_bytes, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + async def i2c_read_restart_transmission(self, address, register, + number_of_bytes, + callback, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the specified register for + the i2c device. This restarts the transmission after the read. It is + required for some i2c devices such as the MMA8452Q accelerometer. + + + :param address: i2c device address + + :param register: i2c register (or None if no register + selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report i2c data as a + result of read command + + :param i2c_port: select the default port (0) or secondary port (1) + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'i2c_read_restart_transmission: A Callback must be specified') + + await self._i2c_read_request(address, register, number_of_bytes, + stop_transmission=False, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + async def _i2c_read_request(self, address, register, number_of_bytes, + stop_transmission=True, callback=None, + i2c_port=0, write_register=True): + """ + This method requests the read of an i2c device. Results are retrieved + via callback. + + :param address: i2c device address + + :param register: register number (or None if no register selection is needed) + + :param number_of_bytes: number of bytes expected to be returned + + :param stop_transmission: stop transmission after read + + :param callback: Required callback function to report i2c data as a + result of read command. + + :param i2c_port: select the default port (0) or secondary port (1) + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 2.') + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('I2C Read: A callback function must be specified.') + + if not i2c_port: + self.i2c_callback = callback + else: + self.i2c_callback2 = callback + + if not register: + register = 0 + + if write_register: + write_register = 1 + else: + write_register = 0 + + # message contains: + # 1. address + # 2. register + # 3. number of bytes + # 4. restart_transmission - True or False + # 5. i2c port + # 6. suppress write flag + + command = [PrivateConstants.I2C_READ, address, register, number_of_bytes, + stop_transmission, i2c_port, write_register] + await self._send_command(command) + + async def i2c_write(self, address, args, i2c_port=0): + """ + Write data to an i2c device. + + :param address: i2c device address + + :param i2c_port: 0= port 1, 1 = port 2 + + :param args: A variable number of bytes to be sent to the device + passed in as a list + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 2.') + + command = [PrivateConstants.I2C_WRITE, len(args), address, i2c_port] + + for item in args: + command.append(item) + + await self._send_command(command) + + async def loop_back(self, start_character, callback): + """ + This is a debugging method to send a character to the + Arduino device, and have the device loop it back. + + :param start_character: The character to loop back. It should be + an integer. + + :param callback: Looped back character will appear in the callback method + + """ + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('loop_back: A callback function must be specified.') + command = [PrivateConstants.LOOP_COMMAND, ord(start_character)] + self.loop_back_callback = callback + await self._send_command(command) + + async def set_analog_scan_interval(self, interval): + """ + Set the analog scanning interval. + + :param interval: value of 0 - 255 - milliseconds + """ + + if 0 <= interval <= 255: + command = [PrivateConstants.SET_ANALOG_SCANNING_INTERVAL, interval] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('Analog interval must be between 0 and 255') + + async def set_pin_mode_analog_input(self, pin_number, differential=0, callback=None): + """ + Set a pin as an analog input. + + :param pin_number: arduino pin number + + :param callback: async callback function + + :param differential: difference in previous to current value before + report will be generated + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for analog input pins = 3 + + """ + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'set_pin_mode_analog_input: A callback function must be specified.') + + await self._set_pin_mode(pin_number, PrivateConstants.AT_ANALOG, + differential, callback=callback) + + async def set_pin_mode_analog_output(self, pin_number): + """ + + Set a pin as a pwm (analog output) pin. + + :param pin_number:arduino pin number + + """ + + await self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT, differential=0, + callback=None) + + async def set_pin_mode_digital_input(self, pin_number, callback): + """ + Set a pin as a digital input. + + :param pin_number: arduino pin number + + :param callback: async callback function + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + + """ + await self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT, differential=0, + callback=callback) + + async def set_pin_mode_digital_input_pullup(self, pin_number, callback): + """ + Set a pin as a digital input with pullup enabled. + + :param pin_number: arduino pin number + + :param callback: async callback function + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + + """ + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'set_pin_mode_digital_input_pullup: A callback function must be specified.') + + await self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT_PULLUP, + differential=0, callback=callback) + + async def set_pin_mode_digital_output(self, pin_number): + """ + Set a pin as a digital output pin. + + :param pin_number: arduino pin number + """ + + await self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT, differential=0, + callback=None) + + # noinspection PyIncorrectDocstring + async def set_pin_mode_i2c(self, i2c_port=0): + """ + Establish the standard Arduino i2c pins for i2c utilization. + + :param i2c_port: 0 = i2c1, 1 = i2c2 + + NOTES: 1. THIS METHOD MUST BE CALLED BEFORE ANY I2C REQUEST IS MADE + 2. Callbacks are set within the individual i2c read methods of this + API. + + See i2c_read, or i2c_read_restart_transmission. + + """ + # test for i2c port 2 + if i2c_port: + # if not previously activated set it to activated + # and the send a begin message for this port + if not self.i2c_2_active: + self.i2c_2_active = True + else: + return + # port 1 + else: + if not self.i2c_1_active: + self.i2c_1_active = True + else: + return + + command = [PrivateConstants.I2C_BEGIN, i2c_port] + await self._send_command(command) + + async def set_pin_mode_dht(self, pin, callback=None, dht_type=22): + """ + + :param pin: connection pin + + :param callback: callback function + + :param dht_type: either 22 for DHT22 or 11 for DHT11 + + Error Callback: [DHT REPORT Type, DHT_ERROR_NUMBER, PIN, DHT_TYPE, Time] + + Valid Data Callback: DHT REPORT Type, DHT_DATA=, PIN, DHT_TYPE, Humidity, + Temperature, + Time] + + """ + if self.reported_features & PrivateConstants.DHT_FEATURE: + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('set_pin_mode_dht: A Callback must be specified') + + if self.dht_count < PrivateConstants.MAX_DHTS - 1: + self.dht_callbacks[pin] = callback + self.dht_count += 1 + + if dht_type != 22 and dht_type != 11: + dht_type = 22 + + command = [PrivateConstants.DHT_NEW, pin, dht_type] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'Maximum Number Of DHTs Exceeded - set_pin_mode_dht fails for pin {pin}') + + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The DHT feature is disabled in the server.') + + async def set_pin_mode_servo(self, pin_number, min_pulse=544, max_pulse=2400): + """ + + Attach a pin to a servo motor + + :param pin_number: pin + + :param min_pulse: minimum pulse width + + :param max_pulse: maximum pulse width + + """ + if self.reported_features & PrivateConstants.SERVO_FEATURE: + + minv = (min_pulse).to_bytes(2, byteorder="big") + maxv = (max_pulse).to_bytes(2, byteorder="big") + + command = [PrivateConstants.SERVO_ATTACH, pin_number, + minv[0], minv[1], maxv[0], maxv[1]] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The SERVO feature is disabled in the server.') + + async def set_pin_mode_sonar(self, trigger_pin, echo_pin, + callback): + """ + + :param trigger_pin: + + :param echo_pin: + + :param callback: callback + + """ + if self.reported_features & PrivateConstants.SONAR_FEATURE: + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('set_pin_mode_sonar: A Callback must be specified') + + if self.sonar_count < PrivateConstants.MAX_SONARS - 1: + self.sonar_callbacks[trigger_pin] = callback + self.sonar_count += 1 + + command = [PrivateConstants.SONAR_NEW, trigger_pin, echo_pin] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'Maximum Number Of Sonars Exceeded - set_pin_mode_sonar fails for pin {trigger_pin}') + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The SONAR feature is disabled in the server.') + + async def set_pin_mode_spi(self, chip_select_list=None): + """ + Specify the list of chip select pins. + + Standard Arduino MISO, MOSI and CLK pins are used for the board in use. + + Chip Select is any digital output capable pin. + + :param chip_select_list: this is a list of pins to be used for chip select. + The pins will be configured as output, and set to high + ready to be used for chip select. + NOTE: You must specify the chips select pins here! + + + command message: [command, number of cs pins, [cs pins...]] + """ + if self.reported_features & PrivateConstants.SPI_FEATURE: + + if type(chip_select_list) != list: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('chip_select_list must be in the form of a list') + if not chip_select_list: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('Chip select pins were not specified') + + self.spi_enabled = True + + command = [PrivateConstants.SPI_INIT, len(chip_select_list)] + + for pin in chip_select_list: + command.append(pin) + self.cs_pins_enabled.append(pin) + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The SPI feature is disabled in the server.') + + # async def set_pin_mode_stepper(self, interface=1, pin1=2, pin2=3, pin3=4, + # pin4=5, enable=True): + # """ + # Stepper motor support is implemented as a proxy for the + # the AccelStepper library for the Arduino. + # + # This feature is compatible with the TB6600 Motor Driver + # + # Note: It may not work for other driver types! + # + # https://github.com/waspinator/AccelStepper + # + # Instantiate a stepper motor. + # + # Initialize the interface and pins for a stepper motor. + # + # :param interface: Motor Interface Type: + # + # 1 = Stepper Driver, 2 driver pins required + # + # 2 = FULL2WIRE 2 wire stepper, 2 motor pins required + # + # 3 = FULL3WIRE 3 wire stepper, such as HDD spindle, + # 3 motor pins required + # + # 4 = FULL4WIRE, 4 wire full stepper, 4 motor pins + # required + # + # 6 = HALF3WIRE, 3 wire half stepper, such as HDD spindle, + # 3 motor pins required + # + # 8 = HALF4WIRE, 4 wire half stepper, 4 motor pins required + # + # :param pin1: Arduino digital pin number for motor pin 1 + # + # :param pin2: Arduino digital pin number for motor pin 2 + # + # :param pin3: Arduino digital pin number for motor pin 3 + # + # :param pin4: Arduino digital pin number for motor pin 4 + # + # :param enable: If this is true, the output pins at construction time. + # + # :return: Motor Reference number + # """ + # if self.reported_features & PrivateConstants.STEPPERS_FEATURE: + # + # if self.number_of_steppers == self.max_number_of_steppers: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('Maximum number of steppers has already been assigned') + # + # if interface not in self.valid_stepper_interfaces: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('Invalid stepper interface') + # + # self.number_of_steppers += 1 + # + # motor_id = self.next_stepper_assigned + # self.next_stepper_assigned += 1 + # self.stepper_info_list[motor_id]['instance'] = True + # + # # build message and send message to server + # command = [PrivateConstants.SET_PIN_MODE_STEPPER, motor_id, interface, pin1, + # pin2, pin3, pin4, enable] + # await self._send_command(command) + # + # # return motor id + # return motor_id + # else: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'The Stepper feature is disabled in the server.') + + async def sonar_disable(self): + """ + Disable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_DISABLE] + await self._send_command(command) + + async def sonar_enable(self): + """ + Enable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_ENABLE] + await self._send_command(command) + + async def spi_cs_control(self, chip_select_pin, select): + """ + Control an SPI chip select line + :param chip_select_pin: pin connected to CS + + :param select: 0=select, 1=deselect + """ + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_cs_control: SPI interface is not enabled.') + + if chip_select_pin not in self.cs_pins_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_cs_control: chip select pin never enabled.') + command = [PrivateConstants.SPI_CS_CONTROL, chip_select_pin, select] + await self._send_command(command) + + async def spi_read_blocking(self, chip_select, register_selection, + number_of_bytes_to_read, + call_back=None): + """ + Read the specified number of bytes from the specified SPI port and + call the callback function with the reported data. + + :param chip_select: chip select pin + + :param register_selection: Register to be selected for read. + + :param number_of_bytes_to_read: Number of bytes to read + + :param call_back: Required callback function to report spi data as a + result of read command + + + callback returns a data list: + [SPI_READ_REPORT, chip select pin, SPI Register, count of data bytes read, + data bytes, time-stamp] + SPI_READ_REPORT = 13 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_read_blocking: SPI interface is not enabled.') + + if not call_back: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('spi_read_blocking: A Callback must be specified') + + self.spi_callback = call_back + + command = [PrivateConstants.SPI_READ_BLOCKING, chip_select, + number_of_bytes_to_read, + register_selection] + + await self._send_command(command) + + async def spi_set_format(self, clock_divisor, bit_order, data_mode): + """ + Configure how the SPI serializes and de-serializes data on the wire. + + See Arduino SPI reference materials for details. + + :param clock_divisor: 1 - 255 + + :param bit_order: + + LSBFIRST = 0 + + MSBFIRST = 1 (default) + + :param data_mode: + + SPI_MODE0 = 0x00 (default) + + SPI_MODE1 = 1 + + SPI_MODE2 = 2 + + SPI_MODE3 = 3 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_set_format: SPI interface is not enabled.') + + if not 0 < clock_divisor <= 255: + raise RuntimeError(f'spi_set_format: illegal clock divisor selected.') + if bit_order not in [0, 1]: + raise RuntimeError(f'spi_set_format: illegal bit_order selected.') + if data_mode not in [0, 1, 2, 3]: + raise RuntimeError(f'spi_set_format: illegal data_order selected.') + + command = [PrivateConstants.SPI_SET_FORMAT, clock_divisor, bit_order, + data_mode] + await self._send_command(command) + + async def spi_write_blocking(self, chip_select, bytes_to_write): + """ + Write a list of bytes to the SPI device. + + :param chip_select: chip select pin + + :param bytes_to_write: A list of bytes to write. This must + be in the form of a list. + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_write_blocking: SPI interface is not enabled.') + + if type(bytes_to_write) is not list: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('spi_write_blocking: bytes_to_write must be a list.') + + command = [PrivateConstants.SPI_WRITE_BLOCKING, chip_select, len(bytes_to_write)] + + for data in bytes_to_write: + command.append(data) + + await self._send_command(command) + + # async def set_pin_mode_one_wire(self, pin): + # """ + # Initialize the one wire serial bus. + # + # :param pin: Data pin connected to the OneWire device + # """ + # self.onewire_enabled = True + # command = [PrivateConstants.ONE_WIRE_INIT, pin] + # await self._send_command(command) + # + # async def onewire_reset(self, callback=None): + # """ + # Reset the onewire device + # + # :param callback: required function to report reset result + # + # callback returns a list: + # [ReportType = 14, Report Subtype = 25, reset result byte, + # timestamp] + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_reset: OneWire interface is not enabled.') + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_reset: A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_RESET] + # await self._send_command(command) + # + # async def onewire_select(self, device_address): + # """ + # Select a device based on its address + # :param device_address: A bytearray of 8 bytes + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_select: OneWire interface is not enabled.') + # + # if type(device_address) is not list: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # + # if len(device_address) != 8: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # command = [PrivateConstants.ONE_WIRE_SELECT] + # for data in device_address: + # command.append(data) + # await self._send_command(command) + # + # async def onewire_skip(self): + # """ + # Skip the device selection. This only works if you have a + # single device, but you can avoid searching and use this to + # immediately access your device. + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_skip: OneWire interface is not enabled.') + # + # command = [PrivateConstants.ONE_WIRE_SKIP] + # await self._send_command(command) + # + # async def onewire_write(self, data, power=0): + # """ + # Write a byte to the onewire device. If 'power' is one + # then the wire is held high at the end for + # parasitically powered devices. You + # are responsible for eventually de-powering it by calling + # another read or write. + # + # :param data: byte to write. + # :param power: power control (see above) + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_write: OneWire interface is not enabled.') + # if 0 < data < 255: + # command = [PrivateConstants.ONE_WIRE_WRITE, data, power] + # await self._send_command(command) + # else: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_write: Data must be no larger than 255') + # + # async def onewire_read(self, callback=None): + # """ + # Read a byte from the onewire device + # :param callback: required function to report onewire data as a + # result of read command + # + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_READ=29, data byte, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_read: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_READ] + # await self._send_command(command) + # + # async def onewire_reset_search(self): + # """ + # Begin a new search. The next use of search will begin at the first device + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_reset_search: OneWire interface is not ' + # f'enabled.') + # else: + # command = [PrivateConstants.ONE_WIRE_RESET_SEARCH] + # await self._send_command(command) + # + # async def onewire_search(self, callback=None): + # """ + # Search for the next device. The device address will returned in the callback. + # If a device is found, the 8 byte address is contained in the callback. + # If no more devices are found, the address returned contains all elements set + # to 0xff. + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_SEARCH=31, 8 byte address, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_search: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_SEARCH] + # await self._send_command(command) + # + # async def onewire_crc8(self, address_list, callback=None): + # """ + # Compute a CRC check on an array of data. + # :param address_list: + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_CRC8=32, CRC, time-stamp] + # + # ONEWIRE_REPORT = 14 + # + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_crc8: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_crc8 A Callback must be specified') + # + # if type(address_list) is not list: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_crc8: address list must be a list.') + # + # self.onewire_callback = callback + # + # address_length = len(address_list) + # + # command = [PrivateConstants.ONE_WIRE_CRC8, address_length - 1] + # + # for data in address_list: + # command.append(data) + # + # await self._send_command(command) + + async def _set_pin_mode(self, pin_number, pin_state, differential, callback): + """ + A private method to set the various pin modes. + + :param pin_number: arduino pin number + + :param pin_state: INPUT/OUTPUT/ANALOG/PWM/PULLUP - for SERVO use + servo_config() + For DHT use: set_pin_mode_dht + + :param differential: for analog inputs - threshold + value to be achieved for report to + be generated + + :param callback: A reference to an async call back function to be + called when pin data value changes + + """ + if not callback and pin_state != PrivateConstants.AT_OUTPUT: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('_set_pin_mode: A Callback must be specified') + else: + if pin_state == PrivateConstants.AT_INPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT, 1] + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_INPUT_PULLUP: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT_PULLUP, 1] + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_ANALOG: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_ANALOG, + differential >> 8, differential & 0xff, 1] + self.analog_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_OUTPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_OUTPUT, 1] + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('Unknown pin state') + + if command: + await self._send_command(command) + + await asyncio.sleep(.05) + + async def servo_detach(self, pin_number): + """ + Detach a servo for reuse + :param pin_number: attached pin + """ + command = [PrivateConstants.SERVO_DETACH, pin_number] + await self._send_command(command) + + async def servo_write(self, pin_number, angle): + """ + + Set a servo attached to a pin to a given angle. + + :param pin_number: pin + + :param angle: angle (0-180) + + """ + command = [PrivateConstants.SERVO_WRITE, pin_number, angle] + await self._send_command(command) + + # async def stepper_move_to(self, motor_id, position): + # """ + # Set an absolution target position. If position is positive, the movement is + # clockwise, else it is counter-clockwise. + # + # The run() function (below) will try to move the motor (at most one step per call) + # from the current position to the target position set by the most + # recent call to this function. Caution: moveTo() also recalculates the + # speed for the next step. + # If you are trying to use constant speed movements, you should call setSpeed() + # after calling moveTo(). + # + # :param motor_id: motor id: 0 - 3 + # + # :param position: target position. Maximum value is 32 bits. + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_move_to: Invalid motor_id.') + # + # if position < 0: + # polarity = 1 + # else: + # polarity = 0 + # position = abs(position) + # + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE_TO, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # + # await self._send_command(command) + # + # async def stepper_move(self, motor_id, relative_position): + # """ + # Set the target position relative to the current position. + # + # :param motor_id: motor id: 0 - 3 + # + # :param relative_position: The desired position relative to the current + # position. Negative is anticlockwise from + # the current position. Maximum value is 32 bits. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_move: Invalid motor_id.') + # + # if relative_position < 0: + # polarity = 1 + # else: + # polarity = 0 + # position = abs(relative_position) + # + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # await self._send_command(command) + # + # async def stepper_run(self, motor_id, completion_callback=None): + # """ + # This method steps the selected motor based on the current speed. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run: A motion complete callback must be ' + # 'specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN, motor_id] + # await self._send_command(command) + # + # async def stepper_run_speed(self, motor_id): + # """ + # This method steps the selected motor based at a constant speed as set by the most + # recent call to stepper_set_max_speed(). The motor will run continuously. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run_speed: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_RUN_SPEED, motor_id] + # await self._send_command(command) + # + # async def stepper_set_max_speed(self, motor_id, max_speed): + # """ + # Sets the maximum permitted speed. The stepper_run() function will accelerate + # up to the speed set by this function. + # + # Caution: the maximum speed achievable depends on your processor and clock speed. + # The default maxSpeed is 1 step per second. + # + # Caution: Speeds that exceed the maximum speed supported by the processor may + # result in non-linear accelerations and decelerations. + # + # :param motor_id: 0 - 3 + # + # :param max_speed: 1 - 1000 + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Invalid motor_id.') + # + # if not 1 < max_speed <= 1000: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Speed range is 1 - 1000.') + # + # self.stepper_info_list[motor_id]['max_speed'] = max_speed + # max_speed_msb = (max_speed & 0xff00) >> 8 + # max_speed_lsb = max_speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MAX_SPEED, motor_id, max_speed_msb, + # max_speed_lsb] + # await self._send_command(command) + # + # async def stepper_get_max_speed(self, motor_id): + # """ + # Returns the maximum speed configured for this stepper + # that was previously set by stepper_set_max_speed() + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # :return: The currently configured maximum speed. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_max_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['max_speed'] + # + # async def stepper_set_acceleration(self, motor_id, acceleration): + # """ + # Sets the acceleration/deceleration rate. + # + # :param motor_id: 0 - 3 + # + # :param acceleration: The desired acceleration in steps per second + # per second. Must be > 0.0. This is an + # expensive call since it requires a square + # root to be calculated on the server. + # Dont call more often than needed. + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Invalid motor_id.') + # + # if not 1 < acceleration <= 1000: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Acceleration range is 1 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['acceleration'] = acceleration + # + # max_accel_msb = acceleration >> 8 + # max_accel_lsb = acceleration & 0xff + # + # command = [PrivateConstants.STEPPER_SET_ACCELERATION, motor_id, max_accel_msb, + # max_accel_lsb] + # await self._send_command(command) + # + # async def stepper_set_speed(self, motor_id, speed): + # """ + # Sets the desired constant speed for use with stepper_run_speed(). + # + # :param motor_id: 0 - 3 + # + # :param speed: 0 - 1000 The desired constant speed in steps per + # second. Positive is clockwise. Speeds of more than 1000 steps per + # second are unreliable. Speed accuracy depends on the Arduino + # crystal. Jitter depends on how frequently you call the + # stepper_run_speed() method. + # The speed will be limited by the current value of + # stepper_set_max_speed(). + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_speed: Invalid motor_id.') + # + # if not 0 < speed <= 1000: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_speed: Speed range is 0 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['speed'] = speed + # + # speed_msb = speed >> 8 + # speed_lsb = speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_SPEED, motor_id, speed_msb, speed_lsb] + # await self._send_command(command) + # + # async def stepper_get_speed(self, motor_id): + # """ + # Returns the most recently set speed. + # that was previously set by stepper_set_speed(); + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['speed'] + # + # async def stepper_get_distance_to_go(self, motor_id, distance_to_go_callback): + # """ + # Request the distance from the current position to the target position + # from the server. + # + # :param motor_id: 0 - 3 + # + # :param distance_to_go_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=15, motor_id, distance in steps, time_stamp] + # + # A positive distance is clockwise from the current position. + # + # """ + # if not distance_to_go_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go: Invalid motor_id.') + # self.stepper_info_list[motor_id][ + # 'distance_to_go_callback'] = distance_to_go_callback + # command = [PrivateConstants.STEPPER_GET_DISTANCE_TO_GO, motor_id] + # await self._send_command(command) + # + # async def stepper_get_target_position(self, motor_id, target_callback): + # """ + # Request the most recently set target position from the server. + # + # :param motor_id: 0 - 3 + # + # :param target_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=16, motor_id, target position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # + # """ + # if not target_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError( + # 'stepper_get_target_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_target_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id][ + # 'target_position_callback'] = target_callback + # + # command = [PrivateConstants.STEPPER_GET_TARGET_POSITION, motor_id] + # await self._send_command(command) + # + # async def stepper_get_current_position(self, motor_id, current_position_callback): + # """ + # Request the current motor position from the server. + # + # :param motor_id: 0 - 3 + # + # :param current_position_callback: required callback function to receive report + # + # :return: The current motor position returned via the callback as a list: + # + # [REPORT_TYPE=17, motor_id, current position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # """ + # if not current_position_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError( + # 'stepper_get_current_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_current_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['current_position_callback'] = current_position_callback + # + # command = [PrivateConstants.STEPPER_GET_CURRENT_POSITION, motor_id] + # await self._send_command(command) + # + # async def stepper_set_current_position(self, motor_id, position): + # """ + # Resets the current position of the motor, so that wherever the motor + # happens to be right now is considered to be the new 0 position. Useful + # for setting a zero position on a stepper after an initial hardware + # positioning move. + # + # Has the side effect of setting the current motor speed to 0. + # + # :param motor_id: 0 - 3 + # + # :param position: Position in steps. This is a 32 bit value + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_current_position: Invalid motor_id.') + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_SET_CURRENT_POSITION, motor_id] + # for value in position_bytes: + # command.append(value) + # await self._send_command(command) + # + # async def stepper_run_speed_to_position(self, motor_id, completion_callback=None): + # """ + # Runs the motor at the currently selected speed until the target position is + # reached. + # + # Does not implement accelerations. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: A motion complete ' + # 'callback must be ' + # 'specified.') + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN_SPEED_TO_POSITION, motor_id] + # await self._send_command(command) + # + # async def stepper_stop(self, motor_id): + # """ + # Sets a new target position that causes the stepper + # to stop as quickly as possible, using the current speed and + # acceleration parameters. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_stop: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_STOP, motor_id] + # await self._send_command(command) + # + # async def stepper_disable_outputs(self, motor_id): + # """ + # Disable motor pin outputs by setting them all LOW. + # + # Depending on the design of your electronics this may turn off + # the power to the motor coils, saving power. + # + # This is useful to support Arduino low power modes: disable the outputs + # during sleep and then re-enable with enableOutputs() before stepping + # again. + # + # If the enable Pin is defined, sets it to OUTPUT mode and clears + # the pin to disabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_disable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_DISABLE_OUTPUTS, motor_id] + # await self._send_command(command) + # + # async def stepper_enable_outputs(self, motor_id): + # """ + # Enable motor pin outputs by setting the motor pins to OUTPUT + # mode. + # + # If the enable Pin is defined, sets it to OUTPUT mode and sets + # the pin to enabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_enable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_ENABLE_OUTPUTS, motor_id] + # await self._send_command(command) + # + # async def stepper_set_min_pulse_width(self, motor_id, minimum_width): + # """ + # Sets the minimum pulse width allowed by the stepper driver. + # + # The minimum practical pulse width is approximately 20 microseconds. + # + # Times less than 20 microseconds will usually result in 20 microseconds or so. + # + # :param motor_id: 0 -3 + # + # :param minimum_width: A 16 bit unsigned value expressed in microseconds. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Invalid motor_id.') + # + # if not 0 < minimum_width <= 0xff: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Pulse width range = ' + # '0-0xffff.') + # + # width_msb = minimum_width >> 8 + # width_lsb = minimum_width & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MINIMUM_PULSE_WIDTH, motor_id, width_msb, + # width_lsb] + # await self._send_command(command) + # + # async def stepper_set_enable_pin(self, motor_id, pin=0xff): + # """ + # Sets the enable pin number for stepper drivers. + # 0xFF indicates unused (default). + # + # Otherwise, if a pin is set, the pin will be turned on when + # enableOutputs() is called and switched off when disableOutputs() + # is called. + # + # :param motor_id: 0 - 4 + # :param pin: 0-0xff + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Invalid motor_id.') + # + # if not 0 < pin <= 0xff: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Pulse width range = ' + # '0-0xff.') + # command = [PrivateConstants.STEPPER_SET_ENABLE_PIN, motor_id, pin] + # + # await self._send_command(command) + # + # async def stepper_set_3_pins_inverted(self, motor_id, direction=False, step=False, + # enable=False): + # """ + # Sets the inversion for stepper driver pins. + # + # :param motor_id: 0 - 3 + # + # :param direction: True=inverted or False + # + # :param step: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_3_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_3_PINS_INVERTED, motor_id, direction, + # step, enable] + # + # await self._send_command(command) + # + # async def stepper_set_4_pins_inverted(self, motor_id, pin1_invert=False, + # pin2_invert=False, + # pin3_invert=False, pin4_invert=False, enable=False): + # """ + # Sets the inversion for 2, 3 and 4 wire stepper pins + # + # :param motor_id: 0 - 3 + # + # :param pin1_invert: True=inverted or False + # + # :param pin2_invert: True=inverted or False + # + # :param pin3_invert: True=inverted or False + # + # :param pin4_invert: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_4_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_4_PINS_INVERTED, motor_id, pin1_invert, + # pin2_invert, pin3_invert, pin4_invert, enable] + # + # await self._send_command(command) + # + # async def stepper_is_running(self, motor_id, callback): + # """ + # Checks to see if the motor is currently running to a target. + # + # Callback return True if the speed is not zero or not at the target position. + # + # :param motor_id: 0-4 + # + # :param callback: required callback function to receive report + # + # :return: The current running state returned via the callback as a list: + # + # [REPORT_TYPE=18, motor_id, True or False for running state, time_stamp] + # """ + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError( + # 'stepper_is_running: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_is_running: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['is_running_callback'] = callback + # + # command = [PrivateConstants.STEPPER_IS_RUNNING, motor_id] + # await self._send_command(command) + + async def shutdown(self): + """ + This method attempts an orderly shutdown + If any exceptions are thrown, they are ignored. + + """ + self.shutdown_flag = True + + if self.hard_reset_on_shutdown: + await self.r4_hard_reset() + # stop all reporting - both analog and digital + else: + try: + command = [PrivateConstants.STOP_ALL_REPORTS] + await self._send_command(command) + + await asyncio.sleep(.5) + await self.serial_port.reset_input_buffer() + await self.serial_port.close() + if self.close_loop_on_shutdown: + self.loop.stop() + except (RuntimeError, SerialException): + pass + + async def r4_hard_reset(self): + """ + Place the r4 into hard reset + """ + command = [PrivateConstants.BOARD_HARD_RESET, 1] + await self._send_command(command) + + async def disable_all_reporting(self): + """ + Disable reporting for all digital and analog input pins + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DISABLE_ALL, 0] + await self._send_command(command) + + async def disable_analog_reporting(self, pin): + """ + Disables analog reporting for a single analog pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_DISABLE, pin] + await self._send_command(command) + + async def disable_digital_reporting(self, pin): + """ + Disables digital reporting for a single digital pin + + + :param pin: pin number + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_DISABLE, pin] + await self._send_command(command) + + async def enable_analog_reporting(self, pin): + """ + Enables analog reporting for the specified pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_ENABLE, pin] + await self._send_command(command) + + async def enable_digital_reporting(self, pin): + """ + Enable reporting on the specified digital pin. + + :param pin: Pin number. + """ + + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_ENABLE, pin] + await self._send_command(command) + + async def _arduino_report_dispatcher(self): + """ + This is a private method. + It continually accepts and interprets data coming from Telemetrix4Arduino,and then + dispatches the correct handler to process the data. + + It first receives the length of the packet, and then reads in the rest of the + packet. A packet consists of a length, report identifier and then the report data. + Using the report identifier, the report handler is fetched from report_dispatch. + + :returns: This method never returns + """ + + while True: + if self.shutdown_flag: + break + try: + packet_length = await self.serial_port.read() + except TypeError: + continue + + # get the rest of the packet + packet = await self.serial_port.read(packet_length) + + report = packet[0] + # print(report) + # handle all other messages by looking them up in the + # command dictionary + + await self.report_dispatch[report](packet[1:]) + await asyncio.sleep(self.sleep_tune) + + ''' + Report message handlers + ''' + + async def _report_loop_data(self, data): + """ + Print data that was looped back + + :param data: byte of loop back data + """ + if self.loop_back_callback: + await self.loop_back_callback(data) + + async def _spi_report(self, report): + + cb_list = [PrivateConstants.SPI_REPORT, report[0]] + report[1:] + + cb_list.append(time.time()) + + await self.spi_callback(cb_list) + + async def _onewire_report(self, report): + cb_list = [PrivateConstants.ONE_WIRE_REPORT, report[0]] + report[1:] + cb_list.append(time.time()) + await self.onewire_callback(cb_list) + + async def _report_debug_data(self, data): + """ + Print debug data sent from Arduino + + :param data: data[0] is a byte followed by 2 + bytes that comprise an integer + """ + value = (data[1] << 8) + data[2] + print(f'DEBUG ID: {data[0]} Value: {value}') + + async def _analog_message(self, data): + """ + This is a private message handler method. + It is a message handler for analog messages. + + :param data: message data + + """ + pin = data[0] + value = (data[1] << 8) + data[2] + + time_stamp = time.time() + + # append pin number, pin value, and pin type to return value and return as a list + message = [PrivateConstants.AT_ANALOG, pin, value, time_stamp] + + await self.analog_callbacks[pin](message) + + async def _dht_report(self, data): + """ + This is a private message handler for dht reports + + :param data: data[0] = report error return + No Errors = 0 + + Checksum Error = 1 + + Timeout Error = 2 + + Invalid Value = 999 + + data[1] = pin number + + data[2] = dht type 11 or 22 + + data[3] = humidity positivity flag + + data[4] = temperature positivity value + + data[5] = humidity integer + + data[6] = humidity fractional value + + data[7] = temperature integer + + data[8] = temperature fractional value + """ + if data[0]: # DHT_ERROR + # error report + # data[0] = report sub type, data[1] = pin, data[2] = error message + if self.dht_callbacks[data[1]]: + # Callback 0=DHT REPORT, DHT_ERROR, PIN, Time + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + time.time()] + await self.dht_callbacks[data[1]](message) + else: + # got valid data DHT_DATA + f_humidity = float(data[5] + data[6] / 100) + if data[3]: + f_humidity *= -1.0 + f_temperature = float(data[7] + data[8] / 100) + if data[4]: + f_temperature *= -1.0 + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + f_humidity, f_temperature, time.time()] + + await self.dht_callbacks[data[1]](message) + + async def _digital_message(self, data): + """ + This is a private message handler method. + It is a message handler for Digital Messages. + + :param data: digital message + + """ + pin = data[0] + value = data[1] + + time_stamp = time.time() + if self.digital_callbacks[pin]: + message = [PrivateConstants.DIGITAL_REPORT, pin, value, time_stamp] + await self.digital_callbacks[pin](message) + + async def _servo_unavailable(self, report): + """ + Message if no servos are available for use. + + :param report: pin number + """ + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'Servo Attach For Pin {report[0]} Failed: No Available Servos') + + async def _i2c_read_report(self, data): + """ + Execute callback for i2c reads. + + :param data: [I2C_READ_REPORT, i2c_port, number of bytes read, address, register, bytes read..., time-stamp] + """ + + # we receive [# data bytes, address, register, data bytes] + # number of bytes of data returned + + # data[0] = number of bytes + # data[1] = i2c_port + # data[2] = number of bytes returned + # data[3] = address + # data[4] = register + # data[5] ... all the data bytes + + cb_list = [PrivateConstants.I2C_READ_REPORT, data[0], data[1]] + data[2:] + cb_list.append(time.time()) + + if cb_list[1]: + await self.i2c_callback2(cb_list) + else: + await self.i2c_callback(cb_list) + + async def _i2c_too_few(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'i2c too few bytes received from i2c port {data[0]} i2c address {data[1]}') + + async def _i2c_too_many(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'i2c too many bytes received from i2c port {data[0]} i2c address {data[1]}') + + async def _sonar_distance_report(self, report): + """ + + :param report: data[0] = trigger pin, data[1] and data[2] = distance + + callback report format: [PrivateConstants.SONAR_DISTANCE, trigger_pin, distance_value, time_stamp] + """ + + # get callback from pin number + cb = self.sonar_callbacks[report[0]] + + # build report data + cb_list = [PrivateConstants.SONAR_DISTANCE, report[0], + ((report[1] << 8) + report[2]), time.time()] + + await cb(cb_list) + + async def _stepper_distance_to_go_report(self, report): + return # for now + # """ + # Report stepper distance to go. + # + # :param report: data[0] = motor_id, data[1] = steps MSB, data[2] = steps byte 1, + # data[3] = steps bytes 2, data[4] = steps LSB + # + # callback report format: [PrivateConstants.STEPPER_DISTANCE_TO_GO, motor_id + # steps, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['distance_to_go_callback'] + # + # # isolate the steps bytes and covert list to bytes + # steps = bytes(report[1:]) + # + # # get value from steps + # num_steps = int.from_bytes(steps, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_DISTANCE_TO_GO, report[0], num_steps, + # time.time()] + # + # await cb(cb_list) + # + + async def _stepper_target_position_report(self, report): + return # for now + + # """ + # Report stepper target position to go. + # + # :param report: data[0] = motor_id, data[1] = target position MSB, + # data[2] = target position byte MSB+1 + # data[3] = target position byte MSB+2 + # data[4] = target position LSB + # + # callback report format: [PrivateConstants.STEPPER_TARGET_POSITION, motor_id + # target_position, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['target_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # target = bytes(report[1:]) + # + # # get value from steps + # target_position = int.from_bytes(target, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_TARGET_POSITION, report[0], target_position, + # time.time()] + # + # await cb(cb_list) + # + async def _stepper_current_position_report(self, report): + return # for now + + # """ + # Report stepper current position. + # + # :param report: data[0] = motor_id, data[1] = current position MSB, + # data[2] = current position byte MSB+1 + # data[3] = current position byte MSB+2 + # data[4] = current position LSB + # + # callback report format: [PrivateConstants.STEPPER_CURRENT_POSITION, motor_id + # current_position, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['current_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # position = bytes(report[1:]) + # + # # get value from steps + # current_position = int.from_bytes(position, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_CURRENT_POSITION, report[0], current_position, + # time.time()] + # + # await cb(cb_list) + # + async def _stepper_is_running_report(self, report): + return # for now + + # """ + # Report if the motor is currently running + # + # :param report: data[0] = motor_id, True if motor is running or False if it is not. + # + # callback report format: [18, motor_id, + # running_state, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['is_running_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUNNING_REPORT, report[0], time.time()] + # + # await cb(cb_list) + # + async def _stepper_run_complete_report(self, report): + return # for now + + # """ + # The motor completed it motion + # + # :param report: data[0] = motor_id + # + # callback report format: [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, motor_id, + # time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['motion_complete_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, report[0], + # time.time()] + # + # await cb(cb_list) + + async def _features_report(self, report): + self.reported_features = report[0] + + async def _send_command(self, command): + """ + This is a private utility method. + + + :param command: command data in the form of a list + + :returns: number of bytes sent + """ + # the length of the list is added at the head + command.insert(0, len(command)) + # print(command) + send_message = bytes(command) + + await self.serial_port.write(send_message) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_analog_input.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_analog_input.py new file mode 100644 index 0000000..479c50f --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_analog_input.py @@ -0,0 +1,100 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +This file demonstrates analog input using both callbacks and +polling. Time stamps are provided in both "cooked" and raw form +""" + +# Set up a pin for analog input and monitor its changes +ANALOG_PIN = 2 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +async def the_callback(data): + """ + A callback function to report data changes. + + :param data: [pin_mode, pin, current_reported_value, timestamp] + """ + + formatted_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Analog Call Input Callback: pin={data[CB_PIN]}, ' + f'Value={data[CB_VALUE]} Time={formatted_time} ' + f'(Raw Time={data[CB_TIME]})') + + +async def analog_in(my_board, pin): + """ + This function establishes the pin as an + analog input. Any changes on this pin will + be reported through the call back function. + + Every 5 seconds the last value and time stamp is polled + and printed. + + Also, the differential parameter is being used. + The callback will only be called when there is + difference of 5 or more between the current and + last value reported. + + :param my_board: a telemetrix_aio instance + + :param pin: Arduino pin number + """ + await my_board.set_pin_mode_analog_input(pin, 5, the_callback) + + # await asyncio.sleep(5) + # await my_board.disable_analog_reporting() + # await asyncio.sleep(5) + # await my_board.enable_analog_reporting() + + # run forever waiting for input changes + try: + while True: + await asyncio.sleep(.001) + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(analog_in(board, ANALOG_PIN)) + loop.run_until_complete(board.shutdown()) +except (KeyboardInterrupt, RuntimeError) as e: + loop.run_until_complete(board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_blink.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_blink.py new file mode 100644 index 0000000..fc954df --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_blink.py @@ -0,0 +1,67 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +import asyncio +import sys + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +Setup a pin for digital output and output a signal +and toggle the pin. Do this 4 times. +""" + +# some globals +DIGITAL_PIN = 13 # arduino pin number + + +async def blink(my_board, pin): + """ + This function will to toggle a digital pin. + + :param my_board: a telemetrix_aio instance + :param pin: pin to be controlled + """ + + # set the pin mode + await my_board.set_pin_mode_digital_output(pin) + + # toggle the pin 4 times and exit + for x in range(4): + print('ON') + await my_board.digital_write(pin, 1) + await asyncio.sleep(1) + print('OFF') + await my_board.digital_write(pin, 0) + await asyncio.sleep(1) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(blink(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_dht.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_dht.py new file mode 100644 index 0000000..5420c24 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_dht.py @@ -0,0 +1,108 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +This program monitors two DHT22 and two DHT11 sensors. +""" + + +# indices into callback data for valid data +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# HUMIDITY = 4 +# TEMPERATURE = 5 +# TIME = 6 + +# indices into callback data for error report +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# TIME = 4 + +# Arduino Pin Number +DHT_PIN = 8 + + +# A callback function to display the distance +# noinspection GrazieInspection +async def the_callback(data): + # noinspection GrazieInspection + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.DHT, error = 0, pin number, + dht_type, humidity, temperature timestamp] + if this is an error report: + [report_type = PrivateConstants.DHT, error != 0, pin number, dht_type + timestamp] + """ + if data[1]: + # error message + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[4])) + print(f'DHT Error Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Error: {data[1]} Time: {date}') + else: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[6])) + print(f'DHT Valid Data Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Humidity: {data[4]} Temperature:' + f' {data[5]} Time: {date}') + + +async def dht(my_board): + # noinspection GrazieInspection + """ + Set the pin mode for a DHT 22 device. Results will appear via the + callback. + + :param my_board: a telemetrix instance + + """ + + # set the pin mode for a DHT 22 + await my_board.set_pin_mode_dht(DHT_PIN, callback=the_callback, dht_type=11) + + # just sit in a loop waiting for the reports to come in + while True: + try: + await asyncio.sleep(.1) + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() +try: + loop.run_until_complete(dht(board)) +except (KeyboardInterrupt, RuntimeError): + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_digital_input.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_digital_input.py new file mode 100644 index 0000000..5f982e7 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_digital_input.py @@ -0,0 +1,115 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +Monitor a digital input pin +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + +# variable to hold the last time a button state changed +debounce_time = time.time() + + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin_mode, pin, current reported value, timestamp] + """ + global debounce_time + + # if the time from the last event change is > .2 seconds, the input is debounced + if data[CB_TIME] - debounce_time > .2: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + debounce_time = data[CB_TIME] + + +async def digital_in(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # set the pin mode + await my_board.set_pin_mode_digital_input(pin, the_callback) + + # uncomment to try out report enable/disable + # await asyncio.sleep(1) + # await my_board.disable_all_reporting() + # await asyncio.sleep(4) + # await my_board.enable_digital_reporting(12) + + # await asyncio.sleep(3) + # await my_board.enable_digital_reporting(pin) + # await asyncio.sleep(1) + + while True: + try: + await asyncio.sleep(.001) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(digital_in(board, 12)) + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) +except (KeyboardInterrupt, RuntimeError) as e: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_digital_input_pullup.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_digital_input_pullup.py new file mode 100644 index 0000000..c05857d --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_digital_input_pullup.py @@ -0,0 +1,91 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +Setup a digital pin for input pullup and monitor its changes. +""" + +# some globals +DIGITAL_PIN = 12 # arduino pin number +KILL_TIME = 5 # sleep time to keep forever loop open + +# Callback data indices +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +# Set up a pin for digital pin input and monitor its changes + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +async def digital_in_pullup(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # start monitoring the pin by setting its mode + await my_board.set_pin_mode_digital_input_pullup(pin, the_callback) + + # get pin changes forever + while True: + try: + await asyncio.sleep(KILL_TIME) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(digital_in_pullup(board, 12)) +except (KeyboardInterrupt, RuntimeError) as e: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_fade.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_fade.py new file mode 100644 index 0000000..944f024 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_fade.py @@ -0,0 +1,70 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import asyncio + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +Setup a pin for output and fade its intensity +""" + +# some globals +# make sure to select a PWM pin +DIGITAL_PIN = 13 + + +async def fade(the_board, pin): + # Set the DIGITAL_PIN as an output pin + await the_board.set_pin_mode_analog_output(pin) + + # When hitting control-c to end the program + # in this loop, we are likely to get a KeyboardInterrupt + # exception. Catch the exception and exit gracefully. + + try: + print('Fading up...') + for i in range(255): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.005) + print('Fading down...') + for i in range(255, -1, -1): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.005) + except KeyboardInterrupt: + the_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(fade(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_hc-sr04_distance_sensor.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_hc-sr04_distance_sensor.py new file mode 100644 index 0000000..8a9fed9 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_hc-sr04_distance_sensor.py @@ -0,0 +1,84 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(.1) + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_i2c_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_i2c_adxl345_accelerometer.py new file mode 100644 index 0000000..aa8a08e --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_i2c_adxl345_accelerometer.py @@ -0,0 +1,104 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +This example sets up and control an ADXL345 i2c accelerometer. +It will continuously print data the raw xyz data from the device. +""" + + +# the call back function to print the adxl345 data +async def the_callback(data): + """ + + :param data: [pin_type, Device address, device read register, x data pair, y data pair, z data pair] + :return: + """ + report_type = data[0] + number_bytes_read = data[2] + i2c_device_address = data[3] + i2c_register = data[4] + x_msb = data[5] + x_lsb = data[6] + y_msb = data[7] + y_lsb = data[8] + z_msb = data[9] + z_lsb = data[10] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 10: + print(f'i2c Report: i2c device address: {i2c_device_address} i2c ' + f'Register: ' + f'{i2c_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + # setup adxl345 + # device address = 83 + await my_board.set_pin_mode_i2c() + + # set up power and control register + await my_board.i2c_write(83, [45, 0]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [45, 8]) + await asyncio.sleep(.1) + + # set up the data format register + await my_board.i2c_write(83, [49, 8]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [49, 3]) + await asyncio.sleep(.1) + + # read_count = 20 + while True: + # read 6 bytes from the data register + try: + await my_board.i2c_read(83, 50, 6, the_callback) + await asyncio.sleep(.1) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_loop_back.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_loop_back.py new file mode 100644 index 0000000..34fbb53 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_loop_back.py @@ -0,0 +1,73 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +Loopback some data to assure that data can be sent and received between +the Telemetrix client and arduino-telemetrix server. +""" + + +async def the_callback(data): + """ + A callback function to report receive the looped back data + + :param data: [looped back data] + """ + print(f'Looped back: {chr(data[0])}') + + +async def loop_back(my_board, loop_back_data): + """ + This function will request that the supplied characters be + sent to the board and looped back and printed out to the console. + + :param my_board: a telemetrix_aio instance + :param loop_back_data: A list of characters to have looped back + """ + try: + for data in loop_back_data: + await my_board.loop_back(data, callback=the_callback) + print(f'Sending: {data}') + await asyncio.sleep(.1) + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() +char_list = ['A', 'B', 'Z'] +try: + # start the main function + loop.run_until_complete(loop_back(board, char_list)) + try: + loop.run_until_complete(board.shutdown()) + except: + pass +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_servo.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_servo.py new file mode 100644 index 0000000..689b851 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_servo.py @@ -0,0 +1,66 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +This example will set a servo to 0, 90 and 180 degree +positions. +""" + + +async def servo(my_board, pin): + """ + Set a pin to servo mode and then adjust + its position. + + :param my_board: telemetrix_aio instance + :param pin: pin to be controlled + """ + + # set the pin mode + await my_board.set_pin_mode_servo(pin) + + await asyncio.sleep(1) + + await my_board.servo_write(pin, 0) + await asyncio.sleep(1) + await my_board.servo_write(pin, 90) + await asyncio.sleep(1) + await my_board.servo_write(pin, 180) + await my_board.servo_detach(pin) + +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() +try: + loop.run_until_complete(servo(board, 5)) + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_sonar_disable.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_sonar_disable.py new file mode 100644 index 0000000..3dd426c --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_sonar_disable.py @@ -0,0 +1,85 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(5) + await my_board.sonar_disable() + await asyncio.sleep(5) + await my_board.sonar_enable() + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) +except KeyboardInterrupt: + loop.run_until_complete(board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_spi_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_spi_adxl345_accelerometer.py new file mode 100644 index 0000000..593c785 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/asyncio/msa_spi_adxl345_accelerometer.py @@ -0,0 +1,123 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima_aio import telemetrix_uno_r4_minima_aio + + +""" +This program reads x, y, and z registers of an ADXL345 using 4 Wire SPI interface +""" + +""" +Connection scheme: +CS = pin 10 +SDA = pin 11 +SDO = pin 12 +SCL = pin 13 + +""" + + +async def the_callback(data): + """ + :param data: [pin_type, chip select pin, device read register, x data pair, + y data pair, z data pair, time stamp] + + """ + report_type = data[0] + chip_select_pin = data[1] + device_read_register = data[2] & 0x3f # strip off read command bits + number_bytes_read = data[3] + x_msb = data[4] + x_lsb = data[5] + y_msb = data[6] + y_lsb = data[7] + z_msb = data[8] + z_lsb = data[9] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 13: + print(f'SPI Report: CS Pin: {chip_select_pin} SPI Register: ' + f'{device_read_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + """ + + :type my_board: object + """ + # initialize spi mode for chipselect on pin 10 + await my_board.set_pin_mode_spi([10]) + await asyncio.sleep(.3) + + # set the SPI format + # spi speed is FPU frequency divided by 4 + # data order is MSB + # mode is MODE3 + await my_board.spi_set_format(8, 1, 3) + await asyncio.sleep(.3) + + # set up power and control register + await my_board.spi_write_blocking(10, [45, 0]) + await asyncio.sleep(.3) + + await my_board.spi_write_blocking(10, [45, 8]) + await asyncio.sleep(.3) + + # set up data format register for 4 wire spi + await my_board.spi_write_blocking(10, [49, 0]) + await asyncio.sleep(.3) + + # read 6 bytes from the data register + # for a multibyte read, we need to OR in a 0x40 into the register value + while True: + # read 6 bytes from the data register + try: + await my_board.spi_read_blocking(10, 50 | 0x40, 6, the_callback) + + await asyncio.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima_aio.TelemetrixUnoR4MinimaAio() + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_analog_input.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_analog_input.py new file mode 100644 index 0000000..bc5bacb --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_analog_input.py @@ -0,0 +1,89 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima +""" +Monitor an analog input pin +""" + +""" +Setup a pin for analog input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +ANALOG_PIN = 2 # arduino pin number (A2) + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin Mode: {data[CB_PIN_MODE]} Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +def analog_in(my_board, pin): + """ + This function establishes the pin as an + analog input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_analog_input(pin, differential=5, callback=the_callback) + + # time.sleep(5) + # my_board.disable_analog_reporting() + # time.sleep(5) + # my_board.enable_analog_reporting() + + print('Enter Control-C to quit.') + try: + while True: + try: + time.sleep(1) + except KeyboardInterrupt: + sys.exit(0) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +try: + analog_in(board, ANALOG_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_blink.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_blink.py new file mode 100644 index 0000000..5b5c33d --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_blink.py @@ -0,0 +1,53 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + """ + +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima +""" +Setup a pin for digital output +and toggle the pin 5 times. +""" + +# some globals +DIGITAL_PIN = 13 # the board LED + +# Create a Telemetrix instance. +# board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() + +# Set the DIGITAL_PIN as an output pin +board.set_pin_mode_digital_output(DIGITAL_PIN) + +# Blink the LED and provide feedback as +# to the LED state on the console. +for blink in range(5): + # When hitting control-c to end the program + # in this loop, we are likely to get a KeyboardInterrupt + # exception. Catch the exception and exit gracefully. + try: + print('1') + board.digital_write(DIGITAL_PIN, 1) + time.sleep(1) + print('0') + board.digital_write(DIGITAL_PIN, 0) + time.sleep(1) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_dht.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_dht.py new file mode 100644 index 0000000..8271794 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_dht.py @@ -0,0 +1,100 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +This program monitors a DHT22 sensor. +""" + +# Arduino pin number +DHT_PIN = 8 + +# indices into callback data for valid data +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# HUMIDITY = 4 +# TEMPERATURE = 5 +# TIME = 6 + +# indices into callback data for error report +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# TIME = 4 + + +# A callback function to display the distance +# noinspection GrazieInspection +def the_callback(data): + # noinspection GrazieInspection + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.DHT, error = 0, pin number, + dht_type, humidity, temperature timestamp] + if this is an error report: + [report_type = PrivateConstants.DHT, error != 0, pin number, dht_type + timestamp] + """ + if data[1]: + # error message + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[4])) + print(f'DHT Error Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Error: {data[1]} Time: {date}') + else: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[6])) + print(f'DHT Valid Data Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Humidity: {data[4]} Temperature:' + f' {data[5]} Time: {date}') + + +def dht(my_board, pin, callback, dht_type): + # noinspection GrazieInspection + """ + Set the pin mode for a DHT 22 device. Results will appear via the + callback. + + :param my_board: an telemetrix instance + :param pin: Arduino pin number + :param callback: The callback function + :param dht_type: 22 or 11 + """ + + # set the pin mode for the DHT device + my_board.set_pin_mode_dht(pin, callback, dht_type) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +try: + dht(board, DHT_PIN, callback=the_callback, dht_type=11) + + # wait forever + while True: + try: + time.sleep(.01) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_digital_input.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_digital_input.py new file mode 100644 index 0000000..03234f4 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_digital_input.py @@ -0,0 +1,99 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima +""" +Monitor a digital input pin +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + +# variable to hold the last time a button state changed +debounce_time = time.time() + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + global debounce_time + + # if the time from the last event change is > .2 seconds, the input is debounced + if data[CB_TIME] - debounce_time > .3: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + debounce_time = data[CB_TIME] + + +def digital_in(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_digital_input(pin, the_callback) + # time.sleep(1) + # my_board.disable_all_reporting() + # time.sleep(4) + # my_board.enable_digital_reporting(12) + + # time.sleep(3) + # my_board.enable_digital_reporting(pin) + # time.sleep(1) + + print('Enter Control-C to quit.') + # my_board.enable_digital_reporting(12) + try: + while True: + time.sleep(.0001) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +try: + digital_in(board, DIGITAL_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_digital_input_pullup.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_digital_input_pullup.py new file mode 100644 index 0000000..076ac3f --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_digital_input_pullup.py @@ -0,0 +1,93 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +Monitor a digital input pin with pullup enabled +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin Mode: {data[CB_PIN_MODE]} Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +def digital_in_pullup(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_digital_input_pullup(pin, the_callback) + # time.sleep(1) + # my_board.disable_all_reporting() + # time.sleep(4) + # my_board.enable_digital_reporting(12) + + # time.sleep(3) + # my_board.enable_digital_reporting(pin) + # time.sleep(1) + + print('Enter Control-C to quit.') + # my_board.enable_digital_reporting(12) + try: + while True: + time.sleep(.0001) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() + +try: + digital_in_pullup(board, DIGITAL_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_fade.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_fade.py new file mode 100644 index 0000000..345d095 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_fade.py @@ -0,0 +1,59 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +Setup a pin for output and fade its intensity +""" + +# some globals +# make sure to select a PWM pin +DIGITAL_PIN = 13 + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() + +# Set the DIGITAL_PIN as an output pin +board.set_pin_mode_analog_output(DIGITAL_PIN) +# board.set_pin_mode_analog_output(DIGITAL_PIN) + + +# When hitting control-c to end the program +# in this loop, we are likely to get a KeyboardInterrupt +# exception. Catch the exception and exit gracefully. + +try: + print('Fading up...') + for i in range(255): + board.analog_write(DIGITAL_PIN, i) + time.sleep(.005) + print('Fading down...') + for i in range(255, -1, -1): + board.analog_write(DIGITAL_PIN, i) + time.sleep(.005) + + board.set_pin_mode_digital_output(DIGITAL_PIN) + +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_hc-sr04_distance_sensor.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_hc-sr04_distance_sensor.py new file mode 100644 index 0000000..ac1fb7a --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_hc-sr04_distance_sensor.py @@ -0,0 +1,75 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + time.sleep(.01) + except KeyboardInterrupt: + my_board.shutdown() + time.sleep(1) + sys.exit(0) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +try: + sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback) + board.shutdown() +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_i2c_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_i2c_adxl345_accelerometer.py new file mode 100644 index 0000000..5e11fee --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_i2c_adxl345_accelerometer.py @@ -0,0 +1,98 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +This example sets up and control an ADXL345 i2c accelerometer. +It will continuously print data the raw xyz data from the device. +""" + + +# the call back function to print the adxl345 data +def the_callback(data): + """ + + :param data: [pin_type, Device address, device read register, x data pair, y data pair, z data pair] + :return: + """ + report_type = data[0] + number_bytes_read = data[2] + i2c_device_address = data[3] + i2c_register = data[4] + x_msb = data[5] + x_lsb = data[6] + y_msb = data[7] + y_lsb = data[8] + z_msb = data[9] + z_lsb = data[10] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 10: + print(f'i2c Report: i2c device address: {i2c_device_address } i2c ' + f'Register: ' + f'{i2c_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +def adxl345(my_board): + # setup adxl345 + # device address = 83 + my_board.set_pin_mode_i2c() + + # set up power and control register + my_board.i2c_write(83, [45, 0]) + time.sleep(.1) + my_board.i2c_write(83, [45, 8]) + time.sleep(.1) + + # set up the data format register + my_board.i2c_write(83, [49, 8]) + time.sleep(.1) + my_board.i2c_write(83, [49, 3]) + time.sleep(.1) + + # read_count = 20 + while True: + # read 6 bytes from the data register + try: + my_board.i2c_read(83, 50, 6, the_callback) + time.sleep(.1) + + except (KeyboardInterrupt, RuntimeError): + my_board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() + +try: + adxl345(board) +except KeyboardInterrupt: + try: + board.shutdown() + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_loop_back.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_loop_back.py new file mode 100644 index 0000000..41f398c --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_loop_back.py @@ -0,0 +1,61 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import time +import sys +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +Loopback some data to assure that data can be sent and received between +the Telemetrix client and arduino-telemetrix server. +""" + + +def the_callback(data): + """ + A callback function to report receive the looped back data + + :param data: [looped back data] + """ + print(f'Looped back: {chr(data[0])}') + + +def loop_back(my_board, loop_back_data): + """ + This function will request that the supplied characters be + sent to the board and looped back and printed out to the console. + + :param my_board: a telemetrix instance + :param loop_back_data: A list of characters to have looped back + """ + try: + for data in loop_back_data: + my_board.loop_back(data, callback=the_callback) + print(f'Sending: {data}') + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +char_list = ['A', 'B', 'Z'] +try: + loop_back(board, char_list) + time.sleep(1) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_servo.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_servo.py new file mode 100644 index 0000000..0fc1251 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_servo.py @@ -0,0 +1,49 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import sys +import time + +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +Attach a pin to a servo and move it about. +""" + +# some globals +SERVO_PIN = 5 + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +try: + board.set_pin_mode_servo(SERVO_PIN, 100, 3000) + time.sleep(.2) + board.servo_write(SERVO_PIN, 90) + time.sleep(1) + board.servo_write(SERVO_PIN, 0) + time.sleep(1) + board.servo_write(SERVO_PIN, 180) + time.sleep(1) + board.servo_write(SERVO_PIN, 90) + time.sleep(1) + + board.servo_detach(SERVO_PIN) + time.sleep(.2) + board.shutdown() +except KeyboardInterrupt: + board.shutdown() diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_sonar_disable.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_sonar_disable.py new file mode 100644 index 0000000..bcca169 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_sonar_disable.py @@ -0,0 +1,81 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + time.sleep(5) + my_board.sonar_disable() + time.sleep(5) + my_board.sonar_enable() + + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +try: + sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback) + board.shutdown() +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_minima_examples/threaded/mst_spi_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_spi_adxl345_accelerometer.py new file mode 100644 index 0000000..10123b9 --- /dev/null +++ b/telemetrix_uno_r4/r4_minima_examples/threaded/mst_spi_adxl345_accelerometer.py @@ -0,0 +1,115 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.minima.telemetrix_uno_r4_minima import telemetrix_uno_r4_minima + +""" +This program reads x, y, and z registers of an ADXL345 using 4 Wire SPI interface +""" + +""" +Connection scheme: +CS = pin 10 +SDA = pin 11 +SDO = pin 12 +SCL = pin 13 + +""" + + +def the_callback(data): + """ + :param data: [pin_type, chip select pin, device read register, x data pair, + y data pair, z data pair, time stamp] + + """ + report_type = data[0] + chip_select_pin = data[1] + device_read_register = data[2] & 0x3f # strip off read command bits + number_bytes_read = data[3] + x_msb = data[4] + x_lsb = data[5] + y_msb = data[6] + y_lsb = data[7] + z_msb = data[8] + z_lsb = data[9] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 13: + print(f'SPI Report: CS Pin: {chip_select_pin} SPI Register: ' + f'{device_read_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +def adxl345(my_board): + """ + + :type my_board: object + """ + # initialize spi mode for chipselect on pin 10 + my_board.set_pin_mode_spi([10]) + time.sleep(.3) + + # set the SPI format + # spi speed is FPU frequency divided by 4 + # data order is MSB + # mode is MODE3 + my_board.spi_set_format(8, 1, 3) + time.sleep(.3) + + # set up power and control register + my_board.spi_write_blocking(10, [45, 0]) + time.sleep(.3) + + my_board.spi_write_blocking(10, [45, 8]) + time.sleep(.3) + + # set up data format register for 4 wire spi + my_board.spi_write_blocking(10, [49, 0]) + time.sleep(.3) + + # read 6 bytes from the data register + # for a multibyte read, we need to OR in a 0x40 into the register value + while True: + # read 6 bytes from the data register + try: + my_board.spi_read_blocking(10, 50 | 0x40, 6, the_callback) + + time.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + my_board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_minima.TelemetrixUnoR4Minima() +try: + adxl345(board) +except KeyboardInterrupt: + try: + board.shutdown() + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_analog_input.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_analog_input.py new file mode 100644 index 0000000..667de88 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_analog_input.py @@ -0,0 +1,102 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This file demonstrates analog input using both callbacks and +polling. Time stamps are provided in both "cooked" and raw form +""" + +# Set up a pin for analog input and monitor its changes +ANALOG_PIN = 2 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +async def the_callback(data): + """ + A callback function to report data changes. + + :param data: [pin_mode, pin, current_reported_value, timestamp] + """ + + formatted_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Analog Call Input Callback: pin={data[CB_PIN]}, ' + f'Value={data[CB_VALUE]} Time={formatted_time} ' + f'(Raw Time={data[CB_TIME]})') + + +async def analog_in(my_board, pin): + """ + This function establishes the pin as an + analog input. Any changes on this pin will + be reported through the call back function. + + Every 5 seconds the last value and time stamp is polled + and printed. + + Also, the differential parameter is being used. + The callback will only be called when there is + difference of 5 or more between the current and + last value reported. + + :param my_board: a telemetrix_aio instance + + :param pin: Arduino pin number + """ + await my_board.start_aio() + await my_board.set_pin_mode_analog_input(pin, 5, the_callback) + + # run forever waiting for input changes + try: + while True: + await asyncio.sleep(.001) + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + +# instantiate telemetrix_aio +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(analog_in(board, ANALOG_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + + + diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_blink.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_blink.py new file mode 100644 index 0000000..80a7bad --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_blink.py @@ -0,0 +1,71 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +import asyncio +import sys + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio +s +""" +Setup a pin for digital output and output a signal +and toggle the pin. Do this 4 times. +""" + +# some globals +DIGITAL_PIN = 13 # arduino pin number + + +async def blink(pin, my_board): + """ + This function will to toggle a digital pin. + + :param pin: pin to be controlled + + :param my_board: telemetrix instance + """ + + # set the pin mode + + await my_board.start_aio() + await my_board.set_pin_mode_digital_output(pin) + + # toggle the pin 4 times and exit + for x in range(4): + print('ON') + await my_board.digital_write(pin, 1) + await asyncio.sleep(1) + print('OFF') + await my_board.digital_write(pin, 0) + await asyncio.sleep(1) + + await my_board.shutdown() + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) +try: + # start the main function + loop.run_until_complete(blink(DIGITAL_PIN, board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_dht.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_dht.py new file mode 100644 index 0000000..0bc57b4 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_dht.py @@ -0,0 +1,114 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program monitors two DHT22 and two DHT11 sensors. +""" + + +# indices into callback data for valid data +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# HUMIDITY = 4 +# TEMPERATURE = 5 +# TIME = 6 + +# indices into callback data for error report +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# TIME = 4 + +# Arduino Pin Number +DHT_PIN = 8 + + +# A callback function to display the distance +# noinspection GrazieInspection +async def the_callback(data): + # noinspection GrazieInspection + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.DHT, error = 0, pin number, + dht_type, humidity, temperature timestamp] + if this is an error report: + [report_type = PrivateConstants.DHT, error != 0, pin number, dht_type + timestamp] + """ + if data[1]: + # error message + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[4])) + print(f'DHT Error Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Error: {data[1]} Time: {date}') + else: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[6])) + print(f'DHT Valid Data Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Humidity: {data[4]} Temperature:' + f' {data[5]} Time: {date}') + + +async def dht(my_board, pin): + # noinspection GrazieInspection + """ + Set the pin mode for a DHT 22 device. Results will appear via the + callback. + + :param my_board: a telemetrix instance + + :param pin: DHT data pin + + """ + await my_board.start_aio() + # set the pin mode for a DHT 11 or 22 + await my_board.set_pin_mode_dht(pin, the_callback, dht_type=11) + + # just sit in a loop waiting for the reports to come in + while True: + try: + await asyncio.sleep(.001) + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + +# instantiate telemetrix_aio +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(dht(board, DHT_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_digital_input.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_digital_input.py new file mode 100644 index 0000000..5cdd513 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_digital_input.py @@ -0,0 +1,106 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Monitor a digital input pin +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin_mode, pin, current reported value, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +async def digital_in(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # set the pin mode + await my_board.start_aio() + await my_board.set_pin_mode_digital_input(pin, the_callback) + + # uncomment to try out report enable/disable + # await asyncio.sleep(1) + # await my_board.disable_all_reporting() + # await asyncio.sleep(4) + # await my_board.enable_digital_reporting(12) + + # await asyncio.sleep(3) + # await my_board.enable_digital_reporting(pin) + # await asyncio.sleep(1) + + while True: + try: + await asyncio.sleep(.001) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) +# instantiate telemetrix_aio +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(digital_in(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + + diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_digital_input_pullup.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_digital_input_pullup.py new file mode 100644 index 0000000..c15e4d1 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_digital_input_pullup.py @@ -0,0 +1,95 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a digital pin for input pullup and monitor its changes. +""" + +# some globals +DIGITAL_PIN = 12 # arduino pin number +KILL_TIME = 5 # sleep time to keep forever loop open + +# Callback data indices +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +# Set up a pin for digital pin input and monitor its changes + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +async def digital_in_pullup(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # start monitoring the pin by setting its mode + await my_board.start_aio() + await my_board.set_pin_mode_digital_input_pullup(pin, the_callback) + + # get pin changes forever + while True: + try: + await asyncio.sleep(KILL_TIME) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) + + +# instantiate telemetrix_aio +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(digital_in_pullup(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_fade.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_fade.py new file mode 100644 index 0000000..548ec9f --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_fade.py @@ -0,0 +1,74 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time +import asyncio + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio +""" +Setup a pin for output and fade its intensity +""" + +# some globals +# make sure to select a PWM pin +DIGITAL_PIN = 13 + + +async def fade(the_board, pin): + await the_board.start_aio() + # Set the DIGITAL_PIN as an output pin + await the_board.set_pin_mode_analog_output(pin) + + # When hitting control-c to end the program + # in this loop, we are likely to get a KeyboardInterrupt + # exception. Catch the exception and exit gracefully. + + try: + print('Fading up...') + for i in range(255): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.009) + print('Fading down...') + for i in range(255, -1, -2): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.009) + await asyncio.sleep(4) + await the_board.shutdown() + except KeyboardInterrupt: + await the_board.shutdown() + except Exception as e: + await the_board.shutdown() + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) +try: + # start the main function + loop.run_until_complete(fade(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass \ No newline at end of file diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_hc-sr04_distance_sensor.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_hc-sr04_distance_sensor.py new file mode 100644 index 0000000..1cb4782 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_hc-sr04_distance_sensor.py @@ -0,0 +1,86 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + await my_board.start_aio() + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(.1) + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) + +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_i2c_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_i2c_adxl345_accelerometer.py new file mode 100644 index 0000000..f53ccbf --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_i2c_adxl345_accelerometer.py @@ -0,0 +1,104 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This example sets up and control an ADXL345 i2c accelerometer. +It will continuously print data the raw xyz data from the device. +""" + + +# the call back function to print the adxl345 data +async def the_callback(data): + """ + + :param data: [pin_type, Device address, device read register, x data pair, y data pair, z data pair] + :return: + """ + report_type = data[0] + number_bytes_read = data[2] + i2c_device_address = data[3] + i2c_register = data[4] + x_msb = data[5] + x_lsb = data[6] + y_msb = data[7] + y_lsb = data[8] + z_msb = data[9] + z_lsb = data[10] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 10: + print(f'i2c Report: i2c device address: {i2c_device_address} i2c ' + f'Register: ' + f'{i2c_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + await my_board.start_aio() + # setup adxl345 + # device address = 83 + await my_board.set_pin_mode_i2c() + + # set up power and control register + await my_board.i2c_write(83, [45, 0]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [45, 8]) + await asyncio.sleep(.1) + + # set up the data format register + await my_board.i2c_write(83, [49, 8]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [49, 3]) + await asyncio.sleep(.1) + + # read_count = 20 + while True: + # read 6 bytes from the data register + try: + await my_board.i2c_read(83, 50, 6, the_callback) + await asyncio.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_loop_back.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_loop_back.py new file mode 100644 index 0000000..6c1a047 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_loop_back.py @@ -0,0 +1,74 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio +""" +Loopback some data to assure that data can be sent and received between +the Telemetrix client and arduino-telemetrix server. +""" + + +async def the_callback(data): + """ + A callback function to report receive the looped back data + + :param data: [looped back data] + """ + print(f'Looped back: {chr(data[0])}') + + +async def loop_back(my_board, loop_back_data): + """ + This function will request that the supplied characters be + sent to the board and looped back and printed out to the console. + + :param my_board: a telemetrix_aio instance + :param loop_back_data: A list of characters to have looped back + """ + await my_board.start_aio() + try: + for data in loop_back_data: + await my_board.loop_back(data, callback=the_callback) + print(f'Sending: {data}') + await asyncio.sleep(.3) + await my_board.shutdown() + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + +# instantiate telemetrix_aio +char_list = ['A', 'B', 'Z'] + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(loop_back(board, char_list)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_scroll_message.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_scroll_message.py new file mode 100644 index 0000000..dcb5ccb --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_scroll_message.py @@ -0,0 +1,55 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import asyncio + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Scroll the message across the LED screen +""" + + +async def test_scroll(the_message): + await board.start_aio() + await board.enable_scroll_message(the_message) + await asyncio.sleep(5) + await board.disable_scroll_message() + await board.shutdown() + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(test_scroll("Hello World")) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + + diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_sonar_disable.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_sonar_disable.py new file mode 100644 index 0000000..fcfd69a --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_sonar_disable.py @@ -0,0 +1,90 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + await my_board.start_aio() + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(5) + await my_board.sonar_disable() + await asyncio.sleep(5) + await my_board.sonar_enable() + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) + +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) \ No newline at end of file diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_spi_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_spi_adxl345_accelerometer.py new file mode 100644 index 0000000..97e1c7b --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/BLE/wba_spi_adxl345_accelerometer.py @@ -0,0 +1,126 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + + +""" +This program reads x, y, and z registers of an ADXL345 using 4 Wire SPI interface +""" + +""" +Connection scheme: +CS = pin 10 +SDA = pin 11 +SDO = pin 12 +SCL = pin 13 + +""" + + +async def the_callback(data): + """ + :param data: [pin_type, chip select pin, device read register, x data pair, + y data pair, z data pair, time stamp] + + """ + report_type = data[0] + chip_select_pin = data[1] + device_read_register = data[2] & 0x3f # strip off read command bits + number_bytes_read = data[3] + x_msb = data[4] + x_lsb = data[5] + y_msb = data[6] + y_lsb = data[7] + z_msb = data[8] + z_lsb = data[9] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 13: + print(f'SPI Report: CS Pin: {chip_select_pin} SPI Register: ' + f'{device_read_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + """ + + :type my_board: object + """ + await my_board.start_aio() + # initialize spi mode for chipselect on pin 10 + await my_board.set_pin_mode_spi([10]) + await asyncio.sleep(.3) + + # set the SPI format + # spi speed is FPU frequency divided by 4 + # data order is MSB + # mode is MODE3 + await my_board.spi_set_format(8, 1, 3) + await asyncio.sleep(.3) + + # set up power and control register + await my_board.spi_write_blocking(10, [45, 0]) + await asyncio.sleep(.3) + + await my_board.spi_write_blocking(10, [45, 8]) + await asyncio.sleep(.3) + + # set up data format register for 4 wire spi + await my_board.spi_write_blocking(10, [49, 0]) + await asyncio.sleep(.3) + + # read 6 bytes from the data register + # for a multibyte read, we need to OR in a 0x40 into the register value + while True: + # read 6 bytes from the data register + try: + await my_board.spi_read_blocking(10, 50 | 0x40, 6, the_callback) + + await asyncio.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + + +# instantiate telemetrix_aio +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(autostart=False, + transport_type=2) + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_analog_input.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_analog_input.py new file mode 100644 index 0000000..ed0b9af --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_analog_input.py @@ -0,0 +1,100 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This file demonstrates analog input using both callbacks and +polling. Time stamps are provided in both "cooked" and raw form +""" + +# Set up a pin for analog input and monitor its changes +ANALOG_PIN = 2 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +async def the_callback(data): + """ + A callback function to report data changes. + + :param data: [pin_mode, pin, current_reported_value, timestamp] + """ + + formatted_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Analog Call Input Callback: pin={data[CB_PIN]}, ' + f'Value={data[CB_VALUE]} Time={formatted_time} ' + f'(Raw Time={data[CB_TIME]})') + + +async def analog_in(my_board, pin): + """ + This function establishes the pin as an + analog input. Any changes on this pin will + be reported through the call back function. + + Every 5 seconds the last value and time stamp is polled + and printed. + + Also, the differential parameter is being used. + The callback will only be called when there is + difference of 5 or more between the current and + last value reported. + + :param my_board: a telemetrix_aio instance + + :param pin: Arduino pin number + """ + + await my_board.set_pin_mode_analog_input(pin, 5, the_callback) + + # await asyncio.sleep(5) + # await my_board.disable_analog_reporting() + # await asyncio.sleep(5) + # await my_board.enable_analog_reporting() + + # run forever waiting for input changes + try: + while True: + await asyncio.sleep(.001) + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(analog_in(board, ANALOG_PIN)) +except (KeyboardInterrupt, RuntimeError) as e: + loop.run_until_complete(board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_blink.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_blink.py new file mode 100644 index 0000000..4d7302d --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_blink.py @@ -0,0 +1,69 @@ +""" + Copyright (c) 2020 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +import asyncio +import sys + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a pin for digital output and output a signal +and toggle the pin. Do this 4 times. +""" + +# some globals +DIGITAL_PIN = 13 # arduino pin number + + +async def blink(my_board, pin): + """ + This function will to toggle a digital pin. + + :param my_board: a telemetrix_aio instance + :param pin: pin to be controlled + """ + + # set the pin mode + await my_board.set_pin_mode_digital_output(pin) + + # toggle the pin 4 times and exit + for x in range(4): + print('ON') + await my_board.digital_write(pin, 0) + await asyncio.sleep(1) + print('OFF') + await my_board.digital_write(pin, 1) + await asyncio.sleep(1) + + await my_board.shutdown() + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio( + transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(blink(board, DIGITAL_PIN)) + # asyncio.run(blink(board, DIGITAL_PIN)) + +except KeyboardInterrupt: + loop.run_until_complete(board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_dht.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_dht.py new file mode 100644 index 0000000..b1c30bc --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_dht.py @@ -0,0 +1,109 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program monitors two DHT22 and two DHT11 sensors. +""" + + +# indices into callback data for valid data +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# HUMIDITY = 4 +# TEMPERATURE = 5 +# TIME = 6 + +# indices into callback data for error report +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# TIME = 4 + +# Arduino Pin Number +DHT_PIN = 8 + + +# A callback function to display the distance +# noinspection GrazieInspection +async def the_callback(data): + # noinspection GrazieInspection + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.DHT, error = 0, pin number, + dht_type, humidity, temperature timestamp] + if this is an error report: + [report_type = PrivateConstants.DHT, error != 0, pin number, dht_type + timestamp] + """ + if data[1]: + pass # ignore errors + # error message + # date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[4])) + # print(f'DHT Error Report:' + # f'Pin: {data[2]} DHT Type: {data[3]} Error: {data[1]} Time: {date}') + else: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[6])) + print(f'DHT Valid Data Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Humidity: {data[4]} Temperature:' + f' {data[5]} Time: {date}') + + +async def dht(my_board): + # noinspection GrazieInspection + """ + Set the pin mode for a DHT 22 device. Results will appear via the + callback. + + :param my_board: a telemetrix instance + + """ + + # set the pin mode for a DHT 11 + await my_board.set_pin_mode_dht(DHT_PIN, callback=the_callback, dht_type=11) + + # just sit in a loop waiting for the reports to come in + while True: + try: + await asyncio.sleep(.001) + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') +try: + loop.run_until_complete(dht(board)) +except (KeyboardInterrupt, RuntimeError): + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_digital_input.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_digital_input.py new file mode 100644 index 0000000..0dbd0a5 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_digital_input.py @@ -0,0 +1,110 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Monitor a digital input pin +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + +# variable to hold the last time a button state changed +debounce_time = time.time() + + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin_mode, pin, current reported value, timestamp] + """ + global debounce_time + + # if the time from the last event change is > .2 seconds, the input is debounced + if data[CB_TIME] - debounce_time > .2: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + debounce_time = data[CB_TIME] + + +async def digital_in(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # set the pin mode + await my_board.set_pin_mode_digital_input(pin, the_callback) + + # uncomment to try out report enable/disable + # await asyncio.sleep(1) + # await my_board.disable_all_reporting() + # await asyncio.sleep(4) + # await my_board.enable_digital_reporting(12) + + # await asyncio.sleep(3) + # await my_board.enable_digital_reporting(pin) + # await asyncio.sleep(1) + + while True: + try: + await asyncio.sleep(.001) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(digital_in(board, 12)) +except (KeyboardInterrupt, RuntimeError) as e: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_digital_input_pullup.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_digital_input_pullup.py new file mode 100644 index 0000000..9df4463 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_digital_input_pullup.py @@ -0,0 +1,91 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a digital pin for input pullup and monitor its changes. +""" + +# some globals +DIGITAL_PIN = 12 # arduino pin number +KILL_TIME = 5 # sleep time to keep forever loop open + +# Callback data indices +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +# Set up a pin for digital pin input and monitor its changes + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +async def digital_in_pullup(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # start monitoring the pin by setting its mode + await my_board.set_pin_mode_digital_input_pullup(pin, the_callback) + + # get pin changes forever + while True: + try: + await asyncio.sleep(KILL_TIME) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(digital_in_pullup(board, 12)) +except (KeyboardInterrupt, RuntimeError) as e: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_fade.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_fade.py new file mode 100644 index 0000000..882ea99 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_fade.py @@ -0,0 +1,73 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time +import asyncio + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a pin for output and fade its intensity +""" + +# some globals +# make sure to select a PWM pin +DIGITAL_PIN = 13 + + +async def fade(the_board, pin): + # Set the DIGITAL_PIN as an output pin + await the_board.set_pin_mode_analog_output(pin) + + # When hitting control-c to end the program + # in this loop, we are likely to get a KeyboardInterrupt + # exception. Catch the exception and exit gracefully. + + try: + print('Fading up...') + for i in range(255): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.005) + print('Fading down...') + for i in range(255, -1, -1): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.005) + await the_board.shutdown() + except KeyboardInterrupt: + the_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(fade(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_hc-sr04_distance_sensor.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_hc-sr04_distance_sensor.py new file mode 100644 index 0000000..175c044 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_hc-sr04_distance_sensor.py @@ -0,0 +1,84 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(.1) + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_i2c_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_i2c_adxl345_accelerometer.py new file mode 100644 index 0000000..d843583 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_i2c_adxl345_accelerometer.py @@ -0,0 +1,104 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This example sets up and control an ADXL345 i2c accelerometer. +It will continuously print data the raw xyz data from the device. +""" + + +# the call back function to print the adxl345 data +async def the_callback(data): + """ + + :param data: [pin_type, Device address, device read register, x data pair, y data pair, z data pair] + :return: + """ + report_type = data[0] + number_bytes_read = data[2] + i2c_device_address = data[3] + i2c_register = data[4] + x_msb = data[5] + x_lsb = data[6] + y_msb = data[7] + y_lsb = data[8] + z_msb = data[9] + z_lsb = data[10] + + x_data = int((x_msb << 8) + x_lsb) + y_data = int((y_msb << 8) + y_lsb) + z_data = int((z_msb << 8) + z_lsb) + + # test report type for SPI report + if report_type == 10: + print(f'i2c Report: i2c device address: {i2c_device_address} i2c ' + f'Register: ' + f'{i2c_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + # setup adxl345 + # device address = 83 + await my_board.set_pin_mode_i2c() + + # set up power and control register + await my_board.i2c_write(83, [45, 0]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [45, 8]) + await asyncio.sleep(.1) + + # set up the data format register + await my_board.i2c_write(83, [49, 8]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [49, 3]) + await asyncio.sleep(.1) + + # read_count = 20 + while True: + # read 6 bytes from the data register + try: + await my_board.i2c_read(83, 50, 6, the_callback) + await asyncio.sleep(.3) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_loop_back.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_loop_back.py new file mode 100644 index 0000000..baeb038 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_loop_back.py @@ -0,0 +1,78 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Loopback some data to assure that data can be sent and received between +the Telemetrix client and arduino-telemetrix server. +""" + + +async def the_callback(data): + """ + A callback function to report receive the looped back data + + :param data: [looped back data] + """ + print(f'Looped back: {chr(data[0])}') + + +async def loop_back(my_board, loop_back_data): + """ + This function will request that the supplied characters be + sent to the board and looped back and printed out to the console. + + :param my_board: a telemetrix_aio instance + :param loop_back_data: A list of characters to have looped back + """ + # await my_board.start_aio() + try: + for data in loop_back_data: + await my_board.loop_back(data, callback=the_callback) + print(f'Sending: {data}') + await asyncio.sleep(1) + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio( + transport_address='192.168.2.118') +char_list = ['A', 'B', 'Z'] + +try: + # start the main function + loop.run_until_complete(loop_back(board, char_list)) + + try: + loop.run_until_complete(board.shutdown()) + except: + pass +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_scroll_message.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_scroll_message.py new file mode 100644 index 0000000..0f38aa7 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_scroll_message.py @@ -0,0 +1,54 @@ +""" + Copyright (c) 2020 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import asyncio + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a pin for digital output +and toggle the pin 5 times. +""" + +# some globals +message = 'Hello World' + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + + +async def test_scroll(the_message): + try: + await board.enable_scroll_message(the_message) + await asyncio.sleep(5) + await board.disable_scroll_message() + await asyncio.sleep(5) + await board.shutdown() + sys.exit(0) + except KeyboardInterrupt: + await board.shutdown() + +try: + loop.run_until_complete(test_scroll(message)) +except KeyboardInterrupt: + loop.run_until_complete(board.shutdown()) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_servo.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_servo.py new file mode 100644 index 0000000..2b2ead2 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_servo.py @@ -0,0 +1,66 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This example will set a servo to 0, 90 and 180 degree +positions. +""" + + +async def servo(my_board, pin): + """ + Set a pin to servo mode and then adjust + its position. + + :param my_board: telemetrix_aio instance + :param pin: pin to be controlled + """ + + # set the pin mode + await my_board.set_pin_mode_servo(pin) + + await asyncio.sleep(1) + + await my_board.servo_write(pin, 0) + await asyncio.sleep(1) + await my_board.servo_write(pin, 90) + await asyncio.sleep(1) + await my_board.servo_write(pin, 180) + await my_board.servo_detach(pin) + +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') +try: + loop.run_until_complete(servo(board, 5)) + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_sonar_disable.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_sonar_disable.py new file mode 100644 index 0000000..9286f38 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_sonar_disable.py @@ -0,0 +1,85 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(5) + await my_board.sonar_disable() + await asyncio.sleep(5) + await my_board.sonar_enable() + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) +except KeyboardInterrupt: + loop.run_until_complete(board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_spi_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_spi_adxl345_accelerometer.py new file mode 100644 index 0000000..3250e60 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_spi_adxl345_accelerometer.py @@ -0,0 +1,122 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + + +""" +This program reads x, y, and z registers of an ADXL345 using 4 Wire SPI interface +""" + +""" +Connection scheme: +CS = pin 10 +SDA = pin 11 +SDO = pin 12 +SCL = pin 13 + +""" + + +async def the_callback(data): + """ + :param data: [pin_type, chip select pin, device read register, x data pair, + y data pair, z data pair, time stamp] + + """ + report_type = data[0] + chip_select_pin = data[1] + device_read_register = data[2] & 0x3f # strip off read command bits + number_bytes_read = data[3] + x_msb = data[4] + x_lsb = data[5] + y_msb = data[6] + y_lsb = data[7] + z_msb = data[8] + z_lsb = data[9] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 13: + print(f'SPI Report: CS Pin: {chip_select_pin} SPI Register: ' + f'{device_read_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + """ + + :type my_board: object + """ + # initialize spi mode for chipselect on pin 10 + await my_board.set_pin_mode_spi([10]) + + # set the SPI format + # spi speed is FPU frequency divided by 4 + # data order is MSB + # mode is MODE3 + await my_board.spi_set_format(8, 1, 3) + await asyncio.sleep(.3) + + # set up power and control register + await my_board.spi_write_blocking(10, [45, 0]) + await asyncio.sleep(.3) + + await my_board.spi_write_blocking(10, [45, 8]) + await asyncio.sleep(.3) + + # set up data format register for 4 wire spi + await my_board.spi_write_blocking(10, [49, 0]) + await asyncio.sleep(.3) + + # read 6 bytes from the data register + # for a multibyte read, we need to OR in a 0x40 into the register value + while True: + # read 6 bytes from the data register + try: + await my_board.spi_read_blocking(10, 50 | 0x40, 6, the_callback) + + await asyncio.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_address='192.168.2.118') + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_using_class.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_using_class.py new file mode 100644 index 0000000..e78d658 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/WIFI/wwa_using_class.py @@ -0,0 +1,89 @@ +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Establishing a digital and analog input pin and monitoring their values. +""" + + +class PinsWithinClass: + def __init__(self, board, d_pin, a_pin): + """ + + :param board: a telemetrix aio instance + :param d_pin: digital input pin number + :param a_pin: analog input pin number + + """ + self.board = board + self.d_pin = d_pin + self.a_pin = a_pin + + self.last_analog_value = 0 + + async def run_it(self): + """ + Initialize pin modes with the callbacks + """ + + await self.board.set_pin_mode_digital_input(self.d_pin, callback=self.callback) + await self.board.set_pin_mode_analog_input(self.a_pin, + differential=3, callback=self.callback) + + async def callback(self, data): + """ + + :param data: data[0] = report type 2 = digital input + report type 3 = analog input + data[1] = pin number + data[2] = reported value + """ + if data[0] == 2: + if data[2] == self.last_analog_value: + pass + else: + self.last_analog_value = data[2] + print(f'digital input pin {data[1]} reports a value of {data[2]}') + elif data[0] == 3: + print(f'analog input pin {data[1]} reports a value of {data[2]}') + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +the_board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio( + transport_address='192.168.2.118') + + +async def monitor(my_board, digital_pin, analog_pin): + """ + Set the pin modes for the pins + + :param my_board: telemetrix aio instance + :param digital_pin: Arduino digital input pin number + :param analog_pin: Arduino analog input pin number + + """ + + pwc = PinsWithinClass(my_board, digital_pin, analog_pin) + await pwc.run_it() + # wait forever + while True: + try: + await asyncio.sleep(.00001) + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +try: + # start the main function + loop.run_until_complete(monitor(the_board, 12, 2)) +except KeyboardInterrupt: + loop.run_until_complete(the_board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_analog_input.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_analog_input.py new file mode 100644 index 0000000..f0c9250 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_analog_input.py @@ -0,0 +1,100 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This file demonstrates analog input using both callbacks and +polling. Time stamps are provided in both "cooked" and raw form +""" + +# Set up a pin for analog input and monitor its changes +ANALOG_PIN = 2 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +async def the_callback(data): + """ + A callback function to report data changes. + + :param data: [pin_mode, pin, current_reported_value, timestamp] + """ + + formatted_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Analog Call Input Callback: pin={data[CB_PIN]}, ' + f'Value={data[CB_VALUE]} Time={formatted_time} ' + f'(Raw Time={data[CB_TIME]})') + + +async def analog_in(my_board, pin): + """ + This function establishes the pin as an + analog input. Any changes on this pin will + be reported through the call back function. + + Every 5 seconds the last value and time stamp is polled + and printed. + + Also, the differential parameter is being used. + The callback will only be called when there is + difference of 5 or more between the current and + last value reported. + + :param my_board: a telemetrix_aio instance + + :param pin: Arduino pin number + """ + await my_board.set_pin_mode_analog_input(pin, 5, the_callback) + + # await asyncio.sleep(5) + # await my_board.disable_analog_reporting() + # await asyncio.sleep(5) + # await my_board.enable_analog_reporting() + + # run forever waiting for input changes + try: + while True: + await asyncio.sleep(.001) + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(analog_in(board, ANALOG_PIN)) + loop.run_until_complete(board.shutdown()) +except (KeyboardInterrupt, RuntimeError) as e: + loop.run_until_complete(board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_blink.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_blink.py new file mode 100644 index 0000000..671578c --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_blink.py @@ -0,0 +1,68 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +import asyncio +import sys + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a pin for digital output and output a signal +and toggle the pin. Do this 4 times. +""" + +# some globals +DIGITAL_PIN = 13 # arduino pin number + + +async def blink(my_board, pin): + """ + This function will to toggle a digital pin. + + :param my_board: a telemetrix_aio instance + :param pin: pin to be controlled + """ + + # set the pin mode + await my_board.set_pin_mode_digital_output(pin) + + # toggle the pin 4 times and exit + for x in range(4): + print('ON') + await my_board.digital_write(pin, 1) + await asyncio.sleep(1) + print('OFF') + await my_board.digital_write(pin, 0) + await asyncio.sleep(1) + + await my_board.shutdown() + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) +try: + # start the main function + loop.run_until_complete(blink(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_dht.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_dht.py new file mode 100644 index 0000000..1649a62 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_dht.py @@ -0,0 +1,109 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program monitors two DHT22 and two DHT11 sensors. +""" + + +# indices into callback data for valid data +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# HUMIDITY = 4 +# TEMPERATURE = 5 +# TIME = 6 + +# indices into callback data for error report +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# TIME = 4 + +# Arduino Pin Number +DHT_PIN = 8 + + +# A callback function to display the distance +# noinspection GrazieInspection +async def the_callback(data): + # noinspection GrazieInspection + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.DHT, error = 0, pin number, + dht_type, humidity, temperature timestamp] + if this is an error report: + [report_type = PrivateConstants.DHT, error != 0, pin number, dht_type + timestamp] + """ + if data[1]: + pass # ignore error messages + # error message + # date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[4])) + # print(f'DHT Error Report:' + # f'Pin: {data[2]} DHT Type: {data[3]} Error: {data[1]} Time: {date}') + else: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[6])) + print(f'DHT Valid Data Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Humidity: {data[4]} Temperature:' + f' {data[5]} Time: {date}') + + +async def dht(my_board): + # noinspection GrazieInspection + """ + Set the pin mode for a DHT 22 device. Results will appear via the + callback. + + :param my_board: a telemetrix instance + + """ + + # set the pin mode for a DHT 22 + await my_board.set_pin_mode_dht(DHT_PIN, callback=the_callback, dht_type=11) + + # just sit in a loop waiting for the reports to come in + while True: + try: + await asyncio.sleep(.1) + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) +try: + loop.run_until_complete(dht(board)) +except (KeyboardInterrupt, RuntimeError): + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_digital_input.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_digital_input.py new file mode 100644 index 0000000..61ad947 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_digital_input.py @@ -0,0 +1,107 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Monitor a digital input pin +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin_mode, pin, current reported value, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +async def digital_in(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # set the pin mode + await my_board.set_pin_mode_digital_input(pin, the_callback) + + # uncomment to try out report enable/disable + # await asyncio.sleep(1) + # await my_board.disable_all_reporting() + # await asyncio.sleep(4) + # await my_board.enable_digital_reporting(12) + + # await asyncio.sleep(3) + # await my_board.enable_digital_reporting(pin) + # await asyncio.sleep(1) + + while True: + try: + await asyncio.sleep(.001) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(digital_in(board, 12)) + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) +except (KeyboardInterrupt, RuntimeError) as e: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_digital_input_pullup.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_digital_input_pullup.py new file mode 100644 index 0000000..8f35297 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_digital_input_pullup.py @@ -0,0 +1,91 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a digital pin for input pullup and monitor its changes. +""" + +# some globals +DIGITAL_PIN = 12 # arduino pin number +KILL_TIME = 5 # sleep time to keep forever loop open + +# Callback data indices +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +# Set up a pin for digital pin input and monitor its changes + +async def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +async def digital_in_pullup(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix_aio instance + :param pin: Arduino pin number + """ + + # start monitoring the pin by setting its mode + await my_board.set_pin_mode_digital_input_pullup(pin, the_callback) + + # get pin changes forever + while True: + try: + await asyncio.sleep(KILL_TIME) + except KeyboardInterrupt: + await board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(digital_in_pullup(board, 12)) +except (KeyboardInterrupt, RuntimeError) as e: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_fade.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_fade.py new file mode 100644 index 0000000..5975e26 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_fade.py @@ -0,0 +1,71 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time +import asyncio + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio +""" +Setup a pin for output and fade its intensity +""" + +# some globals +# make sure to select a PWM pin +DIGITAL_PIN = 13 + + +async def fade(the_board, pin): + # Set the DIGITAL_PIN as an output pin + await the_board.set_pin_mode_analog_output(pin) + + # When hitting control-c to end the program + # in this loop, we are likely to get a KeyboardInterrupt + # exception. Catch the exception and exit gracefully. + + try: + print('Fading up...') + for i in range(255): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.005) + print('Fading down...') + for i in range(255, -1, -1): + await the_board.analog_write(DIGITAL_PIN, i) + await asyncio.sleep(.005) + except KeyboardInterrupt: + the_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(fade(board, DIGITAL_PIN)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_hc-sr04_distance_sensor.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_hc-sr04_distance_sensor.py new file mode 100644 index 0000000..54b861d --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_hc-sr04_distance_sensor.py @@ -0,0 +1,84 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(.1) + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_i2c_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_i2c_adxl345_accelerometer.py new file mode 100644 index 0000000..c62c20a --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_i2c_adxl345_accelerometer.py @@ -0,0 +1,104 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This example sets up and control an ADXL345 i2c accelerometer. +It will continuously print data the raw xyz data from the device. +""" + + +# the call back function to print the adxl345 data +async def the_callback(data): + """ + + :param data: [pin_type, Device address, device read register, x data pair, y data pair, z data pair] + :return: + """ + report_type = data[0] + number_bytes_read = data[2] + i2c_device_address = data[3] + i2c_register = data[4] + x_msb = data[5] + x_lsb = data[6] + y_msb = data[7] + y_lsb = data[8] + z_msb = data[9] + z_lsb = data[10] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 10: + print(f'i2c Report: i2c device address: {i2c_device_address} i2c ' + f'Register: ' + f'{i2c_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + # setup adxl345 + # device address = 83 + await my_board.set_pin_mode_i2c() + + # set up power and control register + await my_board.i2c_write(83, [45, 0]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [45, 8]) + await asyncio.sleep(.1) + + # set up the data format register + await my_board.i2c_write(83, [49, 8]) + await asyncio.sleep(.1) + await my_board.i2c_write(83, [49, 3]) + await asyncio.sleep(.1) + + # read_count = 20 + while True: + # read 6 bytes from the data register + try: + await my_board.i2c_read(83, 50, 6, the_callback) + await asyncio.sleep(.1) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_loop_back.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_loop_back.py new file mode 100644 index 0000000..e52d8d1 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_loop_back.py @@ -0,0 +1,74 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio +""" +Loopback some data to assure that data can be sent and received between +the Telemetrix client and arduino-telemetrix server. +""" + + +async def the_callback(data): + """ + A callback function to report receive the looped back data + + :param data: [looped back data] + """ + print(f'Looped back: {chr(data[0])}') + + +async def loop_back(my_board, loop_back_data): + """ + This function will request that the supplied characters be + sent to the board and looped back and printed out to the console. + + :param my_board: a telemetrix_aio instance + :param loop_back_data: A list of characters to have looped back + """ + try: + for data in loop_back_data: + await my_board.loop_back(data, callback=the_callback) + print(f'Sending: {data}') + await asyncio.sleep(.1) + await asyncio.sleep(1) + await my_board.shutdown() + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) +char_list = ['A', 'B', 'Z'] +try: + # start the main function + loop.run_until_complete(loop_back(board, char_list)) + try: + loop.run_until_complete(board.shutdown()) + except: + pass +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_scroll_message.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_scroll_message.py new file mode 100644 index 0000000..e03423d --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_scroll_message.py @@ -0,0 +1,51 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import asyncio + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +Setup a pin for digital output +and toggle the pin 5 times. +""" + +# some globals +message = 'Hello World' + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + + +async def test_scroll(the_message): + await board.enable_scroll_message(the_message) + await asyncio.sleep(5) + await board.disable_scroll_message() + await asyncio.sleep(5) + await board.shutdown() + sys.exit(0) + +try: + loop.run_until_complete(test_scroll(message)) +except KeyboardInterrupt: + loop.run_until_complete(board.shutdown()) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_servo.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_servo.py new file mode 100644 index 0000000..04b35d6 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_servo.py @@ -0,0 +1,66 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This example will set a servo to 0, 90 and 180 degree +positions. +""" + + +async def servo(my_board, pin): + """ + Set a pin to servo mode and then adjust + its position. + + :param my_board: telemetrix_aio instance + :param pin: pin to be controlled + """ + + # set the pin mode + await my_board.set_pin_mode_servo(pin) + + await asyncio.sleep(1) + + await my_board.servo_write(pin, 0) + await asyncio.sleep(1) + await my_board.servo_write(pin, 90) + await asyncio.sleep(1) + await my_board.servo_write(pin, 180) + await my_board.servo_detach(pin) + +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) +try: + loop.run_until_complete(servo(board, 5)) + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_sonar_disable.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_sonar_disable.py new file mode 100644 index 0000000..057bd95 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_sonar_disable.py @@ -0,0 +1,85 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +async def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +async def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + await my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + await asyncio.sleep(5) + await my_board.sonar_disable() + await asyncio.sleep(5) + await my_board.sonar_enable() + + except KeyboardInterrupt: + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback)) +except KeyboardInterrupt: + loop.run_until_complete(board.shutdown()) + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_spi_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_spi_adxl345_accelerometer.py new file mode 100644 index 0000000..2151fef --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/asyncio/usbSerial/wsa_spi_adxl345_accelerometer.py @@ -0,0 +1,123 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio import telemetrix_uno_r4_wifi_aio + + +""" +This program reads x, y, and z registers of an ADXL345 using 4 Wire SPI interface +""" + +""" +Connection scheme: +CS = pin 10 +SDA = pin 11 +SDO = pin 12 +SCL = pin 13 + +""" + + +async def the_callback(data): + """ + :param data: [pin_type, chip select pin, device read register, x data pair, + y data pair, z data pair, time stamp] + + """ + report_type = data[0] + chip_select_pin = data[1] + device_read_register = data[2] & 0x3f # strip off read command bits + number_bytes_read = data[3] + x_msb = data[4] + x_lsb = data[5] + y_msb = data[6] + y_lsb = data[7] + z_msb = data[8] + z_lsb = data[9] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 13: + print(f'SPI Report: CS Pin: {chip_select_pin} SPI Register: ' + f'{device_read_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +async def adxl345(my_board): + """ + + :type my_board: object + """ + # initialize spi mode for chipselect on pin 10 + await my_board.set_pin_mode_spi([10]) + await asyncio.sleep(.3) + + # set the SPI format + # spi speed is FPU frequency divided by 4 + # data order is MSB + # mode is MODE3 + await my_board.spi_set_format(8, 1, 3) + await asyncio.sleep(.3) + + # set up power and control register + await my_board.spi_write_blocking(10, [45, 0]) + await asyncio.sleep(.3) + + await my_board.spi_write_blocking(10, [45, 8]) + await asyncio.sleep(.3) + + # set up data format register for 4 wire spi + await my_board.spi_write_blocking(10, [49, 0]) + await asyncio.sleep(.3) + + # read 6 bytes from the data register + # for a multibyte read, we need to OR in a 0x40 into the register value + while True: + # read 6 bytes from the data register + try: + await my_board.spi_read_blocking(10, 50 | 0x40, 6, the_callback) + + await asyncio.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + await my_board.shutdown() + sys.exit(0) + + +# get the event loop +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi_aio.TelemetrixUnoR4WiFiAio(transport_type=1) + +try: + # start the main function + loop.run_until_complete(adxl345(board)) +except KeyboardInterrupt: + try: + loop.run_until_complete(board.shutdown()) + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_analog_input.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_analog_input.py new file mode 100644 index 0000000..ba5108d --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_analog_input.py @@ -0,0 +1,89 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi +""" +Monitor an analog input pin +""" + +""" +Setup a pin for analog input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +ANALOG_PIN = 2 # arduino pin number (A2) + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin Mode: {data[CB_PIN_MODE]} Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +def analog_in(my_board, pin): + """ + This function establishes the pin as an + analog input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_analog_input(pin, differential=5, callback=the_callback) + + # time.sleep(5) + # my_board.disable_analog_reporting() + # time.sleep(5) + # my_board.enable_analog_reporting() + + print('Enter Control-C to quit.') + try: + while True: + try: + time.sleep(1) + except KeyboardInterrupt: + sys.exit(0) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') +try: + analog_in(board, ANALOG_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_blink.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_blink.py new file mode 100644 index 0000000..c14b701 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_blink.py @@ -0,0 +1,55 @@ +""" + Copyright (c) 2020 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi +""" +Setup a pin for digital output +and toggle the pin 5 times. +""" + +# some globals +DIGITAL_PIN = 13 # the board LED + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') + + +# Set the DIGITAL_PIN as an output pin +board.set_pin_mode_digital_output(DIGITAL_PIN) + +# Blink the LED and provide feedback as +# to the LED state on the console. +for blink in range(5): + # When hitting control-c to end the program + # in this loop, we are likely to get a KeyboardInterrupt + # exception. Catch the exception and exit gracefully. + try: + print('On') + board.digital_write(DIGITAL_PIN, 0) + time.sleep(1) + print('Off') + board.digital_write(DIGITAL_PIN, 1) + time.sleep(1) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) +board.shutdown() diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_dht.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_dht.py new file mode 100644 index 0000000..cb9b4f0 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_dht.py @@ -0,0 +1,100 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program monitors a DHT22 sensor. +""" + +# Arduino pin number +DHT_PIN = 8 + +# indices into callback data for valid data +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# HUMIDITY = 4 +# TEMPERATURE = 5 +# TIME = 6 + +# indices into callback data for error report +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# TIME = 4 + + +# A callback function to display the distance +# noinspection GrazieInspection +def the_callback(data): + # noinspection GrazieInspection + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.DHT, error = 0, pin number, + dht_type, humidity, temperature timestamp] + if this is an error report: + [report_type = PrivateConstants.DHT, error != 0, pin number, dht_type + timestamp] + """ + if data[1]: + # error message + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[4])) + print(f'DHT Error Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Error: {data[1]} Time: {date}') + else: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[6])) + print(f'DHT Valid Data Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Humidity: {data[4]} Temperature:' + f' {data[5]} Time: {date}') + + +def dht(my_board, pin, callback, dht_type): + # noinspection GrazieInspection + """ + Set the pin mode for a DHT 22 device. Results will appear via the + callback. + + :param my_board: an telemetrix instance + :param pin: Arduino pin number + :param callback: The callback function + :param dht_type: 22 or 11 + """ + + # set the pin mode for the DHT device + my_board.set_pin_mode_dht(pin, callback=callback, dht_type=dht_type) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') +try: + dht(board, DHT_PIN, the_callback, 11) + + # wait forever + while True: + try: + time.sleep(2) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_digital_input.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_digital_input.py new file mode 100644 index 0000000..7899b7d --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_digital_input.py @@ -0,0 +1,99 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi +""" +Monitor a digital input pin +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + +# variable to hold the last time a button state changed +debounce_time = time.time() + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + global debounce_time + + # if the time from the last event change is > .2 seconds, the input is debounced + if data[CB_TIME] - debounce_time > .3: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + debounce_time = data[CB_TIME] + + +def digital_in(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_digital_input(pin, the_callback) + # time.sleep(1) + # my_board.disable_all_reporting() + # time.sleep(4) + # my_board.enable_digital_reporting(12) + + # time.sleep(3) + # my_board.enable_digital_reporting(pin) + # time.sleep(1) + + print('Enter Control-C to quit.') + # my_board.enable_digital_reporting(12) + try: + while True: + time.sleep(.0001) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') +try: + digital_in(board, DIGITAL_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_digital_input_pullup.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_digital_input_pullup.py new file mode 100644 index 0000000..7a18782 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_digital_input_pullup.py @@ -0,0 +1,93 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Monitor a digital input pin with pullup enabled +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin Mode: {data[CB_PIN_MODE]} Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +def digital_in_pullup(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_digital_input_pullup(pin, the_callback) + # time.sleep(1) + # my_board.disable_all_reporting() + # time.sleep(4) + # my_board.enable_digital_reporting(12) + + # time.sleep(3) + # my_board.enable_digital_reporting(pin) + # time.sleep(1) + + print('Enter Control-C to quit.') + # my_board.enable_digital_reporting(12) + try: + while True: + time.sleep(.0001) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') + +try: + digital_in_pullup(board, DIGITAL_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_fade.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_fade.py new file mode 100644 index 0000000..0ad9f33 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_fade.py @@ -0,0 +1,59 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Setup a pin for output and fade its intensity +""" + +# some globals +# make sure to select a PWM pin +DIGITAL_PIN = 13 + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') + +# Set the DIGITAL_PIN as an output pin +board.set_pin_mode_analog_output(DIGITAL_PIN) +# board.set_pin_mode_analog_output(DIGITAL_PIN) + + +# When hitting control-c to end the program +# in this loop, we are likely to get a KeyboardInterrupt +# exception. Catch the exception and exit gracefully. + +try: + print('Fading up...') + for i in range(255): + board.analog_write(DIGITAL_PIN, i) + time.sleep(.005) + print('Fading down...') + for i in range(255, -1, -1): + board.analog_write(DIGITAL_PIN, i) + time.sleep(.005) + + board.set_pin_mode_digital_output(DIGITAL_PIN) + +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_hc-sr04_distance_sensor.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_hc-sr04_distance_sensor.py new file mode 100644 index 0000000..8315ef3 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_hc-sr04_distance_sensor.py @@ -0,0 +1,75 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + time.sleep(.01) + except KeyboardInterrupt: + my_board.shutdown() + time.sleep(1) + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') +try: + sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback) + board.shutdown() +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_i2c_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_i2c_adxl345_accelerometer.py new file mode 100644 index 0000000..258d949 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_i2c_adxl345_accelerometer.py @@ -0,0 +1,98 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This example sets up and control an ADXL345 i2c accelerometer. +It will continuously print data the raw xyz data from the device. +""" + + +# the call back function to print the adxl345 data +def the_callback(data): + """ + + :param data: [pin_type, Device address, device read register, x data pair, y data pair, z data pair] + :return: + """ + report_type = data[0] + number_bytes_read = data[2] + i2c_device_address = data[3] + i2c_register = data[4] + x_msb = data[5] + x_lsb = data[6] + y_msb = data[7] + y_lsb = data[8] + z_msb = data[9] + z_lsb = data[10] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 10: + print(f'i2c Report: i2c device address: {i2c_device_address } i2c ' + f'Register: ' + f'{i2c_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +def adxl345(my_board): + # setup adxl345 + # device address = 83 + my_board.set_pin_mode_i2c() + + # set up power and control register + my_board.i2c_write(83, [45, 0]) + time.sleep(.1) + my_board.i2c_write(83, [45, 8]) + time.sleep(.1) + + # set up the data format register + my_board.i2c_write(83, [49, 8]) + time.sleep(.1) + my_board.i2c_write(83, [49, 3]) + time.sleep(.1) + + # read_count = 20 + while True: + # read 6 bytes from the data register + try: + my_board.i2c_read(83, 50, 6, the_callback) + time.sleep(.1) + + except (KeyboardInterrupt, RuntimeError): + my_board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') + +try: + adxl345(board) +except KeyboardInterrupt: + try: + board.shutdown() + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_loop_back.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_loop_back.py new file mode 100644 index 0000000..8d637e1 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_loop_back.py @@ -0,0 +1,61 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import time +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Loopback some data to assure that data can be sent and received between +the Telemetrix client and arduino-telemetrix server. +""" + + +def the_callback(data): + """ + A callback function to report receive the looped back data + + :param data: [looped back data] + """ + print(f'Looped back: {chr(data[0])}') + + +def loop_back(my_board, loop_back_data): + """ + This function will request that the supplied characters be + sent to the board and looped back and printed out to the console. + + :param my_board: a telemetrix instance + :param loop_back_data: A list of characters to have looped back + """ + try: + for data in loop_back_data: + my_board.loop_back(data, callback=the_callback) + print(f'Sending: {data}') + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') +char_list = ['A', 'B', 'Z'] +try: + loop_back(board, char_list) + time.sleep(1.5) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_scroll_message.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_scroll_message.py new file mode 100644 index 0000000..b9612cb --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_scroll_message.py @@ -0,0 +1,47 @@ +""" + Copyright (c) 2020 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Setup a pin for digital output +and toggle the pin 5 times. +""" + +# some globals +message = 'Hello World' + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') + +try: + # Set the DIGITAL_PIN as an output pin + board.enable_scroll_message(message) + time.sleep(5) + board.disable_scroll_message() + time.sleep(5) + board.shutdown() + sys.exit(0) +except: + board.shutdown() + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_servo.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_servo.py new file mode 100644 index 0000000..c5fc090 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_servo.py @@ -0,0 +1,50 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Attach a pin to a servo and move it about. +""" + +# some globals +SERVO_PIN = 5 + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') + +try: + board.set_pin_mode_servo(SERVO_PIN, 100, 3000) + time.sleep(.2) + board.servo_write(SERVO_PIN, 90) + time.sleep(1) + board.servo_write(SERVO_PIN, 0) + time.sleep(1) + board.servo_write(SERVO_PIN, 180) + time.sleep(1) + board.servo_write(SERVO_PIN, 90) + time.sleep(1) + + board.servo_detach(SERVO_PIN) + time.sleep(.2) + board.shutdown() +except KeyboardInterrupt: + board.shutdown() diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_sonar_disable.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_sonar_disable.py new file mode 100644 index 0000000..397bf10 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_sonar_disable.py @@ -0,0 +1,81 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + time.sleep(5) + my_board.sonar_disable() + time.sleep(5) + my_board.sonar_enable() + + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') +try: + sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback) + board.shutdown() +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_spi_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_spi_adxl345_accelerometer.py new file mode 100644 index 0000000..7da3975 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/WIFI/wwt_spi_adxl345_accelerometer.py @@ -0,0 +1,115 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program reads x, y, and z registers of an ADXL345 using 4 Wire SPI interface +""" + +""" +Connection scheme: +CS = pin 10 +SDA = pin 11 +SDO = pin 12 +SCL = pin 13 + +""" + + +def the_callback(data): + """ + :param data: [pin_type, chip select pin, device read register, x data pair, + y data pair, z data pair, time stamp] + + """ + report_type = data[0] + chip_select_pin = data[1] + device_read_register = data[2] & 0x3f # strip off read command bits + number_bytes_read = data[3] + x_msb = data[4] + x_lsb = data[5] + y_msb = data[6] + y_lsb = data[7] + z_msb = data[8] + z_lsb = data[9] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 13: + print(f'SPI Report: CS Pin: {chip_select_pin} SPI Register: ' + f'{device_read_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +def adxl345(my_board): + """ + + :type my_board: object + """ + # initialize spi mode for chipselect on pin 10 + my_board.set_pin_mode_spi([10]) + time.sleep(.3) + + # set the SPI format + # spi speed is FPU frequency divided by 4 + # data order is MSB + # mode is MODE3 + my_board.spi_set_format(8, 1, 3) + time.sleep(.3) + + # set up power and control register + my_board.spi_write_blocking(10, [45, 0]) + time.sleep(.3) + + my_board.spi_write_blocking(10, [45, 8]) + time.sleep(.3) + + # set up data format register for 4 wire spi + my_board.spi_write_blocking(10, [49, 0]) + time.sleep(.3) + + # read 6 bytes from the data register + # for a multibyte read, we need to OR in a 0x40 into the register value + while True: + # read 6 bytes from the data register + try: + my_board.spi_read_blocking(10, 50 | 0x40, 6, the_callback) + + time.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + my_board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_address='192.168.2.118') +try: + adxl345(board) +except KeyboardInterrupt: + try: + board.shutdown() + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_analog_input.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_analog_input.py new file mode 100644 index 0000000..57b6aba --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_analog_input.py @@ -0,0 +1,89 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi +""" +Monitor an analog input pin +""" + +""" +Setup a pin for analog input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +ANALOG_PIN = 2 # arduino pin number (A2) + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin Mode: {data[CB_PIN_MODE]} Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +def analog_in(my_board, pin): + """ + This function establishes the pin as an + analog input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_analog_input(pin, differential=5, callback=the_callback) + + # time.sleep(5) + # my_board.disable_analog_reporting() + # time.sleep(5) + # my_board.enable_analog_reporting() + + print('Enter Control-C to quit.') + try: + while True: + try: + time.sleep(1) + except KeyboardInterrupt: + sys.exit(0) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +try: + analog_in(board, ANALOG_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_blink.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_blink.py new file mode 100644 index 0000000..ee92420 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_blink.py @@ -0,0 +1,55 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + """ + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + + +""" +Setup a pin for digital output +and toggle the pin 5 times. +""" + +# some globals +DIGITAL_PIN = 13 # the board LED + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) + +# Set the DIGITAL_PIN as an output pin +board.set_pin_mode_digital_output(DIGITAL_PIN) + +# Blink the LED and provide feedback as +# to the LED state on the console. +for blink in range(5): + # When hitting control-c to end the program + # in this loop, we are likely to get a KeyboardInterrupt + # exception. Catch the exception and exit gracefully. + try: + print('1') + board.digital_write(DIGITAL_PIN, 1) + time.sleep(1) + print('0') + board.digital_write(DIGITAL_PIN, 0) + time.sleep(1) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) +board.shutdown() diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_dht.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_dht.py new file mode 100644 index 0000000..4595e53 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_dht.py @@ -0,0 +1,100 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program monitors a DHT22 sensor. +""" + +# Arduino pin number +DHT_PIN = 8 + +# indices into callback data for valid data +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# HUMIDITY = 4 +# TEMPERATURE = 5 +# TIME = 6 + +# indices into callback data for error report +# REPORT_TYPE = 0 +# READ_RESULT = 1 +# PIN = 2 +# DHT_TYPE = 3 +# TIME = 4 + + +# A callback function to display the distance +# noinspection GrazieInspection +def the_callback(data): + # noinspection GrazieInspection + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.DHT, error = 0, pin number, + dht_type, humidity, temperature timestamp] + if this is an error report: + [report_type = PrivateConstants.DHT, error != 0, pin number, dht_type + timestamp] + """ + if data[1]: + # error message + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[4])) + print(f'DHT Error Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Error: {data[1]} Time: {date}') + else: + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[6])) + print(f'DHT Valid Data Report:' + f'Pin: {data[2]} DHT Type: {data[3]} Humidity: {data[4]} Temperature:' + f' {data[5]} Time: {date}') + + +def dht(my_board, pin, callback, dht_type): + # noinspection GrazieInspection + """ + Set the pin mode for a DHT 22 device. Results will appear via the + callback. + + :param my_board: an telemetrix instance + :param pin: Arduino pin number + :param callback: The callback function + :param dht_type: 22 or 11 + """ + + # set the pin mode for the DHT device + my_board.set_pin_mode_dht(pin, callback, dht_type) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +try: + dht(board, DHT_PIN, callback=the_callback, dht_type=11) + + # wait forever + while True: + try: + time.sleep(.01) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_digital_input.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_digital_input.py new file mode 100644 index 0000000..756e7aa --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_digital_input.py @@ -0,0 +1,92 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi +""" +Monitor a digital input pin +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +def digital_in(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_digital_input(pin, the_callback) + # time.sleep(1) + # my_board.disable_all_reporting() + # time.sleep(4) + # my_board.enable_digital_reporting(12) + + # time.sleep(3) + # my_board.enable_digital_reporting(pin) + # time.sleep(1) + + print('Enter Control-C to quit.') + # my_board.enable_digital_reporting(12) + try: + while True: + time.sleep(.0001) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +try: + digital_in(board, DIGITAL_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_digital_input_pullup.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_digital_input_pullup.py new file mode 100644 index 0000000..1f2d4c4 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_digital_input_pullup.py @@ -0,0 +1,93 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + DHT support courtesy of Martyn Wheeler + Based on the DHTNew library - https://github.com/RobTillaart/DHTNew +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Monitor a digital input pin with pullup enabled +""" + +""" +Setup a pin for digital input and monitor its changes +""" + +# Set up a pin for analog input and monitor its changes +DIGITAL_PIN = 12 # arduino pin number + +# Callback data indices +CB_PIN_MODE = 0 +CB_PIN = 1 +CB_VALUE = 2 +CB_TIME = 3 + + +def the_callback(data): + """ + A callback function to report data changes. + This will print the pin number, its reported value and + the date and time when the change occurred + + :param data: [pin, current reported value, pin_mode, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[CB_TIME])) + print(f'Pin Mode: {data[CB_PIN_MODE]} Pin: {data[CB_PIN]} Value: {data[CB_VALUE]} Time Stamp: {date}') + + +def digital_in_pullup(my_board, pin): + """ + This function establishes the pin as a + digital input. Any changes on this pin will + be reported through the call back function. + + :param my_board: a telemetrix instance + :param pin: Arduino pin number + """ + + # set the pin mode + my_board.set_pin_mode_digital_input_pullup(pin, the_callback) + # time.sleep(1) + # my_board.disable_all_reporting() + # time.sleep(4) + # my_board.enable_digital_reporting(12) + + # time.sleep(3) + # my_board.enable_digital_reporting(pin) + # time.sleep(1) + + print('Enter Control-C to quit.') + # my_board.enable_digital_reporting(12) + try: + while True: + time.sleep(.0011) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) + +try: + digital_in_pullup(board, DIGITAL_PIN) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_fade.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_fade.py new file mode 100644 index 0000000..f9742f6 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_fade.py @@ -0,0 +1,59 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Setup a pin for output and fade its intensity +""" + +# some globals +# make sure to select a PWM pin +DIGITAL_PIN = 13 + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) + +# Set the DIGITAL_PIN as an output pin +board.set_pin_mode_analog_output(DIGITAL_PIN) +# board.set_pin_mode_analog_output(DIGITAL_PIN) + + +# When hitting control-c to end the program +# in this loop, we are likely to get a KeyboardInterrupt +# exception. Catch the exception and exit gracefully. + +try: + print('Fading up...') + for i in range(255): + board.analog_write(DIGITAL_PIN, i) + time.sleep(.005) + print('Fading down...') + for i in range(255, -1, -1): + board.analog_write(DIGITAL_PIN, i) + time.sleep(.005) + + board.set_pin_mode_digital_output(DIGITAL_PIN) + +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_hc-sr04_distance_sensor.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_hc-sr04_distance_sensor.py new file mode 100644 index 0000000..617306e --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_hc-sr04_distance_sensor.py @@ -0,0 +1,75 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + time.sleep(.01) + except KeyboardInterrupt: + my_board.shutdown() + time.sleep(1) + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +try: + sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback) + board.shutdown() +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_i2c_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_i2c_adxl345_accelerometer.py new file mode 100644 index 0000000..94b132e --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_i2c_adxl345_accelerometer.py @@ -0,0 +1,98 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This example sets up and control an ADXL345 i2c accelerometer. +It will continuously print data the raw xyz data from the device. +""" + + +# the call back function to print the adxl345 data +def the_callback(data): + """ + + :param data: [pin_type, Device address, device read register, x data pair, y data pair, z data pair] + :return: + """ + report_type = data[0] + number_bytes_read = data[2] + i2c_device_address = data[3] + i2c_register = data[4] + x_msb = data[5] + x_lsb = data[6] + y_msb = data[7] + y_lsb = data[8] + z_msb = data[9] + z_lsb = data[10] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 10: + print(f'i2c Report: i2c device address: {i2c_device_address } i2c ' + f'Register: ' + f'{i2c_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +def adxl345(my_board): + # setup adxl345 + # device address = 83 + my_board.set_pin_mode_i2c() + + # set up power and control register + my_board.i2c_write(83, [45, 0]) + time.sleep(.1) + my_board.i2c_write(83, [45, 8]) + time.sleep(.1) + + # set up the data format register + my_board.i2c_write(83, [49, 8]) + time.sleep(.1) + my_board.i2c_write(83, [49, 3]) + time.sleep(.1) + + # read_count = 20 + while True: + # read 6 bytes from the data register + try: + my_board.i2c_read(83, 50, 6, the_callback) + time.sleep(.1) + + except (KeyboardInterrupt, RuntimeError): + my_board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) + +try: + adxl345(board) +except KeyboardInterrupt: + try: + board.shutdown() + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_loop_back.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_loop_back.py new file mode 100644 index 0000000..196f724 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_loop_back.py @@ -0,0 +1,62 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import time +import sys +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Loopback some data to assure that data can be sent and received between +the Telemetrix client and arduino-telemetrix server. +""" + + +def the_callback(data): + """ + A callback function to report receive the looped back data + + :param data: [looped back data] + """ + print(f'Looped back: {chr(data[0])}') + + +def loop_back(my_board, loop_back_data): + """ + This function will request that the supplied characters be + sent to the board and looped back and printed out to the console. + + :param my_board: a telemetrix instance + :param loop_back_data: A list of characters to have looped back + """ + try: + for data in loop_back_data: + my_board.loop_back(data, callback=the_callback) + print(f'Sending: {data}') + time.sleep(.1) + except KeyboardInterrupt: + board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +char_list = ['A', 'B', 'Z'] +try: + loop_back(board, char_list) + time.sleep(.1) +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_scroll_message.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_scroll_message.py new file mode 100644 index 0000000..90eb88e --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_scroll_message.py @@ -0,0 +1,46 @@ +""" + Copyright (c) 2020 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Setup a pin for digital output +and toggle the pin 5 times. +""" + +# some globals +message = 'Hello World' + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) + + +# Set the DIGITAL_PIN as an output pin +try: + board.enable_scroll_message(message) + time.sleep(5) + board.disable_scroll_message() + time.sleep(5) + board.shutdown() +except KeyboardInterrupt: + board.shutdown() + diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_servo.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_servo.py new file mode 100644 index 0000000..b5ef344 --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_servo.py @@ -0,0 +1,49 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import sys +import time + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +Attach a pin to a servo and move it about. +""" + +# some globals +SERVO_PIN = 5 + +# Create a Telemetrix instance. +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +try: + board.set_pin_mode_servo(SERVO_PIN, 100, 3000) + time.sleep(.2) + board.servo_write(SERVO_PIN, 90) + time.sleep(1) + board.servo_write(SERVO_PIN, 0) + time.sleep(1) + board.servo_write(SERVO_PIN, 180) + time.sleep(1) + board.servo_write(SERVO_PIN, 90) + time.sleep(1) + + board.servo_detach(SERVO_PIN) + time.sleep(.2) + board.shutdown() +except KeyboardInterrupt: + board.shutdown() diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_sonar_disable.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_sonar_disable.py new file mode 100644 index 0000000..972f68a --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_sonar_disable.py @@ -0,0 +1,81 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program continuously monitors an HC-SR04 Ultrasonic Sensor +It reports changes to the distance sensed. +""" +TRIGGER_PIN = 9 +ECHO_PIN = 10 + +# indices into callback data +REPORT_TYPE = 0 +TRIG_PIN = 1 +DISTANCE = 2 +TIME = 3 + + +# A callback function to display the distance +def the_callback(data): + """ + The callback function to display the change in distance + :param data: [report_type = PrivateConstants.SONAR_DISTANCE, trigger pin number, distance, timestamp] + """ + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[3])) + print(f'Sonar Report: Trigger Pin: {data[1]} Distance: {data[2]} Time: {date}') + + +def sonar(my_board, trigger_pin, echo_pin, callback): + """ + Set the pin mode for a sonar device. Results will appear via the + callback. + + :param my_board: a telemetrix_aio instance + :param trigger_pin: Arduino pin number + :param echo_pin: Arduino pin number + :param callback: The callback function + """ + + # set the pin mode for the trigger and echo pins + my_board.set_pin_mode_sonar(trigger_pin, echo_pin, callback) + # wait forever + while True: + try: + time.sleep(5) + my_board.sonar_disable() + time.sleep(5) + my_board.sonar_enable() + + except KeyboardInterrupt: + my_board.shutdown() + sys.exit(0) + + +# instantiate telemetrix_aio +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +try: + sonar(board, TRIGGER_PIN, ECHO_PIN, the_callback) + board.shutdown() +except (KeyboardInterrupt, RuntimeError): + board.shutdown() + sys.exit(0) + diff --git a/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_spi_adxl345_accelerometer.py b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_spi_adxl345_accelerometer.py new file mode 100644 index 0000000..722574a --- /dev/null +++ b/telemetrix_uno_r4/r4_wifi_examples/threaded/usbSerial/wst_spi_adxl345_accelerometer.py @@ -0,0 +1,115 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import sys +import time +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi import telemetrix_uno_r4_wifi + +""" +This program reads x, y, and z registers of an ADXL345 using 4 Wire SPI interface +""" + +""" +Connection scheme: +CS = pin 10 +SDA = pin 11 +SDO = pin 12 +SCL = pin 13 + +""" + + +def the_callback(data): + """ + :param data: [pin_type, chip select pin, device read register, x data pair, + y data pair, z data pair, time stamp] + + """ + report_type = data[0] + chip_select_pin = data[1] + device_read_register = data[2] & 0x3f # strip off read command bits + number_bytes_read = data[3] + x_msb = data[4] + x_lsb = data[5] + y_msb = data[6] + y_lsb = data[7] + z_msb = data[8] + z_lsb = data[9] + + x_data = (x_msb << 8) + x_lsb + y_data = (y_msb << 8) + y_lsb + z_data = (z_msb << 8) + z_lsb + + # test report type for SPI report + if report_type == 13: + print(f'SPI Report: CS Pin: {chip_select_pin} SPI Register: ' + f'{device_read_register} Number Of Bytes Read: {number_bytes_read} x: ' + f'{x_data} y: {y_data} z: {z_data}') + else: + print(f'unexpected report type: {report_type}') + + +def adxl345(my_board): + """ + + :type my_board: object + """ + # initialize spi mode for chipselect on pin 10 + my_board.set_pin_mode_spi([10]) + time.sleep(.3) + + # set the SPI format + # spi speed is FPU frequency divided by 4 + # data order is MSB + # mode is MODE3 + my_board.spi_set_format(8, 1, 3) + time.sleep(.3) + + # set up power and control register + my_board.spi_write_blocking(10, [45, 0]) + time.sleep(.3) + + my_board.spi_write_blocking(10, [45, 8]) + time.sleep(.3) + + # set up data format register for 4 wire spi + my_board.spi_write_blocking(10, [49, 0]) + time.sleep(.3) + + # read 6 bytes from the data register + # for a multibyte read, we need to OR in a 0x40 into the register value + while True: + # read 6 bytes from the data register + try: + my_board.spi_read_blocking(10, 50 | 0x40, 6, the_callback) + + time.sleep(.5) + + except (KeyboardInterrupt, RuntimeError): + my_board.shutdown() + sys.exit(0) + + +board = telemetrix_uno_r4_wifi.TelemetrixUnoR4WiFi(transport_type=1) +try: + adxl345(board) +except KeyboardInterrupt: + try: + board.shutdown() + except: + pass + sys.exit(0) diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/__init__.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/private_constants.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/private_constants.py new file mode 100644 index 0000000..ea6d662 --- /dev/null +++ b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/private_constants.py @@ -0,0 +1,155 @@ +""" + Copyright (c) 2015-2021 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + +class PrivateConstants: + """ + This class contains a set of constants for telemetrix internal use . + """ + + # commands + # send a loop back request - for debugging communications + LOOP_COMMAND = 0 + SET_PIN_MODE = 1 # set a pin to INPUT/OUTPUT/PWM/etc + DIGITAL_WRITE = 2 # set a single digital pin value instead of entire port + ANALOG_WRITE = 3 + MODIFY_REPORTING = 4 + GET_FIRMWARE_VERSION = 5 + ARE_U_THERE = 6 # Arduino ID query for auto-detect of telemetrix connected boards + SERVO_ATTACH = 7 + SERVO_WRITE = 8 + SERVO_DETACH = 9 + I2C_BEGIN = 10 + I2C_READ = 11 + I2C_WRITE = 12 + SONAR_NEW = 13 + DHT_NEW = 14 + STOP_ALL_REPORTS = 15 + SET_ANALOG_SCANNING_INTERVAL = 16 + ENABLE_ALL_REPORTS = 17 + RESET = 18 + SPI_INIT = 19 + SPI_WRITE_BLOCKING = 20 + SPI_READ_BLOCKING = 21 + SPI_SET_FORMAT = 22 + SPI_CS_CONTROL = 23 + ONE_WIRE_INIT = 24 + ONE_WIRE_RESET = 25 + ONE_WIRE_SELECT = 26 + ONE_WIRE_SKIP = 27 + ONE_WIRE_WRITE = 28 + ONE_WIRE_READ = 29 + ONE_WIRE_RESET_SEARCH = 30 + ONE_WIRE_SEARCH = 31 + ONE_WIRE_CRC8 = 32 + SET_PIN_MODE_STEPPER = 33 + STEPPER_MOVE_TO = 34 + STEPPER_MOVE = 35 + STEPPER_RUN = 36 + STEPPER_RUN_SPEED = 37 + STEPPER_SET_MAX_SPEED = 38 + STEPPER_SET_ACCELERATION = 39 + STEPPER_SET_SPEED = 40 + STEPPER_SET_CURRENT_POSITION = 41 + STEPPER_RUN_SPEED_TO_POSITION = 42 + STEPPER_STOP = 43 + STEPPER_DISABLE_OUTPUTS = 44 + STEPPER_ENABLE_OUTPUTS = 45 + STEPPER_SET_MINIMUM_PULSE_WIDTH = 46 + STEPPER_SET_ENABLE_PIN = 47 + STEPPER_SET_3_PINS_INVERTED = 48 + STEPPER_SET_4_PINS_INVERTED = 49 + STEPPER_IS_RUNNING = 50 + STEPPER_GET_CURRENT_POSITION = 51 + STEPPER_GET_DISTANCE_TO_GO = 52 + STEPPER_GET_TARGET_POSITION = 53 + GET_FEATURES = 54 + SONAR_DISABLE = 55 + SONAR_ENABLE = 56 + BOARD_HARD_RESET = 57 + SCROLL_MESSAGE_ON = 58 + SCROLL_MESSAGE_OFF = 59 + + # reports + # debug data from Arduino + DIGITAL_REPORT = DIGITAL_WRITE + ANALOG_REPORT = ANALOG_WRITE + FIRMWARE_REPORT = GET_FIRMWARE_VERSION + I_AM_HERE_REPORT = ARE_U_THERE + SERVO_UNAVAILABLE = SERVO_ATTACH + I2C_TOO_FEW_BYTES_RCVD = 8 + I2C_TOO_MANY_BYTES_RCVD = 9 + I2C_READ_REPORT = 10 + SONAR_DISTANCE = 11 + DHT_REPORT = 12 + SPI_REPORT = 13 + ONE_WIRE_REPORT = 14 + STEPPER_DISTANCE_TO_GO = 15 + STEPPER_TARGET_POSITION = 16 + STEPPER_CURRENT_POSITION = 17 + STEPPER_RUNNING_REPORT = 18 + STEPPER_RUN_COMPLETE_REPORT = 19 + FEATURES = 20 + DEBUG_PRINT = 99 + + TELEMETRIX_VERSION = "1.00" + + # reporting control + REPORTING_DISABLE_ALL = 0 + REPORTING_ANALOG_ENABLE = 1 + REPORTING_DIGITAL_ENABLE = 2 + REPORTING_ANALOG_DISABLE = 3 + REPORTING_DIGITAL_DISABLE = 4 + + # Pin mode definitions + AT_INPUT = 0 + AT_OUTPUT = 1 + AT_INPUT_PULLUP = 2 + AT_ANALOG = 3 + AT_SERVO = 4 + AT_SONAR = 5 + AT_DHT = 6 + AT_MODE_NOT_SET = 255 + + # maximum number of digital pins supported + NUMBER_OF_DIGITAL_PINS = 100 + + # maximum number of analog pins supported + NUMBER_OF_ANALOG_PINS = 20 + + # maximum number of sonars allowed + MAX_SONARS = 6 + + # maximum number of DHT devices allowed + MAX_DHTS = 6 + + # DHT Report sub-types + DHT_DATA = 0 + DHT_ERROR = 1 + + # feature masks + ONEWIRE_FEATURE = 0x01 + DHT_FEATURE = 0x02 + STEPPERS_FEATURE = 0x04 + SPI_FEATURE = 0x08 + SERVO_FEATURE = 0x10 + SONAR_FEATURE = 0x20 + + # transport types + TRANSPORT_WIFI = 1 + TRANSPORT_USB_SERIAL = 2 + TRANSPORT_BLE = 3 diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/telemetrix_uno_r4_wifi.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/telemetrix_uno_r4_wifi.py new file mode 100644 index 0000000..e06fb2a --- /dev/null +++ b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi/telemetrix_uno_r4_wifi.py @@ -0,0 +1,2645 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import socket +import sys +import threading +import time +from collections import deque + +import serial +# noinspection PyPackageRequirementscd +from serial.serialutil import SerialException +# noinspection PyPackageRequirements +from serial.tools import list_ports + +# noinspection PyUnresolvedReferences +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi.private_constants import PrivateConstants + + +# noinspection PyPep8,PyMethodMayBeStatic,GrazieInspection,PyBroadException,PyCallingNonCallable +class TelemetrixUnoR4WiFi(threading.Thread): + """ + This class exposes and implements the telemetrix API. + It uses threading to accommodate concurrency. + It includes the public API methods as well as + a set of private methods. + + """ + + # noinspection PyPep8,PyPep8,PyPep8 + def __init__(self, com_port=None, arduino_instance_id=1, + arduino_wait=1, sleep_tune=0.000001, + shutdown_on_exception=True, hard_reset_on_shutdown=True, + transport_address=None, ip_port=31336, transport_type=0): + + """ + + :param com_port: e.g. COM3 or /dev/ttyACM0. + Only use if you wish to bypass auto com port + detection. + + :param arduino_instance_id: Match with the value installed on the + arduino-telemetrix sketch. + + :param arduino_wait: Amount of time to wait for an Arduino to + fully reset itself. + + :param sleep_tune: A tuning parameter (typically not changed by user) + + :param shutdown_on_exception: call shutdown before raising + a RunTimeError exception, or + receiving a KeyboardInterrupt exception + + :param hard_reset_on_shutdown: reset the board on shutdown + + :param transport_address: ip address of tcp/ip connected device. + + :param ip_port: ip port of tcp/ip connected device + + :param transport_type: 0 = WiFI + 1 = USBSerial + 2 = BLE + + + """ + + # initialize threading parent + threading.Thread.__init__(self) + + # create the threads and set them as daemons so + # that they stop when the program is closed + + # create a thread to interpret received serial data + self.the_reporter_thread = threading.Thread(target=self._reporter) + self.the_reporter_thread.daemon = True + + self.transport_address = transport_address + self.ip_port = ip_port + + if transport_type not in [0, 1, 2]: + raise RuntimeError("Valid transport_type value is 0, 1, or 2") + + self.transport_type = transport_type + + if transport_type == 0: + if not transport_address: + raise RuntimeError("An IP address must be specified.") + + if not self.transport_address: + self.the_data_receive_thread = threading.Thread(target=self._serial_receiver) + else: + self.the_data_receive_thread = threading.Thread(target=self._tcp_receiver) + + self.the_data_receive_thread.daemon = True + + # flag to allow the reporter and receive threads to run. + self.run_event = threading.Event() + + # check to make sure that Python interpreter is version 3.7 or greater + python_version = sys.version_info + if python_version[0] >= 3: + if python_version[1] >= 7: + pass + else: + raise RuntimeError("ERROR: Python 3.7 or greater is " + "required for use of this program.") + + # save input parameters as instance variables + self.com_port = com_port + self.arduino_instance_id = arduino_instance_id + self.arduino_wait = arduino_wait + self.sleep_tune = sleep_tune + self.shutdown_on_exception = shutdown_on_exception + self.hard_reset_on_shutdown = hard_reset_on_shutdown + + # create a deque to receive and process data from the arduino + self.the_deque = deque() + + # The report_dispatch dictionary is used to process + # incoming report messages by looking up the report message + # and executing its associated processing method. + + self.report_dispatch = {} + + # To add a command to the command dispatch table, append here. + self.report_dispatch.update( + {PrivateConstants.LOOP_COMMAND: self._report_loop_data}) + self.report_dispatch.update( + {PrivateConstants.DEBUG_PRINT: self._report_debug_data}) + self.report_dispatch.update( + {PrivateConstants.DIGITAL_REPORT: self._digital_message}) + self.report_dispatch.update( + {PrivateConstants.ANALOG_REPORT: self._analog_message}) + self.report_dispatch.update( + {PrivateConstants.FIRMWARE_REPORT: self._firmware_message}) + self.report_dispatch.update({PrivateConstants.I_AM_HERE_REPORT: self._i_am_here}) + self.report_dispatch.update( + {PrivateConstants.SERVO_UNAVAILABLE: self._servo_unavailable}) + self.report_dispatch.update( + {PrivateConstants.I2C_READ_REPORT: self._i2c_read_report}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_FEW_BYTES_RCVD: self._i2c_too_few}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_MANY_BYTES_RCVD: self._i2c_too_many}) + self.report_dispatch.update( + {PrivateConstants.SONAR_DISTANCE: self._sonar_distance_report}) + self.report_dispatch.update({PrivateConstants.DHT_REPORT: self._dht_report}) + self.report_dispatch.update( + {PrivateConstants.SPI_REPORT: self._spi_report}) + self.report_dispatch.update( + {PrivateConstants.ONE_WIRE_REPORT: self._onewire_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_CURRENT_POSITION: + self._stepper_current_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUNNING_REPORT: + self._stepper_is_running_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUN_COMPLETE_REPORT: + self._stepper_run_complete_report}) + + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.FEATURES: + self._features_report}) + + # dictionaries to store the callbacks for each pin + self.analog_callbacks = {} + + self.digital_callbacks = {} + + self.i2c_callback = None + self.i2c_callback2 = None + + self.i2c_1_active = False + self.i2c_2_active = False + + self.spi_callback = None + + self.onewire_callback = None + + self.cs_pins_enabled = [] + + # the trigger pin will be the key to retrieve + # the callback for a specific HC-SR04 + self.sonar_callbacks = {} + + self.sonar_count = 0 + + self.dht_callbacks = {} + + self.dht_count = 0 + + # serial port in use + self.serial_port = None + + # socket for tcp/ip communications + self.sock = None + + # flag to indicate we are in shutdown mode + self.shutdown_flag = False + + # debug loopback callback method + self.loop_back_callback = None + + # flag to indicate the start of a new report + # self.new_report_start = True + + # firmware version to be stored here + self.firmware_version = [] + + # reported arduino instance id + self.reported_arduino_id = [] + + # reported features + self.reported_features = 0 + + # flag to indicate if i2c was previously enabled + self.i2c_enabled = False + + # flag to indicate if spi is initialized + self.spi_enabled = False + + # flag to indicate if onewire is initialized + self.onewire_enabled = False + + # stepper motor variables + + # updated when a new motor is added + # self.next_stepper_assigned = 0 + # + # # valid list of stepper motor interface types + # self.valid_stepper_interfaces = [1, 2, 3, 4, 6, 8] + # + # # maximum number of steppers supported + # self.max_number_of_steppers = 4 + # + # # number of steppers created - not to exceed the maximum + # self.number_of_steppers = 0 + # + # # dictionary to hold stepper motor information + # self.stepper_info = {'instance': False, 'is_running': None, + # 'maximum_speed': 1, 'speed': 0, 'acceleration': 0, + # 'distance_to_go_callback': None, + # 'target_position_callback': None, + # 'current_position_callback': None, + # 'is_running_callback': None, + # 'motion_complete_callback': None, + # 'acceleration_callback': None} + # + # # build a list of stepper motor info items + # self.stepper_info_list = [] + # # a list of dictionaries to hold stepper information + # for motor in range(self.max_number_of_steppers): + # self.stepper_info_list.append(self.stepper_info) + + self.the_reporter_thread.start() + self.the_data_receive_thread.start() + + print(f"telemetrix_uno_r4_wifi: Version" + f" {PrivateConstants.TELEMETRIX_VERSION}\n\n" + f"Copyright (c) 2023 Alan Yorinks All Rights Reserved.\n") + + # using the serial link + if not self.transport_address: + if not self.com_port: + # user did not specify a com_port + try: + self._find_arduino() + except KeyboardInterrupt: + if self.shutdown_on_exception: + self.shutdown() + else: + # com_port specified - set com_port and baud rate + try: + self._manual_open() + except KeyboardInterrupt: + if self.shutdown_on_exception: + self.shutdown() + + if self.serial_port: + print( + f"Arduino compatible device found and connected to {self.serial_port.port}") + + self.serial_port.reset_input_buffer() + self.serial_port.reset_output_buffer() + self.disable_scroll_message() + + # no com_port found - raise a runtime exception + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('No Arduino Found or User Aborted Program') + else: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.transport_address, self.ip_port)) + print(f'Successfully connected to: {self.transport_address}:{self.ip_port}') + + # allow the threads to run + self._run_threads() + print(f'Waiting for Arduino to reset') + print(f'Reset Complete') + + # get telemetrix firmware version and print it + print('\nRetrieving Telemetrix4UnoR4WiFi firmware ID...') + self._get_firmware_version() + if not self.firmware_version: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'Telemetrix4UnoR4WiFi firmware version') + + else: + + print(f'Telemetrix4UnoR4WiFi firmware version: {self.firmware_version[0]}.' + f'{self.firmware_version[1]}.{self.firmware_version[2]}') + command = [PrivateConstants.ENABLE_ALL_REPORTS] + self._send_command(command) + + # get the features list + command = [PrivateConstants.GET_FEATURES] + self._send_command(command) + time.sleep(.2) + + # Have the server reset its data structures + command = [PrivateConstants.RESET] + self._send_command(command) + time.sleep(.2) + + def _find_arduino(self): + """ + This method will search all potential serial ports for an Arduino + containing a sketch that has a matching arduino_instance_id as + specified in the input parameters of this class. + + This is used explicitly with the Telemetrix4Arduino sketch. + """ + + # a list of serial ports to be checked + serial_ports = [] + + print('Opening all potential serial ports...') + the_ports_list = list_ports.comports() + for port in the_ports_list: + if port.pid is None: + continue + try: + self.serial_port = serial.Serial(port.device, 115200, + timeout=1, writeTimeout=0) + except SerialException: + continue + # create a list of serial ports that we opened + serial_ports.append(self.serial_port) + + # display to the user + print('\t' + port.device) + + # clear out any possible data in the input buffer + # wait for arduino to reset + print( + f'\nWaiting {self.arduino_wait} seconds(arduino_wait) for Arduino devices to ' + 'reset...') + # temporary for testing + time.sleep(self.arduino_wait) + self._run_threads() + + for serial_port in serial_ports: + self.serial_port = serial_port + + self._get_arduino_id() + if self.reported_arduino_id != self.arduino_instance_id: + continue + else: + print('Valid Arduino ID Found.') + self.serial_port.reset_input_buffer() + self.serial_port.reset_output_buffer() + return + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'Incorrect Arduino ID: {self.reported_arduino_id}') + + def _manual_open(self): + """ + Com port was specified by the user - try to open up that port + + """ + # if port is not found, a serial exception will be thrown + try: + print(f'Opening {self.com_port}...') + self.serial_port = serial.Serial(self.com_port, 115200, + timeout=1, writeTimeout=0) + + print( + f'\nWaiting {self.arduino_wait} seconds(arduino_wait) for Arduino devices to ' + 'reset...') + self._run_threads() + time.sleep(self.arduino_wait) + + self._get_arduino_id() + + if self.reported_arduino_id != self.arduino_instance_id: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'Incorrect Arduino ID: {self.reported_arduino_id}') + print('Valid Arduino ID Found.') + # get arduino firmware version and print it + print('\nRetrieving Telemetrix4Arduino firmware ID...') + self._get_firmware_version() + + if not self.firmware_version: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Telemetrix4Arduino Sketch Firmware Version Not Found') + + else: + print(f'Telemetrix4UnoR4 firmware version: {self.firmware_version[0]}.' + f'{self.firmware_version[1]}') + except KeyboardInterrupt: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('User Hit Control-C') + + def analog_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (maximum 16 bits) + + """ + value_msb = value >> 8 + value_lsb = value & 0xff + command = [PrivateConstants.ANALOG_WRITE, pin, value_msb, value_lsb] + self._send_command(command) + + def digital_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (1 or 0) + + """ + + command = [PrivateConstants.DIGITAL_WRITE, pin, value] + self._send_command(command) + + def disable_all_reporting(self): + """ + Disable reporting for all digital and analog input pins + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DISABLE_ALL, 0] + self._send_command(command) + + def disable_analog_reporting(self, pin): + """ + Disables analog reporting for a single analog pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_DISABLE, pin] + self._send_command(command) + + def disable_digital_reporting(self, pin): + """ + Disables digital reporting for a single digital input. + + :param pin: Pin number. + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_DISABLE, pin] + self._send_command(command) + + def enable_analog_reporting(self, pin): + """ + Enables analog reporting for the specified pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_ENABLE, pin] + self._send_command(command) + + def enable_digital_reporting(self, pin): + """ + Enable reporting on the specified digital pin. + + :param pin: Pin number. + """ + + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_ENABLE, pin] + self._send_command(command) + + def _get_arduino_id(self): + """ + Retrieve arduino-telemetrix arduino id + + """ + command = [PrivateConstants.ARE_U_THERE] + self._send_command(command) + # provide time for the reply + time.sleep(.5) + + def _get_firmware_version(self): + """ + This method retrieves the + arduino-telemetrix firmware version + + """ + command = [PrivateConstants.GET_FIRMWARE_VERSION] + self._send_command(command) + # provide time for the reply + time.sleep(.5) + + def i2c_read(self, address, register, number_of_bytes, + callback=None, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the + specified register for the i2c device. + + + :param address: i2c device address + + :param register: i2c register (or None if no register + selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report + i2c data as a result of read command + + :param i2c_port: 0 = default, 1 = secondary + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + + self._i2c_read_request(address, register, number_of_bytes, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + def i2c_read_restart_transmission(self, address, register, + number_of_bytes, + callback=None, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the specified + register for the i2c device. This restarts the transmission + after the read. It is required for some i2c devices such as the MMA8452Q + accelerometer. + + + :param address: i2c device address + + :param register: i2c register (or None if no register + selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report i2c + data as a result of read command + + :param i2c_port: 0 = default 1 = secondary + + :param write_register: If True, the register is written before read + Else, the write is suppressed + + + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + + self._i2c_read_request(address, register, number_of_bytes, + stop_transmission=False, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + def _i2c_read_request(self, address, register, number_of_bytes, + stop_transmission=True, callback=None, i2c_port=0, + write_register=True): + """ + This method requests the read of an i2c device. Results are retrieved + via callback. + + :param address: i2c device address + + :param register: register number (or None if no register selection is needed) + + :param number_of_bytes: number of bytes expected to be returned + + :param stop_transmission: stop transmission after read + + :param callback: Required callback function to report i2c data as a + result of read command. + + :param write_register: If True, the register is written before read + Else, the write is suppressed + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 2.') + + if not callback: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('I2C Read: A callback function must be specified.') + + if not i2c_port: + self.i2c_callback = callback + else: + self.i2c_callback2 = callback + + if not register: + register = 0 + + if write_register: + write_register = 1 + else: + write_register = 0 + + # message contains: + # 1. address + # 2. register + # 3. number of bytes + # 4. restart_transmission - True or False + # 5. i2c port + # 6. suppress write flag + + command = [PrivateConstants.I2C_READ, address, register, number_of_bytes, + stop_transmission, i2c_port, write_register] + self._send_command(command) + + def i2c_write(self, address, args, i2c_port=0): + """ + Write data to an i2c device. + + :param address: i2c device address + + :param i2c_port: 0= port 1, 1 = port 2 + + :param args: A variable number of bytes to be sent to the device + passed in as a list + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 2.') + + command = [PrivateConstants.I2C_WRITE, len(args), address, i2c_port] + + for item in args: + command.append(item) + + self._send_command(command) + + def loop_back(self, start_character, callback=None): + """ + This is a debugging method to send a character to the + Arduino device, and have the device loop it back. + + :param start_character: The character to loop back. It should be + an integer. + + :param callback: Looped back character will appear in the callback method + + """ + command = [PrivateConstants.LOOP_COMMAND, ord(start_character)] + self.loop_back_callback = callback + self._send_command(command) + + def set_analog_scan_interval(self, interval): + """ + Set the analog scanning interval. + + :param interval: value of 0 - 255 - milliseconds + """ + + if 0 <= interval <= 255: + command = [PrivateConstants.SET_ANALOG_SCANNING_INTERVAL, interval] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('Analog interval must be between 0 and 255') + + def set_pin_mode_analog_output(self, pin_number): + """ + Set a pin as a pwm (analog output) pin. + + :param pin_number:arduino pin number + + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT) + + def set_pin_mode_analog_input(self, pin_number, differential=0, callback=None): + """ + Set a pin as an analog input. + + :param pin_number: arduino pin number + + :param differential: difference in previous to current value before + report will be generated + + :param callback: callback function + + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for analog input pins = 3 + + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_ANALOG, differential, + callback) + + def set_pin_mode_digital_input(self, pin_number, callback=None): + """ + Set a pin as a digital input. + + :param pin_number: arduino pin number + + :param callback: callback function + + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT, callback=callback) + + def set_pin_mode_digital_input_pullup(self, pin_number, callback=None): + """ + Set a pin as a digital input with pullup enabled. + + :param pin_number: arduino pin number + + :param callback: callback function + + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + """ + self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT_PULLUP, + callback=callback) + + def set_pin_mode_digital_output(self, pin_number): + """ + Set a pin as a digital output pin. + + :param pin_number: arduino pin number + """ + + self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT) + + def set_pin_mode_i2c(self, i2c_port=0): + """ + Establish the standard Arduino i2c pins for i2c utilization. + + :param i2c_port: 0 = i2c1, 1 = i2c2 + + NOTES: 1. THIS METHOD MUST BE CALLED BEFORE ANY I2C REQUEST IS MADE + 2. Callbacks are set within the individual i2c read methods of this + API. + + See i2c_read, or i2c_read_restart_transmission. + + """ + # test for i2c port 2 + if i2c_port: + # if not previously activated set it to activated + # and the send a begin message for this port + if not self.i2c_2_active: + self.i2c_2_active = True + else: + return + # port 1 + else: + if not self.i2c_1_active: + self.i2c_1_active = True + else: + return + + command = [PrivateConstants.I2C_BEGIN, i2c_port] + self._send_command(command) + + def set_pin_mode_dht(self, pin, callback=None, dht_type=22): + """ + + :param pin: connection pin + + :param callback: callback function + + :param dht_type: either 22 for DHT22 or 11 for DHT11 + + Error Callback: [DHT REPORT Type, DHT_ERROR_NUMBER, PIN, DHT_TYPE, Time] + + Valid Data Callback: DHT REPORT Type, DHT_DATA=, PIN, DHT_TYPE, Humidity, + Temperature, + Time] + + DHT_REPORT_TYPE = 12 + """ + if self.reported_features & PrivateConstants.DHT_FEATURE: + if not callback: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('set_pin_mode_dht: A Callback must be specified') + + if self.dht_count < PrivateConstants.MAX_DHTS - 1: + self.dht_callbacks[pin] = callback + self.dht_count += 1 + + if dht_type != 22 and dht_type != 11: + dht_type = 22 + + command = [PrivateConstants.DHT_NEW, pin, dht_type] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Maximum Number Of DHTs Exceeded - set_pin_mode_dht fails for pin {pin}') + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The DHT feature is disabled in the server.') + + # noinspection PyRedundantParentheses + def set_pin_mode_servo(self, pin_number, min_pulse=544, max_pulse=2400): + """ + + Attach a pin to a servo motor + + :param pin_number: pin + + :param min_pulse: minimum pulse width + + :param max_pulse: maximum pulse width + + """ + if self.reported_features & PrivateConstants.SERVO_FEATURE: + + minv = (min_pulse).to_bytes(2, byteorder="big") + maxv = (max_pulse).to_bytes(2, byteorder="big") + + command = [PrivateConstants.SERVO_ATTACH, pin_number, + minv[0], minv[1], maxv[0], maxv[1]] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The SERVO feature is disabled in the server.') + + def set_pin_mode_sonar(self, trigger_pin, echo_pin, + callback=None): + """ + + :param trigger_pin: + + :param echo_pin: + + :param callback: callback + + callback data: [PrivateConstants.SONAR_DISTANCE, trigger_pin, distance_value, time_stamp] + + """ + if self.reported_features & PrivateConstants.SONAR_FEATURE: + + if not callback: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('set_pin_mode_sonar: A Callback must be specified') + + if self.sonar_count < PrivateConstants.MAX_SONARS - 1: + self.sonar_callbacks[trigger_pin] = callback + self.sonar_count += 1 + + command = [PrivateConstants.SONAR_NEW, trigger_pin, echo_pin] + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Maximum Number Of Sonars Exceeded - set_pin_mode_sonar fails for pin {trigger_pin}') + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The SONAR feature is disabled in the server.') + + def set_pin_mode_spi(self, chip_select_list=None): + """ + Specify the list of chip select pins. + + Standard Arduino MISO, MOSI and CLK pins are used for the board in use. + + Chip Select is any digital output capable pin. + + :param chip_select_list: this is a list of pins to be used for chip select. + The pins will be configured as output, and set to high + ready to be used for chip select. + NOTE: You must specify the chips select pins here! + + + command message: [command, [cs pins...]] + """ + + if self.reported_features & PrivateConstants.SPI_FEATURE: + if type(chip_select_list) != list: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('chip_select_list must be in the form of a list') + if not chip_select_list: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('Chip select pins were not specified') + + self.spi_enabled = True + + command = [PrivateConstants.SPI_INIT, len(chip_select_list)] + + for pin in chip_select_list: + command.append(pin) + self.cs_pins_enabled.append(pin) + self._send_command(command) + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'The SPI feature is disabled in the server.') + + # def set_pin_mode_stepper(self, interface=1, pin1=2, pin2=3, pin3=4, + # pin4=5, enable=True): + # """ + # Stepper motor support is implemented as a proxy for the + # the AccelStepper library for the Arduino. + # + # This feature is compatible with the TB6600 Motor Driver + # + # Note: It may not work for other driver types! + # + # https://github.com/waspinator/AccelStepper + # + # Instantiate a stepper motor. + # + # Initialize the interface and pins for a stepper motor. + # + # :param interface: Motor Interface Type: + # + # 1 = Stepper Driver, 2 driver pins required + # + # 2 = FULL2WIRE 2 wire stepper, 2 motor pins required + # + # 3 = FULL3WIRE 3 wire stepper, such as HDD spindle, + # 3 motor pins required + # + # 4 = FULL4WIRE, 4 wire full stepper, 4 motor pins + # required + # + # 6 = HALF3WIRE, 3 wire half stepper, such as HDD spindle, + # 3 motor pins required + # + # 8 = HALF4WIRE, 4 wire half stepper, 4 motor pins required + # + # :param pin1: Arduino digital pin number for motor pin 1 + # + # :param pin2: Arduino digital pin number for motor pin 2 + # + # :param pin3: Arduino digital pin number for motor pin 3 + # + # :param pin4: Arduino digital pin number for motor pin 4 + # + # :param enable: If this is true, the output pins at construction time. + # + # :return: Motor Reference number + # """ + # if self.reported_features & PrivateConstants.STEPPERS_FEATURE: + # + # if self.number_of_steppers == self.max_number_of_steppers: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('Maximum number of steppers has already been assigned') + # + # if interface not in self.valid_stepper_interfaces: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('Invalid stepper interface') + # + # self.number_of_steppers += 1 + # + # motor_id = self.next_stepper_assigned + # self.next_stepper_assigned += 1 + # self.stepper_info_list[motor_id]['instance'] = True + # + # # build message and send message to server + # command = [PrivateConstants.SET_PIN_MODE_STEPPER, motor_id, interface, pin1, + # pin2, pin3, pin4, enable] + # self._send_command(command) + # + # # return motor id + # return motor_id + # else: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'The Stepper feature is disabled in the server.') + + def servo_write(self, pin_number, angle): + """ + + Set a servo attached to a pin to a given angle. + + :param pin_number: pin + + :param angle: angle (0-180) + + """ + command = [PrivateConstants.SERVO_WRITE, pin_number, angle] + self._send_command(command) + + def servo_detach(self, pin_number): + """ + Detach a servo for reuse + + :param pin_number: attached pin + + """ + command = [PrivateConstants.SERVO_DETACH, pin_number] + self._send_command(command) + + # def stepper_move_to(self, motor_id, position): + # """ + # Set an absolution target position. If position is positive, the movement is + # clockwise, else it is counter-clockwise. + # + # The run() function (below) will try to move the motor (at most one step per call) + # from the current position to the target position set by the most + # recent call to this function. Caution: moveTo() also recalculates the + # speed for the next step. + # If you are trying to use constant speed movements, you should call setSpeed() + # after calling moveTo(). + # + # :param motor_id: motor id: 0 - 3 + # + # :param position: target position. Maximum value is 32 bits. + # """ + # if position < 0: + # polarity = 1 + # else: + # polarity = 0 + # position = abs(position) + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_move_to: Invalid motor_id.') + # + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE_TO, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # self._send_command(command) + # + # def stepper_move(self, motor_id, relative_position): + # """ + # Set the target position relative to the current position. + # + # :param motor_id: motor id: 0 - 3 + # + # :param relative_position: The desired position relative to the current + # position. Negative is anticlockwise from + # the current position. Maximum value is 32 bits. + # """ + # if relative_position < 0: + # polarity = 1 + # else: + # polarity = 0 + # + # relative_position = abs(relative_position) + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_move: Invalid motor_id.') + # + # position_bytes = list(relative_position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # self._send_command(command) + # + # def stepper_run(self, motor_id, completion_callback=None): + # """ + # This method steps the selected motor based on the current speed. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run: A motion complete callback must be ' + # 'specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN, motor_id] + # self._send_command(command) + # + # def stepper_run_speed(self, motor_id): + # """ + # This method steps the selected motor based at a constant speed as set by the most + # recent call to stepper_set_max_speed(). The motor will run continuously. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run_speed: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_RUN_SPEED, motor_id] + # self._send_command(command) + # + # def stepper_set_max_speed(self, motor_id, max_speed): + # """ + # Sets the maximum permitted speed. The stepper_run() function will accelerate + # up to the speed set by this function. + # + # Caution: the maximum speed achievable depends on your processor and clock speed. + # The default maxSpeed is 1 step per second. + # + # Caution: Speeds that exceed the maximum speed supported by the processor may + # result in non-linear accelerations and decelerations. + # + # :param motor_id: 0 - 3 + # + # :param max_speed: 1 - 1000 + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Invalid motor_id.') + # + # if not 1 < max_speed <= 1000: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Speed range is 1 - 1000.') + # + # self.stepper_info_list[motor_id]['max_speed'] = max_speed + # max_speed_msb = (max_speed & 0xff00) >> 8 + # max_speed_lsb = max_speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MAX_SPEED, motor_id, max_speed_msb, + # max_speed_lsb] + # self._send_command(command) + # + # def stepper_get_max_speed(self, motor_id): + # """ + # Returns the maximum speed configured for this stepper + # that was previously set by stepper_set_max_speed() + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # :return: The currently configured maximum speed. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_max_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['max_speed'] + # + # def stepper_set_acceleration(self, motor_id, acceleration): + # """ + # Sets the acceleration/deceleration rate. + # + # :param motor_id: 0 - 3 + # + # :param acceleration: The desired acceleration in steps per second + # per second. Must be > 0.0. This is an + # expensive call since it requires a square + # root to be calculated on the server. + # Dont call more often than needed. + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Invalid motor_id.') + # + # if not 1 < acceleration <= 1000: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Acceleration range is 1 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['acceleration'] = acceleration + # + # max_accel_msb = acceleration >> 8 + # max_accel_lsb = acceleration & 0xff + # + # command = [PrivateConstants.STEPPER_SET_ACCELERATION, motor_id, max_accel_msb, + # max_accel_lsb] + # self._send_command(command) + # + # def stepper_set_speed(self, motor_id, speed): + # """ + # Sets the desired constant speed for use with stepper_run_speed(). + # + # :param motor_id: 0 - 3 + # + # :param speed: 0 - 1000 The desired constant speed in steps per + # second. Positive is clockwise. Speeds of more than 1000 steps per + # second are unreliable. Speed accuracy depends on the Arduino + # crystal. Jitter depends on how frequently you call the + # stepper_run_speed() method. + # The speed will be limited by the current value of + # stepper_set_max_speed(). + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_speed: Invalid motor_id.') + # + # if not 0 < speed <= 1000: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_speed: Speed range is 0 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['speed'] = speed + # + # speed_msb = speed >> 8 + # speed_lsb = speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_SPEED, motor_id, speed_msb, speed_lsb] + # self._send_command(command) + # + # def stepper_get_speed(self, motor_id): + # """ + # Returns the most recently set speed. + # that was previously set by stepper_set_speed(); + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['speed'] + # + # def stepper_get_distance_to_go(self, motor_id, distance_to_go_callback): + # """ + # Request the distance from the current position to the target position + # from the server. + # + # :param motor_id: 0 - 3 + # + # :param distance_to_go_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=15, motor_id, distance in steps, time_stamp] + # + # A positive distance is clockwise from the current position. + # + # """ + # if not distance_to_go_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go: Invalid motor_id.') + # self.stepper_info_list[motor_id][ + # 'distance_to_go_callback'] = distance_to_go_callback + # command = [PrivateConstants.STEPPER_GET_DISTANCE_TO_GO, motor_id] + # self._send_command(command) + # + # def stepper_get_target_position(self, motor_id, target_callback): + # """ + # Request the most recently set target position from the server. + # + # :param motor_id: 0 - 3 + # + # :param target_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=16, motor_id, target position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # + # """ + # if not target_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError( + # 'stepper_get_target_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_target_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id][ + # 'target_position_callback'] = target_callback + # + # command = [PrivateConstants.STEPPER_GET_TARGET_POSITION, motor_id] + # self._send_command(command) + # + # def stepper_get_current_position(self, motor_id, current_position_callback): + # """ + # Request the current motor position from the server. + # + # :param motor_id: 0 - 3 + # + # :param current_position_callback: required callback function to receive report + # + # :return: The current motor position returned via the callback as a list: + # + # [REPORT_TYPE=17, motor_id, current position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # """ + # if not current_position_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError( + # 'stepper_get_current_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_get_current_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['current_position_callback'] = current_position_callback + # + # command = [PrivateConstants.STEPPER_GET_CURRENT_POSITION, motor_id] + # self._send_command(command) + # + # def stepper_set_current_position(self, motor_id, position): + # """ + # Resets the current position of the motor, so that wherever the motor + # happens to be right now is considered to be the new 0 position. Useful + # for setting a zero position on a stepper after an initial hardware + # positioning move. + # + # Has the side effect of setting the current motor speed to 0. + # + # :param motor_id: 0 - 3 + # + # :param position: Position in steps. This is a 32 bit value + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_current_position: Invalid motor_id.') + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_SET_CURRENT_POSITION, motor_id] + # for value in position_bytes: + # command.append(value) + # self._send_command(command) + # + # def stepper_run_speed_to_position(self, motor_id, completion_callback=None): + # """ + # Runs the motor at the currently selected speed until the target position is + # reached. + # + # Does not implement accelerations. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: A motion complete ' + # 'callback must be ' + # 'specified.') + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN_SPEED_TO_POSITION, motor_id] + # self._send_command(command) + # + # def stepper_stop(self, motor_id): + # """ + # Sets a new target position that causes the stepper + # to stop as quickly as possible, using the current speed and + # acceleration parameters. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_stop: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_STOP, motor_id] + # self._send_command(command) + # + # def stepper_disable_outputs(self, motor_id): + # """ + # Disable motor pin outputs by setting them all LOW. + # + # Depending on the design of your electronics this may turn off + # the power to the motor coils, saving power. + # + # This is useful to support Arduino low power modes: disable the outputs + # during sleep and then re-enable with enableOutputs() before stepping + # again. + # + # If the enable Pin is defined, sets it to OUTPUT mode and clears + # the pin to disabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_disable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_DISABLE_OUTPUTS, motor_id] + # self._send_command(command) + # + # def stepper_enable_outputs(self, motor_id): + # """ + # Enable motor pin outputs by setting the motor pins to OUTPUT + # mode. + # + # If the enable Pin is defined, sets it to OUTPUT mode and sets + # the pin to enabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_enable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_ENABLE_OUTPUTS, motor_id] + # self._send_command(command) + # + # def stepper_set_min_pulse_width(self, motor_id, minimum_width): + # """ + # Sets the minimum pulse width allowed by the stepper driver. + # + # The minimum practical pulse width is approximately 20 microseconds. + # + # Times less than 20 microseconds will usually result in 20 microseconds or so. + # + # :param motor_id: 0 -3 + # + # :param minimum_width: A 16 bit unsigned value expressed in microseconds. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Invalid motor_id.') + # + # if not 0 < minimum_width <= 0xff: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Pulse width range = ' + # '0-0xffff.') + # + # width_msb = minimum_width >> 8 + # width_lsb = minimum_width & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MINIMUM_PULSE_WIDTH, motor_id, width_msb, + # width_lsb] + # self._send_command(command) + # + # def stepper_set_enable_pin(self, motor_id, pin=0xff): + # """ + # Sets the enable pin number for stepper drivers. + # 0xFF indicates unused (default). + # + # Otherwise, if a pin is set, the pin will be turned on when + # enableOutputs() is called and switched off when disableOutputs() + # is called. + # + # :param motor_id: 0 - 4 + # :param pin: 0-0xff + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Invalid motor_id.') + # + # if not 0 < pin <= 0xff: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Pulse width range = ' + # '0-0xff.') + # command = [PrivateConstants.STEPPER_SET_ENABLE_PIN, motor_id, pin] + # + # self._send_command(command) + # + # def stepper_set_3_pins_inverted(self, motor_id, direction=False, step=False, + # enable=False): + # """ + # Sets the inversion for stepper driver pins. + # + # :param motor_id: 0 - 3 + # + # :param direction: True=inverted or False + # + # :param step: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_3_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_3_PINS_INVERTED, motor_id, direction, + # step, enable] + # + # self._send_command(command) + # + # def stepper_set_4_pins_inverted(self, motor_id, pin1_invert=False, pin2_invert=False, + # pin3_invert=False, pin4_invert=False, enable=False): + # """ + # Sets the inversion for 2, 3 and 4 wire stepper pins + # + # :param motor_id: 0 - 3 + # + # :param pin1_invert: True=inverted or False + # + # :param pin2_invert: True=inverted or False + # + # :param pin3_invert: True=inverted or False + # + # :param pin4_invert: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_set_4_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_4_PINS_INVERTED, motor_id, pin1_invert, + # pin2_invert, pin3_invert, pin4_invert, enable] + # + # self._send_command(command) + # + # def stepper_is_running(self, motor_id, callback): + # """ + # Checks to see if the motor is currently running to a target. + # + # Callback return True if the speed is not zero or not at the target position. + # + # :param motor_id: 0-4 + # + # :param callback: required callback function to receive report + # + # :return: The current running state returned via the callback as a list: + # + # [REPORT_TYPE=18, motor_id, True or False for running state, time_stamp] + # """ + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError( + # 'stepper_is_running: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('stepper_is_running: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['is_running_callback'] = callback + # + # command = [PrivateConstants.STEPPER_IS_RUNNING, motor_id] + # self._send_command(command) + + def _set_pin_mode(self, pin_number, pin_state, differential=0, callback=None): + """ + A private method to set the various pin modes. + + :param pin_number: arduino pin number + + :param pin_state: INPUT/OUTPUT/ANALOG/PWM/PULLUP + For SERVO use: set_pin_mode_servo + For DHT use: set_pin_mode_dht + + :param differential: for analog inputs - threshold + value to be achieved for report to + be generated + + :param callback: A reference to a call back function to be + called when pin data value changes + + """ + if callback: + if pin_state == PrivateConstants.AT_INPUT: + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_INPUT_PULLUP: + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_ANALOG: + self.analog_callbacks[pin_number] = callback + else: + print('{} {}'.format('set_pin_mode: callback ignored for ' + 'pin state:', pin_state)) + + if pin_state == PrivateConstants.AT_INPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT, 1] + + elif pin_state == PrivateConstants.AT_INPUT_PULLUP: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT_PULLUP, 1] + + elif pin_state == PrivateConstants.AT_OUTPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_OUTPUT] + + elif pin_state == PrivateConstants.AT_ANALOG: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_ANALOG, + differential >> 8, differential & 0xff, 1] + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('Unknown pin state') + + if command: + self._send_command(command) + + def shutdown(self): + """ + This method attempts an orderly shutdown + If any exceptions are thrown, they are ignored. + """ + self.shutdown_flag = True + + self._stop_threads() + + try: + command = [PrivateConstants.STOP_ALL_REPORTS] + self._send_command(command) + time.sleep(.5) + + if self.hard_reset_on_shutdown: + self.r4_hard_reset() + + if self.transport_address: + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except Exception: + pass + else: + try: + self.serial_port.reset_input_buffer() + self.serial_port.reset_output_buffer() + + self.serial_port.close() + + except (RuntimeError, SerialException, OSError): + # ignore error on shutdown + pass + except Exception: + # raise RuntimeError('Shutdown failed - could not send stop streaming + # message') + pass + + def sonar_disable(self): + """ + Disable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_DISABLE] + self._send_command(command) + + def sonar_enable(self): + """ + Enable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_ENABLE] + self._send_command(command) + + def spi_cs_control(self, chip_select_pin, select): + """ + Control an SPI chip select line + :param chip_select_pin: pin connected to CS + + :param select: 0=select, 1=deselect + """ + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_cs_control: SPI interface is not enabled.') + + if chip_select_pin not in self.cs_pins_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_cs_control: chip select pin never enabled.') + command = [PrivateConstants.SPI_CS_CONTROL, chip_select_pin, select] + self._send_command(command) + + def spi_read_blocking(self, chip_select, register_selection, number_of_bytes_to_read, + call_back=None): + """ + Read the specified number of bytes from the specified SPI port and + call the callback function with the reported data. + + :param chip_select: chip select pin + + :param register_selection: Register to be selected for read. + + :param number_of_bytes_to_read: Number of bytes to read + + :param call_back: Required callback function to report spi data as a + result of read command + + + callback returns a data list: + [SPI_READ_REPORT, chip select pin, SPI Register, count of data bytes read, + data bytes, time-stamp] + + SPI_READ_REPORT = 13 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_read_blocking: SPI interface is not enabled.') + + if not call_back: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('spi_read_blocking: A Callback must be specified') + + self.spi_callback = call_back + + command = [PrivateConstants.SPI_READ_BLOCKING, chip_select, + number_of_bytes_to_read, + register_selection] + + self._send_command(command) + + def spi_set_format(self, clock_divisor, bit_order, data_mode): + """ + Configure how the SPI serializes and de-serializes data on the wire. + + See Arduino SPI reference materials for details. + + :param clock_divisor: 1 - 255 + + :param bit_order: + + LSBFIRST = 0 + + MSBFIRST = 1 (default) + + :param data_mode: + + SPI_MODE0 = 0x00 (default) + + SPI_MODE1 = 1 + + SPI_MODE2 = 2 + + SPI_MODE3 = 3 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_set_format: SPI interface is not enabled.') + + if not 0 < clock_divisor <= 255: + raise RuntimeError(f'spi_set_format: illegal clock divisor selected.') + if bit_order not in [0, 1]: + raise RuntimeError(f'spi_set_format: illegal bit_order selected.') + if data_mode not in [0, 1, 2, 3]: + raise RuntimeError(f'spi_set_format: illegal data_order selected.') + + command = [PrivateConstants.SPI_SET_FORMAT, clock_divisor, bit_order, + data_mode] + self._send_command(command) + + def spi_write_blocking(self, chip_select, bytes_to_write): + """ + Write a list of bytes to the SPI device. + + :param chip_select: chip select pin + + :param bytes_to_write: A list of bytes to write. This must + be in the form of a list. + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError(f'spi_write_blocking: SPI interface is not enabled.') + + if type(bytes_to_write) is not list: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('spi_write_blocking: bytes_to_write must be a list.') + + command = [PrivateConstants.SPI_WRITE_BLOCKING, chip_select, len(bytes_to_write)] + + for data in bytes_to_write: + command.append(data) + + self._send_command(command) + + # def set_pin_mode_one_wire(self, pin): + # """ + # Initialize the one wire serial bus. + # + # :param pin: Data pin connected to the OneWire device + # """ + # if self.reported_features & PrivateConstants.ONEWIRE_FEATURE: + # self.onewire_enabled = True + # command = [PrivateConstants.ONE_WIRE_INIT, pin] + # self._send_command(command) + # else: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'The OneWire feature is disabled in the server.') + # + # def onewire_reset(self, callback=None): + # """ + # Reset the onewire device + # + # :param callback: required function to report reset result + # + # callback returns a list: + # [ReportType = 14, Report Subtype = 25, reset result byte, + # timestamp] + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_reset: OneWire interface is not enabled.') + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_reset: A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_RESET] + # self._send_command(command) + # + # def onewire_select(self, device_address): + # """ + # Select a device based on its address + # :param device_address: A bytearray of 8 bytes + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_select: OneWire interface is not enabled.') + # + # if type(device_address) is not list: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # + # if len(device_address) != 8: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # command = [PrivateConstants.ONE_WIRE_SELECT] + # for data in device_address: + # command.append(data) + # self._send_command(command) + # + # def onewire_skip(self): + # """ + # Skip the device selection. This only works if you have a + # single device, but you can avoid searching and use this to + # immediately access your device. + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_skip: OneWire interface is not enabled.') + # + # command = [PrivateConstants.ONE_WIRE_SKIP] + # self._send_command(command) + # + # def onewire_write(self, data, power=0): + # """ + # Write a byte to the onewire device. If 'power' is one + # then the wire is held high at the end for + # parasitically powered devices. You + # are responsible for eventually de-powering it by calling + # another read or write. + # + # :param data: byte to write. + # :param power: power control (see above) + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_write: OneWire interface is not enabled.') + # if 0 < data < 255: + # command = [PrivateConstants.ONE_WIRE_WRITE, data, power] + # self._send_command(command) + # else: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_write: Data must be no larger than 255') + # + # def onewire_read(self, callback=None): + # """ + # Read a byte from the onewire device + # :param callback: required function to report onewire data as a + # result of read command + # + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_READ=29, data byte, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_read: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_READ] + # self._send_command(command) + # time.sleep(.2) + # + # def onewire_reset_search(self): + # """ + # Begin a new search. The next use of search will begin at the first device + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_reset_search: OneWire interface is not ' + # f'enabled.') + # else: + # command = [PrivateConstants.ONE_WIRE_RESET_SEARCH] + # self._send_command(command) + # + # def onewire_search(self, callback=None): + # """ + # Search for the next device. The device address will returned in the callback. + # If a device is found, the 8 byte address is contained in the callback. + # If no more devices are found, the address returned contains all elements set + # to 0xff. + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_SEARCH=31, 8 byte address, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_search: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_SEARCH] + # self._send_command(command) + # + # def onewire_crc8(self, address_list, callback=None): + # """ + # Compute a CRC check on an array of data. + # :param address_list: + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_CRC8=32, CRC, time-stamp] + # + # ONEWIRE_REPORT = 14 + # + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError(f'onewire_crc8: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_crc8 A Callback must be specified') + # + # if type(address_list) is not list: + # if self.shutdown_on_exception: + # self.shutdown() + # raise RuntimeError('onewire_crc8: address list must be a list.') + # + # self.onewire_callback = callback + # + # address_length = len(address_list) + # + # command = [PrivateConstants.ONE_WIRE_CRC8, address_length - 1] + # + # for data in address_list: + # command.append(data) + # + # self._send_command(command) + + def r4_hard_reset(self): + """ + Place the r4 into hard reset + """ + command = [PrivateConstants.RESET, 1] + self._send_command(command) + time.sleep(.5) + command = [PrivateConstants.BOARD_HARD_RESET, 1] + self._send_command(command) + + def enable_scroll_message(self, message, scroll_speed=50): + """ + + :param message: Message with maximum length of 25 + :param scroll_speed: in milliseconds (maximum of 255) + """ + if len(message) > 25: + raise RuntimeError("Scroll message size is maximum of 25 characters.") + + if scroll_speed > 255: + raise RuntimeError("Scroll speed maximum of 255 milliseconds.") + + message = message.encode() + command = [PrivateConstants.SCROLL_MESSAGE_ON, len(message), scroll_speed] + for x in message: + command.append(x) + self._send_command(command) + + def disable_scroll_message(self): + """ + Turn off a scrolling message + """ + + command = [PrivateConstants.SCROLL_MESSAGE_OFF] + self._send_command(command) + + ''' + report message handlers + ''' + + def _analog_message(self, data): + """ + This is a private message handler method. + It is a message handler for analog messages. + + :param data: message data + + """ + pin = data[0] + value = (data[1] << 8) + data[2] + # set the current value in the pin structure + time_stamp = time.time() + # self.digital_pins[pin].event_time = time_stamp + if self.analog_callbacks[pin]: + message = [PrivateConstants.ANALOG_REPORT, pin, value, time_stamp] + try: + self.analog_callbacks[pin](message) + except KeyError: + pass + + def _dht_report(self, data): + """ + This is the dht report handler method. + + :param data: data[0] = report error return + No Errors = 0 + + Checksum Error = 1 + + Timeout Error = 2 + + Invalid Value = 999 + + data[1] = pin number + + data[2] = dht type 11 or 22 + + data[3] = humidity positivity flag + + data[4] = temperature positivity value + + data[5] = humidity integer + + data[6] = humidity fractional value + + data[7] = temperature integer + + data[8] = temperature fractional value + + + """ + if data[0]: # DHT_ERROR + # error report + # data[0] = report sub type, data[1] = pin, data[2] = error message + if self.dht_callbacks[data[1]]: + # Callback 0=DHT REPORT, DHT_ERROR, PIN, Time + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + time.time()] + try: + self.dht_callbacks[data[1]](message) + except KeyError: + pass + else: + # got valid data DHT_DATA + f_humidity = float(data[5] + data[6] / 100) + if data[3]: + f_humidity *= -1.0 + f_temperature = float(data[7] + data[8] / 100) + if data[4]: + f_temperature *= -1.0 + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + f_humidity, f_temperature, time.time()] + + try: + self.dht_callbacks[data[1]](message) + except KeyError: + pass + + def _digital_message(self, data): + """ + This is a private message handler method. + It is a message handler for Digital Messages. + + :param data: digital message + + """ + pin = data[0] + value = data[1] + + time_stamp = time.time() + if self.digital_callbacks[pin]: + message = [PrivateConstants.DIGITAL_REPORT, pin, value, time_stamp] + self.digital_callbacks[pin](message) + + def _firmware_message(self, data): + """ + Telemetrix4Arduino firmware version message + + :param data: data[0] = major number, data[1] = minor number. + + data[2] = patch number + """ + + self.firmware_version = [data[0], data[1], data[2]] + + def _i2c_read_report(self, data): + """ + Execute callback for i2c reads. + + :param data: [I2C_READ_REPORT, i2c_port, number of bytes read, address, register, bytes read..., time-stamp] + """ + + # we receive [# data bytes, address, register, data bytes] + # number of bytes of data returned + + # data[0] = number of bytes + # data[1] = i2c_port + # data[2] = number of bytes returned + # data[3] = address + # data[4] = register + # data[5] ... all the data bytes + + cb_list = [PrivateConstants.I2C_READ_REPORT, data[0], data[1]] + data[2:] + cb_list.append(time.time()) + + if cb_list[1]: + self.i2c_callback2(cb_list) + else: + self.i2c_callback(cb_list) + + def _i2c_too_few(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'i2c too few bytes received from i2c port {data[0]} i2c address {data[1]}') + + def _i2c_too_many(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'i2c too many bytes received from i2c port {data[0]} i2c address {data[1]}') + + def _i_am_here(self, data): + """ + Reply to are_u_there message + :param data: arduino id + """ + self.reported_arduino_id = data[0] + + def _spi_report(self, report): + + cb_list = [PrivateConstants.SPI_REPORT, report[0]] + report[1:] + + cb_list.append(time.time()) + + self.spi_callback(cb_list) + + def _onewire_report(self, report): + cb_list = [PrivateConstants.ONE_WIRE_REPORT, report[0]] + report[1:] + cb_list.append(time.time()) + self.onewire_callback(cb_list) + + def _report_debug_data(self, data): + """ + Print debug data sent from Arduino + :param data: data[0] is a byte followed by 2 + bytes that comprise an integer + :return: + """ + value = (data[1] << 8) + data[2] + print(f'DEBUG ID: {data[0]} Value: {value}') + + def _report_loop_data(self, data): + """ + Print data that was looped back + :param data: byte of loop back data + :return: + """ + if self.loop_back_callback: + self.loop_back_callback(data) + + def _send_command(self, command): + """ + This is a private utility method. + + + :param command: command data in the form of a list + + """ + # the length of the list is added at the head + command.insert(0, len(command)) + send_message = bytes(command) + + if self.serial_port: + try: + self.serial_port.write(send_message) + except SerialException: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError('write fail in _send_command') + elif self.transport_address: + self.sock.sendall(send_message) + else: + raise RuntimeError('No serial port or ip address set.') + + def _servo_unavailable(self, report): + """ + Message if no servos are available for use. + :param report: pin number + """ + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + f'Servo Attach For Pin {report[0]} Failed: No Available Servos') + + def _sonar_distance_report(self, report): + """ + + :param report: data[0] = trigger pin, data[1] and data[2] = distance + + callback report format: [PrivateConstants.SONAR_DISTANCE, trigger_pin, distance_value, time_stamp] + """ + + # get callback from pin number + cb = self.sonar_callbacks[report[0]] + + # build report data + cb_list = [PrivateConstants.SONAR_DISTANCE, report[0], + ((report[1] << 8) + report[2]), time.time()] + + cb(cb_list) + + def _stepper_distance_to_go_report(self, report): + return # for now + # """ + # Report stepper distance to go. + # + # :param report: data[0] = motor_id, data[1] = steps MSB, data[2] = steps byte 1, + # data[3] = steps bytes 2, data[4] = steps LSB + # + # callback report format: [PrivateConstants.STEPPER_DISTANCE_TO_GO, motor_id + # steps, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['distance_to_go_callback'] + # + # # isolate the steps bytes and covert list to bytes + # steps = bytes(report[1:]) + # + # # get value from steps + # num_steps = int.from_bytes(steps, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_DISTANCE_TO_GO, report[0], num_steps, + # time.time()] + # + # cb(cb_list) + # + def _stepper_target_position_report(self, report): + return # for now + # """ + # Report stepper target position to go. + # + # :param report: data[0] = motor_id, data[1] = target position MSB, + # data[2] = target position byte MSB+1 + # data[3] = target position byte MSB+2 + # data[4] = target position LSB + # + # callback report format: [PrivateConstants.STEPPER_TARGET_POSITION, motor_id + # target_position, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['target_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # target = bytes(report[1:]) + # + # # get value from steps + # target_position = int.from_bytes(target, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_TARGET_POSITION, report[0], target_position, + # time.time()] + # + # cb(cb_list) + # + def _stepper_current_position_report(self, report): + return # for now + # """ + # Report stepper current position. + # + # :param report: data[0] = motor_id, data[1] = current position MSB, + # data[2] = current position byte MSB+1 + # data[3] = current position byte MSB+2 + # data[4] = current position LSB + # + # callback report format: [PrivateConstants.STEPPER_CURRENT_POSITION, motor_id + # current_position, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['current_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # position = bytes(report[1:]) + # + # # get value from steps + # current_position = int.from_bytes(position, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_CURRENT_POSITION, report[0], current_position, + # time.time()] + # + # cb(cb_list) + # + def _stepper_is_running_report(self, report): + return # for now + # """ + # Report if the motor is currently running + # + # :param report: data[0] = motor_id, True if motor is running or False if it is not. + # + # callback report format: [18, motor_id, + # running_state, time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['is_running_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUNNING_REPORT, report[0], time.time()] + # + # cb(cb_list) + # + def _stepper_run_complete_report(self, report): + return # for now + # """ + # The motor completed it motion + # + # :param report: data[0] = motor_id + # + # callback report format: [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, motor_id, + # time_stamp] + # """ + # + # # get callback + # cb = self.stepper_info_list[report[0]]['motion_complete_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, report[0], + # time.time()] + # + # cb(cb_list) + + def _features_report(self, report): + self.reported_features = report[0] + + def _run_threads(self): + self.run_event.set() + + def _is_running(self): + return self.run_event.is_set() + + def _stop_threads(self): + self.run_event.clear() + + def _reporter(self): + """ + This is the reporter thread. It continuously pulls data from + the deque. When a full message is detected, that message is + processed. + """ + self.run_event.wait() + + while self._is_running() and not self.shutdown_flag: + if len(self.the_deque): + # response_data will be populated with the received data for the report + response_data = [] + packet_length = self.the_deque.popleft() + # print(f'packet_length {packet_length}') + if packet_length: + # get all the data for the report and place it into response_data + for i in range(packet_length): + while not len(self.the_deque): + time.sleep(self.sleep_tune) + data = self.the_deque.popleft() + response_data.append(data) + + # print(f'response_data {response_data}') + + # get the report type and look up its dispatch method + # here we pop the report type off of response_data + report_type = response_data.pop(0) + # print(f' reported type {report_type}') + + # retrieve the report handler from the dispatch table + dispatch_entry = self.report_dispatch.get(report_type) + + # if there is additional data for the report, + # it will be contained in response_data + # noinspection PyArgumentList + dispatch_entry(response_data) + continue + else: + if self.shutdown_on_exception: + self.shutdown() + raise RuntimeError( + 'A report with a packet length of zero was received.') + else: + time.sleep(self.sleep_tune) + + def _serial_receiver(self): + """ + Thread to continuously check for incoming data. + When a byte comes in, place it onto the deque. + """ + self.run_event.wait() + + # Don't start this thread if using a tcp/ip transport + if self.transport_address: + return + + while self._is_running() and not self.shutdown_flag: + # we can get an OSError: [Errno9] Bad file descriptor when shutting down + # just ignore it + try: + if self.serial_port.inWaiting(): + c = self.serial_port.read() + self.the_deque.append(ord(c)) + # print(ord(c)) + else: + time.sleep(self.sleep_tune) + # continue + except OSError: + pass + + def _tcp_receiver(self): + """ + Thread to continuously check for incoming data. + When a byte comes in, place it onto the deque. + """ + self.run_event.wait() + + # Start this thread only if transport_address is set + + if self.transport_address: + + while self._is_running() and not self.shutdown_flag: + try: + payload = self.sock.recv(1) + self.the_deque.append(ord(payload)) + except Exception: + pass + else: + return diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/__init__.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/private_constants.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/private_constants.py new file mode 100644 index 0000000..b7fdbf5 --- /dev/null +++ b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/private_constants.py @@ -0,0 +1,155 @@ +""" + Copyright (c) 2015-2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + +class PrivateConstants: + """ + This class contains a set of constants for telemetrix internal use . + """ + + # transport types + WIFI_TRANSPORT = 0 + SERIAL_TRANSPORT = 1 + BLE_TRANSPORT = 2 + + # commands + # send a loop back request - for debugging communications + LOOP_COMMAND = 0 + SET_PIN_MODE = 1 # set a pin to INPUT/OUTPUT/PWM/etc + DIGITAL_WRITE = 2 # set a single digital pin value instead of entire port + ANALOG_WRITE = 3 + MODIFY_REPORTING = 4 + GET_FIRMWARE_VERSION = 5 + ARE_U_THERE = 6 # Arduino ID query for auto-detect of telemetrix connected boards + SERVO_ATTACH = 7 + SERVO_WRITE = 8 + SERVO_DETACH = 9 + I2C_BEGIN = 10 + I2C_READ = 11 + I2C_WRITE = 12 + SONAR_NEW = 13 + DHT_NEW = 14 + STOP_ALL_REPORTS = 15 + SET_ANALOG_SCANNING_INTERVAL = 16 + ENABLE_ALL_REPORTS = 17 + RESET = 18 + SPI_INIT = 19 + SPI_WRITE_BLOCKING = 20 + SPI_READ_BLOCKING = 21 + SPI_SET_FORMAT = 22 + SPI_CS_CONTROL = 23 + ONE_WIRE_INIT = 24 + ONE_WIRE_RESET = 25 + ONE_WIRE_SELECT = 26 + ONE_WIRE_SKIP = 27 + ONE_WIRE_WRITE = 28 + ONE_WIRE_READ = 29 + ONE_WIRE_RESET_SEARCH = 30 + ONE_WIRE_SEARCH = 31 + ONE_WIRE_CRC8 = 32 + SET_PIN_MODE_STEPPER = 33 + STEPPER_MOVE_TO = 34 + STEPPER_MOVE = 35 + STEPPER_RUN = 36 + STEPPER_RUN_SPEED = 37 + STEPPER_SET_MAX_SPEED = 38 + STEPPER_SET_ACCELERATION = 39 + STEPPER_SET_SPEED = 40 + STEPPER_SET_CURRENT_POSITION = 41 + STEPPER_RUN_SPEED_TO_POSITION = 42 + STEPPER_STOP = 43 + STEPPER_DISABLE_OUTPUTS = 44 + STEPPER_ENABLE_OUTPUTS = 45 + STEPPER_SET_MINIMUM_PULSE_WIDTH = 46 + STEPPER_SET_ENABLE_PIN = 47 + STEPPER_SET_3_PINS_INVERTED = 48 + STEPPER_SET_4_PINS_INVERTED = 49 + STEPPER_IS_RUNNING = 50 + STEPPER_GET_CURRENT_POSITION = 51 + STEPPER_GET_DISTANCE_TO_GO = 52 + STEPPER_GET_TARGET_POSITION = 53 + GET_FEATURES = 54 + SONAR_DISABLE = 55 + SONAR_ENABLE = 56 + BOARD_HARD_RESET = 57 + SCROLL_MESSAGE_ON = 58 + SCROLL_MESSAGE_OFF = 59 + + # reports + # debug data from Arduino + DIGITAL_REPORT = DIGITAL_WRITE + ANALOG_REPORT = ANALOG_WRITE + FIRMWARE_REPORT = GET_FIRMWARE_VERSION + I_AM_HERE_REPORT = ARE_U_THERE + SERVO_UNAVAILABLE = SERVO_ATTACH + I2C_TOO_FEW_BYTES_RCVD = 8 + I2C_TOO_MANY_BYTES_RCVD = 9 + I2C_READ_REPORT = 10 + SONAR_DISTANCE = 11 + DHT_REPORT = 12 + SPI_REPORT = 13 + ONE_WIRE_REPORT = 14 + STEPPER_DISTANCE_TO_GO = 15 + STEPPER_TARGET_POSITION = 16 + STEPPER_CURRENT_POSITION = 17 + STEPPER_RUNNING_REPORT = 18 + STEPPER_RUN_COMPLETE_REPORT = 19 + FEATURES = 20 + DEBUG_PRINT = 99 + + TELEMETRIX_VERSION = "1.00" + + # reporting control + REPORTING_DISABLE_ALL = 0 + REPORTING_ANALOG_ENABLE = 1 + REPORTING_DIGITAL_ENABLE = 2 + REPORTING_ANALOG_DISABLE = 3 + REPORTING_DIGITAL_DISABLE = 4 + + # Pin mode definitions + AT_INPUT = 0 + AT_OUTPUT = 1 + AT_INPUT_PULLUP = 2 + AT_ANALOG = 3 + AT_SERVO = 4 + AT_SONAR = 5 + AT_DHT = 6 + AT_MODE_NOT_SET = 255 + + # maximum number of digital pins supported + NUMBER_OF_DIGITAL_PINS = 100 + + # maximum number of analog pins supported + NUMBER_OF_ANALOG_PINS = 20 + + # maximum number of sonars allowed + MAX_SONARS = 6 + + # maximum number of DHT devices allowed + MAX_DHTS = 6 + + # DHT Report sub-types + DHT_DATA = 0 + DHT_ERROR = 1 + + # feature masks + ONEWIRE_FEATURE = 0x01 + DHT_FEATURE = 0x02 + STEPPERS_FEATURE = 0x04 + SPI_FEATURE = 0x08 + SERVO_FEATURE = 0x10 + SONAR_FEATURE = 0x20 diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_ble.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_ble.py new file mode 100644 index 0000000..56529dc --- /dev/null +++ b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_ble.py @@ -0,0 +1,72 @@ + +""" + Copyright (c) 2020-2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + +import asyncio +import sys +import bleak + +from bleak import BleakClient, BleakScanner +from bleak.backends.characteristic import BleakGATTCharacteristic +from bleak.backends.device import BLEDevice +from bleak.backends.scanner import AdvertisementData + +UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + + +class TelemetrixAioBle: + """ + This class encapsulates management of a BLE UART connection that communicates + with an Arduino UNO R4 WIFI + """ + def __init__(self, ble_device_name, receive_notification_callback): + self.ble_device_name = ble_device_name + self.receive_notification_callback = receive_notification_callback + self.ble_device = None + self.bleak_client = None + self.nus = None + self.rx_char = None + self.tx_char = None + + async def connect(self): + """ + This method connects to a device matching the ble_device_name + + :return: + """ + print(f'Scanning for BLE device {self.ble_device_name}. Please wait...') + + self.ble_device = await BleakScanner.find_device_by_name(self.ble_device_name) + if self.ble_device is None: + raise RuntimeError('Did not find the BLE device. Please check name.') + print(f'Found {self.ble_device_name} address: {self.ble_device.address}') + self.bleak_client = BleakClient(self.ble_device.address) + await self.bleak_client.connect() + await self.bleak_client.start_notify(UART_TX_CHAR_UUID, + self.receive_notification_callback) + + async def write(self, data): + """ + This method writes data to the IP device + :param data: + + :return: None + """ + await self.bleak_client.write_gatt_char(UART_RX_CHAR_UUID, data) diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_serial.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_serial.py new file mode 100644 index 0000000..c3c8ce0 --- /dev/null +++ b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_serial.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +""" + Copyright (c) 2015-2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import serial +import time + +LF = 0x0a + + +# noinspection PyStatementEffect,PyUnresolvedReferences,PyUnresolvedReferences +class TelemetrixAioSerial: + """ + This class encapsulates management of the serial port that communicates + with the Arduino Firmata + It provides a 'futures' interface to make Pyserial compatible with asyncio + """ + + def __init__(self, com_port='/dev/ttyACM0', baud_rate=115200, sleep_tune=.0001, + telemetrix_aio_instance=None, close_loop_on_error=True): + + """ + This is the constructor for the aio serial handler + + :param com_port: Com port designator + + :param baud_rate: UART baud rate + + :param telemetrix_aio_instance: reference to caller + + :return: None + """ + # print('Initializing Arduino - Please wait...', end=" ") + sys.stdout.flush() + self.my_serial = serial.Serial(com_port, baud_rate, timeout=1, + writeTimeout=1) + + self.com_port = com_port + self.sleep_tune = sleep_tune + self.telemetrix_aio_instance = telemetrix_aio_instance + self.close_loop_on_error = close_loop_on_error + + # used by read_until + self.start_time = None + + async def get_serial(self): + """ + This method returns a reference to the serial port in case the + user wants to call pyserial methods directly + + :return: pyserial instance + """ + return self.my_serial + + async def write(self, data): + """ + This is an asyncio adapted version of pyserial write. It provides a + non-blocking write and returns the number of bytes written upon + completion + + :param data: Data to be written + :return: Number of bytes written + """ + # the secret sauce - it is in your future + future = asyncio.Future() + result = None + try: + # result = self.my_serial.write(bytes([ord(data)])) + result = self.my_serial.write(bytes(data)) + + except serial.SerialException: + # noinspection PyBroadException + loop = None + await self.close() + future.cancel() + if self.close_loop_on_error: + loop = asyncio.get_event_loop() + loop.stop() + + if self.telemetrix_aio_instance.the_task: + self.telemetrix_aio_instance.the_task.cancel() + await asyncio.sleep(1) + if self.close_loop_on_error: + loop.close() + + if result: + future.set_result(result) + while True: + if not future.done(): + # spin our asyncio wheels until future completes + await asyncio.sleep(self.sleep_tune) + + else: + return future.result() + + async def read(self, size=1): + """ + This is an asyncio adapted version of pyserial read + that provides non-blocking read. + + :return: One character + """ + + # create an asyncio Future + future = asyncio.Future() + + # create a flag to indicate when data becomes available + data_available = False + + # wait for a character to become available and read from + # the serial port + while True: + if not data_available: + # test to see if a character is waiting to be read. + # if not, relinquish control back to the event loop through the + # short sleep + if not self.my_serial.in_waiting: + await asyncio.sleep(self.sleep_tune*2) + + # data is available. + # set the flag to true so that the future can "wait" until the + # read is completed. + else: + data_available = True + data = self.my_serial.read(size) + # set future result to make the character available + if size == 1: + future.set_result(ord(data)) + else: + future.set_result(list(data)) + else: + # wait for the future to complete + if not future.done(): + await asyncio.sleep(self.sleep_tune) + else: + # future is done, so return the character + return future.result() + + async def read_until(self, expected=LF, size=None, timeout=1): + """ + This is an asyncio adapted version of pyserial read + that provides non-blocking read. + + :return: Data delimited by expected + """ + + expected = str(expected).encode() + # create an asyncio Future + future = asyncio.Future() + + # create a flag to indicate when data becomes available + data_available = False + + if timeout: + self.start_time = time.time() + + # wait for a character to become available and read from + # the serial port + while True: + if not data_available: + # test to see if a character is waiting to be read. + # if not, relinquish control back to the event loop through the + # short sleep + if not self.my_serial.in_waiting: + if timeout: + elapsed_time = time.time() - self.start_time + if elapsed_time > timeout: + return None + await asyncio.sleep(self.sleep_tune) + # data is available. + # set the flag to true so that the future can "wait" until the + # read is completed. + else: + data_available = True + data = self.my_serial.read_until(expected, size) + # set future result to make the character available + return_value = list(data) + future.set_result(return_value) + else: + # wait for the future to complete + if not future.done(): + await asyncio.sleep(self.sleep_tune) + else: + # future is done, so return the character + return future.result() + + async def reset_input_buffer(self): + """ + Reset the input buffer + """ + self.my_serial.reset_input_buffer() + + async def close(self): + """ + Close the serial port + """ + if self.my_serial: + self.my_serial.close() diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_socket.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_socket.py new file mode 100644 index 0000000..ac11ce1 --- /dev/null +++ b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_aio_socket.py @@ -0,0 +1,80 @@ + +""" + Copyright (c) 2020-2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + +import asyncio +import sys + + +# noinspection PyStatementEffect,PyUnresolvedReferences,PyUnresolvedReferences +class TelemetrixAioSocket: + """ + This class encapsulates management of a tcp/ip connection that communicates + with an Arduino UNO R4 WIFI + """ + def __init__(self, ip_address, ip_port, loop): + self.ip_address = ip_address + self.ip_port = ip_port + self.loop = loop + self.reader = None + self.writer = None + + async def start(self): + """ + This method opens an IP connection on the IP device + + :return: None + """ + try: + self.reader, self.writer = await asyncio.open_connection( + self.ip_address, self.ip_port) + print(f'Successfully connected to: {self.ip_address}:{self.ip_port}') + except OSError: + print("Can't open connection to " + self.ip_address) + sys.exit(0) + + async def write(self, data): + """ + This method writes data to the IP device + :param data: + + :return: None + """ + # we need to convert data formats, + # so all of the below. + output_list = [] + + # create an array of integers from the data to be sent + for x in data: + # output_list.append((ord(x))) + output_list.append(x) + + # now convert the integer list to a bytearray + to_wifi = bytearray(output_list) + self.writer.write(to_wifi) + await self.writer.drain() + + async def read(self, num_bytes=1): + """ + This method reads one byte of data from IP device + + :return: Next byte + """ + buffer = await self.reader.read(num_bytes) + # await asyncio.sleep(num_bytes * 0.02) + return buffer diff --git a/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_uno_r4_wifi_aio.py b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_uno_r4_wifi_aio.py new file mode 100644 index 0000000..0cab0ca --- /dev/null +++ b/telemetrix_uno_r4/wifi/telemetrix_uno_r4_wifi_aio/telemetrix_uno_r4_wifi_aio.py @@ -0,0 +1,2629 @@ +""" + Copyright (c) 2023 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library 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 AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +import asyncio +import sys +import time + +# noinspection PyPackageRequirementscd +from serial.serialutil import SerialException +# noinspection PyPackageRequirements +from serial.tools import list_ports + +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio.private_constants import ( + PrivateConstants) +# noinspection PyUnresolvedReferences +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio.telemetrix_aio_serial import ( + TelemetrixAioSerial) +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio.telemetrix_aio_socket import ( + TelemetrixAioSocket) +from telemetrix_uno_r4.wifi.telemetrix_uno_r4_wifi_aio.telemetrix_aio_ble import ( + TelemetrixAioBle) + + +# noinspection GrazieInspection,PyArgumentList,PyMethodMayBeStatic,PyRedundantParentheses +class TelemetrixUnoR4WiFiAio: + """ + This class exposes and implements the TelemetrixUnoR4WifiAio API. + It includes the public API methods as well as + a set of private methods. This is an asyncio API. + + """ + + # noinspection PyPep8,PyPep8 + def __init__(self, com_port=None, + arduino_instance_id=1, arduino_wait=1, + sleep_tune=0.0001, autostart=True, + loop=None, shutdown_on_exception=True, + close_loop_on_shutdown=True, hard_reset_on_shutdown=True, + transport_address=None, ip_port=31336, transport_type=0, + ble_device_name='Telemetrix4UnoR4 BLE'): + + """ + If you have a single Arduino connected to your computer, + then you may accept all the default values. + + Otherwise, specify a unique arduino_instance id for each board in use. + + :param com_port: e.g. COM3 or /dev/ttyACM0. + + :param arduino_instance_id: Must match value in the Telemetrix4Arduino sketch + + :param arduino_wait: Amount of time to wait for an Arduino to + fully reset itself. + + :param sleep_tune: A tuning parameter (typically not changed by user) + + :param autostart: If you wish to call the start method within + your application, then set this to False. + + :param loop: optional user provided event loop + + :param shutdown_on_exception: call shutdown before raising + a RunTimeError exception, or + receiving a KeyboardInterrupt exception + + :param close_loop_on_shutdown: stop and close the event loop loop + when a shutdown is called or a serial + error occurs + + :param hard_reset_on_shutdown: reset the board on shutdown + + :param transport_address: ip address of tcp/ip connected device. + + :param ip_port: ip port of tcp/ip connected device + + :param transport_type: 0 = WiFi + 1 = SerialUSB + 2 = BLE + + :param ble_device_name: name of Arduino UNO R4 WIFI BLE device. + It must match that of Telemetrix4UnoR4BLE.ino + + """ + # check to make sure that Python interpreter is version 3.8.3 or greater + python_version = sys.version_info + if python_version[0] >= 3: + if python_version[1] >= 8: + if python_version[2] >= 3: + pass + else: + raise RuntimeError("ERROR: Python 3.7 or greater is " + "required for use of this program.") + + # save input parameters + self.com_port = com_port + self.arduino_instance_id = arduino_instance_id + self.arduino_wait = arduino_wait + self.sleep_tune = sleep_tune + self.autostart = autostart + self.hard_reset_on_shutdown = hard_reset_on_shutdown + + self.transport_address = transport_address + self.ip_port = ip_port + if transport_type not in [0, 1, 2]: + raise RuntimeError('Invalid transport type') + self.transport_type = transport_type + self.firmware_version = None + # if tcp, this variable is set to the connected socket + self.sock = None + + self.ble_device_name = ble_device_name + + # instance of telemetrix_aio_ble + self.ble_instance = None + + # set the event loop + if loop is None: + self.loop = asyncio.get_event_loop() + else: + self.loop = loop + + self.shutdown_on_exception = shutdown_on_exception + self.close_loop_on_shutdown = close_loop_on_shutdown + + # dictionaries to store the callbacks for each pin + self.analog_callbacks = {} + + self.digital_callbacks = {} + + self.i2c_callback = None + self.i2c_callback2 = None + + self.i2c_1_active = False + self.i2c_2_active = False + + self.spi_callback = None + + self.onewire_callback = None + + # debug loopback callback method + self.loop_back_callback = None + + # the trigger pin will be the key to retrieve + # the callback for a specific HC-SR04 + self.sonar_callbacks = {} + + self.sonar_count = 0 + + self.dht_callbacks = {} + + self.dht_count = 0 + + # serial port in use + self.serial_port = None + + # generic asyncio task holder + self.the_task = None + + # flag to indicate we are in shutdown mode + self.shutdown_flag = False + + self.report_dispatch = {} + + # reported features + self.reported_features = 0 + + # To add a command to the command dispatch table, append here. + self.report_dispatch.update( + {PrivateConstants.LOOP_COMMAND: self._report_loop_data}) + self.report_dispatch.update( + {PrivateConstants.DEBUG_PRINT: self._report_debug_data}) + self.report_dispatch.update( + {PrivateConstants.DIGITAL_REPORT: self._digital_message}) + self.report_dispatch.update( + {PrivateConstants.ANALOG_REPORT: self._analog_message}) + self.report_dispatch.update( + {PrivateConstants.SERVO_UNAVAILABLE: self._servo_unavailable}) + self.report_dispatch.update( + {PrivateConstants.I2C_READ_REPORT: self._i2c_read_report}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_FEW_BYTES_RCVD: self._i2c_too_few}) + self.report_dispatch.update( + {PrivateConstants.I2C_TOO_MANY_BYTES_RCVD: self._i2c_too_many}) + self.report_dispatch.update( + {PrivateConstants.SONAR_DISTANCE: self._sonar_distance_report}) + self.report_dispatch.update({PrivateConstants.DHT_REPORT: self._dht_report}) + self.report_dispatch.update( + {PrivateConstants.SPI_REPORT: self._spi_report}) + self.report_dispatch.update( + {PrivateConstants.ONE_WIRE_REPORT: self._onewire_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_CURRENT_POSITION: + self._stepper_current_position_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUNNING_REPORT: + self._stepper_is_running_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_RUN_COMPLETE_REPORT: + self._stepper_run_complete_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_DISTANCE_TO_GO: + self._stepper_distance_to_go_report}) + self.report_dispatch.update( + {PrivateConstants.STEPPER_TARGET_POSITION: + self._stepper_target_position_report}) + self.report_dispatch.update( + {PrivateConstants.FEATURES: + self._features_report}) + + # dictionaries to store the callbacks for each pin + self.analog_callbacks = {} + + self.digital_callbacks = {} + + self.i2c_callback = None + self.i2c_callback2 = None + + self.i2c_1_active = False + self.i2c_2_active = False + + self.spi_callback = None + + self.onewire_callback = None + + self.cs_pins_enabled = [] + + # flag to indicate if spi is initialized + self.spi_enabled = False + + # flag to indicate if onewire is initialized + self.onewire_enabled = False + + # the trigger pin will be the key to retrieve + # the callback for a specific HC-SR04 + self.sonar_callbacks = {} + + self.sonar_count = 0 + + self.dht_callbacks = {} + + # # stepper motor variables + # + # # updated when a new motor is added + # self.next_stepper_assigned = 0 + # + # # valid list of stepper motor interface types + # self.valid_stepper_interfaces = [1, 2, 3, 4, 6, 8] + # + # # maximum number of steppers supported + # self.max_number_of_steppers = 4 + # + # # number of steppers created - not to exceed the maximum + # self.number_of_steppers = 0 + # + # # dictionary to hold stepper motor information + # self.stepper_info = {'instance': False, 'is_running': None, + # 'maximum_speed': 1, 'speed': 0, 'acceleration': 0, + # 'distance_to_go_callback': None, + # 'target_position_callback': None, + # 'current_position_callback': None, + # 'is_running_callback': None, + # 'motion_complete_callback': None, + # 'acceleration_callback': None} + # + # # build a list of stepper motor info items + # self.stepper_info_list = [] + # # a list of dictionaries to hold stepper information + # for motor in range(self.max_number_of_steppers): + # self.stepper_info_list.append(self.stepper_info) + + print(f'telemetrix_uno_r4_wifi_aio Version:' + f' {PrivateConstants.TELEMETRIX_VERSION}') + print(f'Copyright (c) 2023 Alan Yorinks All rights reserved.\n') + + if autostart: + self.loop.run_until_complete(self.start_aio()) + + async def start_aio(self): + """ + This method may be called directly, if the autostart + parameter in __init__ is set to false. + + This method instantiates the serial interface and then performs auto pin + discovery if using a serial interface, or creates and connects to + a TCP/IP enabled device running StandardFirmataWiFi. + + Use this method if you wish to start TelemetrixAIO manually from + an asyncio function. + """ + + if self.transport_type == PrivateConstants.SERIAL_TRANSPORT: + if not self.com_port: + # user did not specify a com_port + try: + await self._find_arduino() + except KeyboardInterrupt: + if self.shutdown_on_exception: + await self.shutdown() + else: + # com_port specified - set com_port and baud rate + try: + await self._manual_open() + except KeyboardInterrupt: + if self.shutdown_on_exception: + await self.shutdown() + + if self.com_port: + print(f'Telemetrix4UnoR4WIFI found and connected to {self.com_port}') + + # no com_port found - raise a runtime exception + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('No Arduino Found or User Aborted Program') + await self.disable_scroll_message() + # using tcp/ip + elif self.transport_type == PrivateConstants.WIFI_TRANSPORT: + self.sock = TelemetrixAioSocket(self.transport_address, self.ip_port, self.loop) + await self.sock.start() + else: # ble + self.ble_instance = TelemetrixAioBle(self.ble_device_name, self._ble_report_dispatcher) + await self.ble_instance.connect() + + # get arduino firmware version and print it + firmware_version = await self._get_firmware_version() + if not firmware_version: + print('*** Firmware Version retrieval timed out. ***') + print('\nDo you have Arduino connectivity and do you have the ') + print('Telemetrix4UnoR4 sketch uploaded to the board and are connected') + print('to the correct serial port.\n') + print('To see a list of serial ports, type: ' + '"list_serial_ports" in your console.') + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError + else: + + print(f'Telemetrix4UnoR4 Version Number: {firmware_version[2]}.' + f'{firmware_version[3]}.{firmware_version[4]}') + # start the command dispatcher loop + command = [PrivateConstants.ENABLE_ALL_REPORTS] + await self._send_command(command) + if not self.loop: + self.loop = asyncio.get_event_loop() + self.the_task = self.loop.create_task(self._arduino_report_dispatcher()) + + # get the features list + command = [PrivateConstants.GET_FEATURES] + await self._send_command(command) + await asyncio.sleep(.5) + + # Have the server reset its data structures + command = [PrivateConstants.RESET] + await self._send_command(command) + await asyncio.sleep(.1) + + async def get_event_loop(self): + """ + Return the currently active asyncio event loop + + :return: Active event loop + + """ + return self.loop + + async def _find_arduino(self): + """ + This method will search all potential serial ports for an Arduino + containing a sketch that has a matching arduino_instance_id as + specified in the input parameters of this class. + + This is used explicitly with the FirmataExpress sketch. + """ + + # a list of serial ports to be checked + serial_ports = [] + + print('Opening all potential serial ports...') + the_ports_list = list_ports.comports() + for port in the_ports_list: + if port.pid is None: + continue + print('\nChecking {}'.format(port.device)) + try: + self.serial_port = TelemetrixAioSerial(port.device, 115200, + telemetrix_aio_instance=self, + close_loop_on_error=self.close_loop_on_shutdown) + except SerialException: + continue + # create a list of serial ports that we opened + serial_ports.append(self.serial_port) + + # display to the user + print('\t' + port.device) + + # clear out any possible data in the input buffer + await self.serial_port.reset_input_buffer() + + # wait for arduino to reset + print('\nWaiting {} seconds(arduino_wait) for Arduino devices to ' + 'reset...'.format(self.arduino_wait)) + await asyncio.sleep(self.arduino_wait) + + print('\nSearching for an Arduino configured with an arduino_instance = ', + self.arduino_instance_id) + + for serial_port in serial_ports: + self.serial_port = serial_port + + command = [PrivateConstants.ARE_U_THERE] + await self._send_command(command) + # provide time for the reply + await asyncio.sleep(.1) + + i_am_here = await self.serial_port.read(3) + + if not i_am_here: + continue + + # got an I am here message - is it the correct ID? + if i_am_here[2] == self.arduino_instance_id: + self.com_port = serial_port.com_port + return + + async def _manual_open(self): + """ + Com port was specified by the user - try to open up that port + + """ + # if port is not found, a serial exception will be thrown + print('Opening {} ...'.format(self.com_port)) + self.serial_port = TelemetrixAioSerial(self.com_port, 115200, + telemetrix_aio_instance=self, + close_loop_on_error=self.close_loop_on_shutdown) + + print('Waiting {} seconds for the Arduino To Reset.' + .format(self.arduino_wait)) + await asyncio.sleep(self.arduino_wait) + command = [PrivateConstants.ARE_U_THERE] + await self._send_command(command) + # provide time for the reply + await asyncio.sleep(.1) + + print(f'Searching for correct arduino_instance_id: {self.arduino_instance_id}') + i_am_here = await self.serial_port.read(3) + + if not i_am_here: + print(f'ERROR: correct arduino_instance_id not found') + + print('Correct arduino_instance_id found') + + async def _get_firmware_version(self): + """ + This method retrieves the Arduino4Telemetrix firmware version + + :returns: Firmata firmware version + """ + self.firmware_version = None + command = [PrivateConstants.GET_FIRMWARE_VERSION] + await self._send_command(command) + # provide time for the reply + await asyncio.sleep(.3) + if self.transport_type == PrivateConstants.SERIAL_TRANSPORT: + self.firmware_version = await self.serial_port.read(5) + elif self.transport_type == PrivateConstants.WIFI_TRANSPORT: + self.firmware_version = list(await self.sock.read(5)) + else: + pass + return self.firmware_version + + async def analog_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (maximum 16 bits) + + """ + value_msb = value >> 8 + value_lsb = value & 0xff + command = [PrivateConstants.ANALOG_WRITE, pin, value_msb, value_lsb] + await self._send_command(command) + + async def digital_write(self, pin, value): + """ + Set the specified pin to the specified value. + + :param pin: arduino pin number + + :param value: pin value (1 or 0) + + """ + command = [PrivateConstants.DIGITAL_WRITE, pin, value] + await self._send_command(command) + + async def i2c_read(self, address, register, number_of_bytes, + callback, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the specified register for + the i2c device. + + + :param address: i2c device address + + :param register: i2c register (or None if no register selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report i2c data as a + result of read command + + :param i2c_port: select the default port (0) or secondary port (1) + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('i2c_read: A Callback must be specified') + + await self._i2c_read_request(address, register, number_of_bytes, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + async def i2c_read_restart_transmission(self, address, register, + number_of_bytes, + callback, i2c_port=0, + write_register=True): + """ + Read the specified number of bytes from the specified register for + the i2c device. This restarts the transmission after the read. It is + required for some i2c devices such as the MMA8452Q accelerometer. + + + :param address: i2c device address + + :param register: i2c register (or None if no register + selection is needed) + + :param number_of_bytes: number of bytes to be read + + :param callback: Required callback function to report i2c data as a + result of read command + + :param i2c_port: select the default port (0) or secondary port (1) + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + callback returns a data list: + + [I2C_READ_REPORT, address, register, count of data bytes, + data bytes, time-stamp] + + """ + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'i2c_read_restart_transmission: A Callback must be specified') + + await self._i2c_read_request(address, register, number_of_bytes, + stop_transmission=False, + callback=callback, i2c_port=i2c_port, + write_register=write_register) + + async def _i2c_read_request(self, address, register, number_of_bytes, + stop_transmission=True, callback=None, + i2c_port=0, write_register=True): + """ + This method requests the read of an i2c device. Results are retrieved + via callback. + + :param address: i2c device address + + :param register: register number (or None if no register selection is needed) + + :param number_of_bytes: number of bytes expected to be returned + + :param stop_transmission: stop transmission after read + + :param callback: Required callback function to report i2c data as a + result of read command. + + :param i2c_port: select the default port (0) or secondary port (1) + + :param write_register: If True, the register is written + before read + Else, the write is suppressed + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Read: set_pin_mode i2c never called for i2c port 2.') + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('I2C Read: A callback function must be specified.') + + if not i2c_port: + self.i2c_callback = callback + else: + self.i2c_callback2 = callback + + if not register: + register = 0 + + if write_register: + write_register = 1 + else: + write_register = 0 + + # message contains: + # 1. address + # 2. register + # 3. number of bytes + # 4. restart_transmission - True or False + # 5. i2c port + # 6. suppress write flag + + command = [PrivateConstants.I2C_READ, address, register, number_of_bytes, + stop_transmission, i2c_port, write_register] + await self._send_command(command) + + async def i2c_write(self, address, args, i2c_port=0): + """ + Write data to an i2c device. + + :param address: i2c device address + + :param i2c_port: 0= port 1, 1 = port 2 + + :param args: A variable number of bytes to be sent to the device + passed in as a list + + """ + if not i2c_port: + if not self.i2c_1_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 1.') + + if i2c_port: + if not self.i2c_2_active: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'I2C Write: set_pin_mode i2c never called for i2c port 2.') + + command = [PrivateConstants.I2C_WRITE, len(args), address, i2c_port] + + for item in args: + command.append(item) + + await self._send_command(command) + + async def loop_back(self, start_character, callback): + """ + This is a debugging method to send a character to the + Arduino device, and have the device loop it back. + + :param start_character: The character to loop back. It should be + an integer. + + :param callback: Looped back character will appear in the callback method + + """ + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('loop_back: A callback function must be specified.') + command = [PrivateConstants.LOOP_COMMAND, ord(start_character)] + self.loop_back_callback = callback + await self._send_command(command) + + async def set_analog_scan_interval(self, interval): + """ + Set the analog scanning interval. + + :param interval: value of 0 - 255 - milliseconds + """ + + if 0 <= interval <= 255: + command = [PrivateConstants.SET_ANALOG_SCANNING_INTERVAL, interval] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('Analog interval must be between 0 and 255') + + async def set_pin_mode_analog_input(self, pin_number, differential=0, callback=None): + """ + Set a pin as an analog input. + + :param pin_number: arduino pin number + + :param callback: async callback function + + :param differential: difference in previous to current value before + report will be generated + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for analog input pins = 3 + + """ + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'set_pin_mode_analog_input: A callback function must be specified.') + + await self._set_pin_mode(pin_number, PrivateConstants.AT_ANALOG, + differential, callback=callback) + + async def set_pin_mode_analog_output(self, pin_number): + """ + + Set a pin as a pwm (analog output) pin. + + :param pin_number:arduino pin number + + """ + + await self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT, differential=0, + callback=None) + + async def set_pin_mode_digital_input(self, pin_number, callback): + """ + Set a pin as a digital input. + + :param pin_number: arduino pin number + + :param callback: async callback function + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + + """ + await self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT, differential=0, + callback=callback) + + async def set_pin_mode_digital_input_pullup(self, pin_number, callback): + """ + Set a pin as a digital input with pullup enabled. + + :param pin_number: arduino pin number + + :param callback: async callback function + + callback returns a data list: + + [pin_type, pin_number, pin_value, raw_time_stamp] + + The pin_type for all digital input pins = 2 + + """ + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + 'set_pin_mode_digital_input_pullup: A callback function must be specified.') + + await self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT_PULLUP, + differential=0, callback=callback) + + async def set_pin_mode_digital_output(self, pin_number): + """ + Set a pin as a digital output pin. + + :param pin_number: arduino pin number + """ + + await self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT, differential=0, + callback=None) + + # noinspection PyIncorrectDocstring + async def set_pin_mode_i2c(self, i2c_port=0): + """ + Establish the standard Arduino i2c pins for i2c utilization. + + :param i2c_port: 0 = i2c1, 1 = i2c2 + + NOTES: 1. THIS METHOD MUST BE CALLED BEFORE ANY I2C REQUEST IS MADE + 2. Callbacks are set within the individual i2c read methods of this + API. + + See i2c_read, or i2c_read_restart_transmission. + + """ + # test for i2c port 2 + if i2c_port: + # if not previously activated set it to activated + # and the send a begin message for this port + if not self.i2c_2_active: + self.i2c_2_active = True + else: + return + # port 1 + else: + if not self.i2c_1_active: + self.i2c_1_active = True + else: + return + + command = [PrivateConstants.I2C_BEGIN, i2c_port] + await self._send_command(command) + + async def set_pin_mode_dht(self, pin, callback=None, dht_type=22): + """ + + :param pin: connection pin + + :param callback: callback function + + :param dht_type: either 22 for DHT22 or 11 for DHT11 + + Error Callback: [DHT REPORT Type, DHT_ERROR_NUMBER, PIN, DHT_TYPE, Time] + + Valid Data Callback: DHT REPORT Type, DHT_DATA=, PIN, DHT_TYPE, Humidity, + Temperature, + Time] + + """ + if self.reported_features & PrivateConstants.DHT_FEATURE: + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('set_pin_mode_dht: A Callback must be specified') + + if self.dht_count < PrivateConstants.MAX_DHTS - 1: + self.dht_callbacks[pin] = callback + self.dht_count += 1 + + if dht_type != 22 and dht_type != 11: + dht_type = 22 + + command = [PrivateConstants.DHT_NEW, pin, dht_type] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'Maximum Number Of DHTs Exceeded - set_pin_mode_dht fails for pin {pin}') + + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The DHT feature is disabled in the server.') + + async def set_pin_mode_servo(self, pin_number, min_pulse=544, max_pulse=2400): + """ + + Attach a pin to a servo motor + + :param pin_number: pin + + :param min_pulse: minimum pulse width + + :param max_pulse: maximum pulse width + + """ + if self.reported_features & PrivateConstants.SERVO_FEATURE: + + minv = (min_pulse).to_bytes(2, byteorder="big") + maxv = (max_pulse).to_bytes(2, byteorder="big") + + command = [PrivateConstants.SERVO_ATTACH, pin_number, + minv[0], minv[1], maxv[0], maxv[1]] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The SERVO feature is disabled in the server.') + + async def set_pin_mode_sonar(self, trigger_pin, echo_pin, + callback): + """ + + :param trigger_pin: + + :param echo_pin: + + :param callback: callback + + """ + if self.reported_features & PrivateConstants.SONAR_FEATURE: + + if not callback: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('set_pin_mode_sonar: A Callback must be specified') + + if self.sonar_count < PrivateConstants.MAX_SONARS - 1: + self.sonar_callbacks[trigger_pin] = callback + self.sonar_count += 1 + + command = [PrivateConstants.SONAR_NEW, trigger_pin, echo_pin] + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'Maximum Number Of Sonars Exceeded - set_pin_mode_sonar fails for pin {trigger_pin}') + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The SONAR feature is disabled in the server.') + + async def set_pin_mode_spi(self, chip_select_list=None): + """ + Specify the list of chip select pins. + + Standard Arduino MISO, MOSI and CLK pins are used for the board in use. + + Chip Select is any digital output capable pin. + + :param chip_select_list: this is a list of pins to be used for chip select. + The pins will be configured as output, and set to high + ready to be used for chip select. + NOTE: You must specify the chips select pins here! + + + command message: [command, number of cs pins, [cs pins...]] + """ + if self.reported_features & PrivateConstants.SPI_FEATURE: + + if type(chip_select_list) != list: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('chip_select_list must be in the form of a list') + if not chip_select_list: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('Chip select pins were not specified') + + self.spi_enabled = True + + command = [PrivateConstants.SPI_INIT, len(chip_select_list)] + + for pin in chip_select_list: + command.append(pin) + self.cs_pins_enabled.append(pin) + await self._send_command(command) + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'The SPI feature is disabled in the server.') + + # async def set_pin_mode_stepper(self, interface=1, pin1=2, pin2=3, pin3=4, + # pin4=5, enable=True): + # """ + # Stepper motor support is implemented as a proxy for the + # the AccelStepper library for the Arduino. + # + # This feature is compatible with the TB6600 Motor Driver + # + # Note: It may not work for other driver types! + # + # https://github.com/waspinator/AccelStepper + # + # Instantiate a stepper motor. + # + # Initialize the interface and pins for a stepper motor. + # + # :param interface: Motor Interface Type: + # + # 1 = Stepper Driver, 2 driver pins required + # + # 2 = FULL2WIRE 2 wire stepper, 2 motor pins required + # + # 3 = FULL3WIRE 3 wire stepper, such as HDD spindle, + # 3 motor pins required + # + # 4 = FULL4WIRE, 4 wire full stepper, 4 motor pins + # required + # + # 6 = HALF3WIRE, 3 wire half stepper, such as HDD spindle, + # 3 motor pins required + # + # 8 = HALF4WIRE, 4 wire half stepper, 4 motor pins required + # + # :param pin1: Arduino digital pin number for motor pin 1 + # + # :param pin2: Arduino digital pin number for motor pin 2 + # + # :param pin3: Arduino digital pin number for motor pin 3 + # + # :param pin4: Arduino digital pin number for motor pin 4 + # + # :param enable: If this is true, the output pins at construction time. + # + # :return: Motor Reference number + # """ + # if self.reported_features & PrivateConstants.STEPPERS_FEATURE: + # + # if self.number_of_steppers == self.max_number_of_steppers: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('Maximum number of steppers has already been assigned') + # + # if interface not in self.valid_stepper_interfaces: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('Invalid stepper interface') + # + # self.number_of_steppers += 1 + # + # motor_id = self.next_stepper_assigned + # self.next_stepper_assigned += 1 + # self.stepper_info_list[motor_id]['instance'] = True + # + # # build message and send message to server + # command = [PrivateConstants.SET_PIN_MODE_STEPPER, motor_id, interface, pin1, + # pin2, pin3, pin4, enable] + # await self._send_command(command) + # + # # return motor id + # return motor_id + # else: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'The Stepper feature is disabled in the server.') + + async def sonar_disable(self): + """ + Disable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_DISABLE] + await self._send_command(command) + + async def sonar_enable(self): + """ + Enable sonar scanning for all sonar sensors + """ + command = [PrivateConstants.SONAR_ENABLE] + await self._send_command(command) + + async def spi_cs_control(self, chip_select_pin, select): + """ + Control an SPI chip select line + :param chip_select_pin: pin connected to CS + + :param select: 0=select, 1=deselect + """ + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_cs_control: SPI interface is not enabled.') + + if chip_select_pin not in self.cs_pins_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_cs_control: chip select pin never enabled.') + command = [PrivateConstants.SPI_CS_CONTROL, chip_select_pin, select] + await self._send_command(command) + + async def spi_read_blocking(self, chip_select, register_selection, + number_of_bytes_to_read, + call_back=None): + """ + Read the specified number of bytes from the specified SPI port and + call the callback function with the reported data. + + :param chip_select: chip select pin + + :param register_selection: Register to be selected for read. + + :param number_of_bytes_to_read: Number of bytes to read + + :param call_back: Required callback function to report spi data as a + result of read command + + + callback returns a data list: + [SPI_READ_REPORT, chip select pin, SPI Register, count of data bytes read, + data bytes, time-stamp] + SPI_READ_REPORT = 13 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_read_blocking: SPI interface is not enabled.') + + if not call_back: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('spi_read_blocking: A Callback must be specified') + + self.spi_callback = call_back + + command = [PrivateConstants.SPI_READ_BLOCKING, chip_select, + number_of_bytes_to_read, + register_selection] + + await self._send_command(command) + + async def spi_set_format(self, clock_divisor, bit_order, data_mode): + """ + Configure how the SPI serializes and de-serializes data on the wire. + + See Arduino SPI reference materials for details. + + :param clock_divisor: 1 - 255 + + :param bit_order: + + LSBFIRST = 0 + + MSBFIRST = 1 (default) + + :param data_mode: + + SPI_MODE0 = 0x00 (default) + + SPI_MODE1 = 1 + + SPI_MODE2 = 2 + + SPI_MODE3 = 3 + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_set_format: SPI interface is not enabled.') + + if not 0 < clock_divisor <= 255: + raise RuntimeError(f'spi_set_format: illegal clock divisor selected.') + if bit_order not in [0, 1]: + raise RuntimeError(f'spi_set_format: illegal bit_order selected.') + if data_mode not in [0, 1, 2, 3]: + raise RuntimeError(f'spi_set_format: illegal data_order selected.') + + command = [PrivateConstants.SPI_SET_FORMAT, clock_divisor, bit_order, + data_mode] + await self._send_command(command) + + async def spi_write_blocking(self, chip_select, bytes_to_write): + """ + Write a list of bytes to the SPI device. + + :param chip_select: chip select pin + + :param bytes_to_write: A list of bytes to write. This must + be in the form of a list. + + """ + + if not self.spi_enabled: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError(f'spi_write_blocking: SPI interface is not enabled.') + + if type(bytes_to_write) is not list: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('spi_write_blocking: bytes_to_write must be a list.') + + command = [PrivateConstants.SPI_WRITE_BLOCKING, chip_select, len(bytes_to_write)] + + for data in bytes_to_write: + command.append(data) + + await self._send_command(command) + + # async def set_pin_mode_one_wire(self, pin): + # """ + # Initialize the one wire serial bus. + # + # :param pin: Data pin connected to the OneWire device + # """ + # self.onewire_enabled = True + # command = [PrivateConstants.ONE_WIRE_INIT, pin] + # await self._send_command(command) + # + # async def onewire_reset(self, callback=None): + # """ + # Reset the onewire device + # + # :param callback: required function to report reset result + # + # callback returns a list: + # [ReportType = 14, Report Subtype = 25, reset result byte, + # timestamp] + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_reset: OneWire interface is not enabled.') + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_reset: A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_RESET] + # await self._send_command(command) + # + # async def onewire_select(self, device_address): + # """ + # Select a device based on its address + # :param device_address: A bytearray of 8 bytes + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_select: OneWire interface is not enabled.') + # + # if type(device_address) is not list: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # + # if len(device_address) != 8: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_select: device address must be an array of 8 ' + # 'bytes.') + # command = [PrivateConstants.ONE_WIRE_SELECT] + # for data in device_address: + # command.append(data) + # await self._send_command(command) + # + # async def onewire_skip(self): + # """ + # Skip the device selection. This only works if you have a + # single device, but you can avoid searching and use this to + # immediately access your device. + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_skip: OneWire interface is not enabled.') + # + # command = [PrivateConstants.ONE_WIRE_SKIP] + # await self._send_command(command) + # + # async def onewire_write(self, data, power=0): + # """ + # Write a byte to the onewire device. If 'power' is one + # then the wire is held high at the end for + # parasitically powered devices. You + # are responsible for eventually de-powering it by calling + # another read or write. + # + # :param data: byte to write. + # :param power: power control (see above) + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_write: OneWire interface is not enabled.') + # if 0 < data < 255: + # command = [PrivateConstants.ONE_WIRE_WRITE, data, power] + # await self._send_command(command) + # else: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_write: Data must be no larger than 255') + # + # async def onewire_read(self, callback=None): + # """ + # Read a byte from the onewire device + # :param callback: required function to report onewire data as a + # result of read command + # + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_READ=29, data byte, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_read: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_READ] + # await self._send_command(command) + # await asynio.sleep(.2) + # + # async def onewire_reset_search(self): + # """ + # Begin a new search. The next use of search will begin at the first device + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_reset_search: OneWire interface is not ' + # f'enabled.') + # else: + # command = [PrivateConstants.ONE_WIRE_RESET_SEARCH] + # await self._send_command(command) + # + # async def onewire_search(self, callback=None): + # """ + # Search for the next device. The device address will returned in the callback. + # If a device is found, the 8 byte address is contained in the callback. + # If no more devices are found, the address returned contains all elements set + # to 0xff. + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_SEARCH=31, 8 byte address, time-stamp] + # + # ONEWIRE_REPORT = 14 + # """ + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_search: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_read A Callback must be specified') + # + # self.onewire_callback = callback + # + # command = [PrivateConstants.ONE_WIRE_SEARCH] + # await self._send_command(command) + # + # async def onewire_crc8(self, address_list, callback=None): + # """ + # Compute a CRC check on an array of data. + # :param address_list: + # + # :param callback: required function to report a onewire device address + # + # callback returns a data list: + # [ONEWIRE_REPORT, ONEWIRE_CRC8=32, CRC, time-stamp] + # + # ONEWIRE_REPORT = 14 + # + # """ + # + # if not self.onewire_enabled: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError(f'onewire_crc8: OneWire interface is not enabled.') + # + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_crc8 A Callback must be specified') + # + # if type(address_list) is not list: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('onewire_crc8: address list must be a list.') + # + # self.onewire_callback = callback + # + # address_length = len(address_list) + # + # command = [PrivateConstants.ONE_WIRE_CRC8, address_length - 1] + # + # for data in address_list: + # command.append(data) + # + # await self._send_command(command) + + async def _set_pin_mode(self, pin_number, pin_state, differential, callback): + """ + A private method to set the various pin modes. + + :param pin_number: arduino pin number + + :param pin_state: INPUT/OUTPUT/ANALOG/PWM/PULLUP - for SERVO use + servo_config() + For DHT use: set_pin_mode_dht + + :param differential: for analog inputs - threshold + value to be achieved for report to + be generated + + :param callback: A reference to an async call back function to be + called when pin data value changes + + """ + if not callback and pin_state != PrivateConstants.AT_OUTPUT: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('_set_pin_mode: A Callback must be specified') + else: + if pin_state == PrivateConstants.AT_INPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT, 1] + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_INPUT_PULLUP: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_INPUT_PULLUP, 1] + self.digital_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_ANALOG: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_ANALOG, + differential >> 8, differential & 0xff, 1] + self.analog_callbacks[pin_number] = callback + elif pin_state == PrivateConstants.AT_OUTPUT: + command = [PrivateConstants.SET_PIN_MODE, pin_number, + PrivateConstants.AT_OUTPUT, 1] + else: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('Unknown pin state') + + if command: + await self._send_command(command) + + await asyncio.sleep(.05) + + async def servo_detach(self, pin_number): + """ + Detach a servo for reuse + :param pin_number: attached pin + """ + command = [PrivateConstants.SERVO_DETACH, pin_number] + await self._send_command(command) + + async def servo_write(self, pin_number, angle): + """ + + Set a servo attached to a pin to a given angle. + + :param pin_number: pin + + :param angle: angle (0-180) + + """ + command = [PrivateConstants.SERVO_WRITE, pin_number, angle] + await self._send_command(command) + + # async def stepper_move_to(self, motor_id, position): + # """ + # Set an absolution target position. If position is positive, the movement is + # clockwise, else it is counter-clockwise. + # + # The run() function (below) will try to move the motor (at most one step per call) + # from the current position to the target position set by the most + # recent call to this function. Caution: moveTo() also recalculates the + # speed for the next step. + # If you are trying to use constant speed movements, you should call setSpeed() + # after calling moveTo(). + # + # :param motor_id: motor id: 0 - 3 + # + # :param position: target position. Maximum value is 32 bits. + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_move_to: Invalid motor_id.') + # + # if position < 0: + # polarity = 1 + # else: + # polarity = 0 + # position = abs(position) + # + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE_TO, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # + # await self._send_command(command) + # + # async def stepper_move(self, motor_id, relative_position): + # """ + # Set the target position relative to the current position. + # + # :param motor_id: motor id: 0 - 3 + # + # :param relative_position: The desired position relative to the current + # position. Negative is anticlockwise from + # the current position. Maximum value is 32 bits. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_move: Invalid motor_id.') + # + # if relative_position < 0: + # polarity = 1 + # else: + # polarity = 0 + # position = abs(relative_position) + # + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_MOVE, motor_id] + # for value in position_bytes: + # command.append(value) + # command.append(polarity) + # await self._send_command(command) + # + # async def stepper_run(self, motor_id, completion_callback=None): + # """ + # This method steps the selected motor based on the current speed. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run: A motion complete callback must be ' + # 'specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN, motor_id] + # await self._send_command(command) + # + # async def stepper_run_speed(self, motor_id): + # """ + # This method steps the selected motor based at a constant speed as set by the most + # recent call to stepper_set_max_speed(). The motor will run continuously. + # + # Once called, the server will continuously attempt to step the motor. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run_speed: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_RUN_SPEED, motor_id] + # await self._send_command(command) + # + # async def stepper_set_max_speed(self, motor_id, max_speed): + # """ + # Sets the maximum permitted speed. The stepper_run() function will accelerate + # up to the speed set by this function. + # + # Caution: the maximum speed achievable depends on your processor and clock speed. + # The default maxSpeed is 1 step per second. + # + # Caution: Speeds that exceed the maximum speed supported by the processor may + # result in non-linear accelerations and decelerations. + # + # :param motor_id: 0 - 3 + # + # :param max_speed: 1 - 1000 + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Invalid motor_id.') + # + # if not 1 < max_speed <= 1000: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_max_speed: Speed range is 1 - 1000.') + # + # self.stepper_info_list[motor_id]['max_speed'] = max_speed + # max_speed_msb = (max_speed & 0xff00) >> 8 + # max_speed_lsb = max_speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MAX_SPEED, motor_id, max_speed_msb, + # max_speed_lsb] + # await self._send_command(command) + # + # async def stepper_get_max_speed(self, motor_id): + # """ + # Returns the maximum speed configured for this stepper + # that was previously set by stepper_set_max_speed() + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # :return: The currently configured maximum speed. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_max_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['max_speed'] + # + # async def stepper_set_acceleration(self, motor_id, acceleration): + # """ + # Sets the acceleration/deceleration rate. + # + # :param motor_id: 0 - 3 + # + # :param acceleration: The desired acceleration in steps per second + # per second. Must be > 0.0. This is an + # expensive call since it requires a square + # root to be calculated on the server. + # Dont call more often than needed. + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Invalid motor_id.') + # + # if not 1 < acceleration <= 1000: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_acceleration: Acceleration range is 1 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['acceleration'] = acceleration + # + # max_accel_msb = acceleration >> 8 + # max_accel_lsb = acceleration & 0xff + # + # command = [PrivateConstants.STEPPER_SET_ACCELERATION, motor_id, max_accel_msb, + # max_accel_lsb] + # await self._send_command(command) + # + # async def stepper_set_speed(self, motor_id, speed): + # """ + # Sets the desired constant speed for use with stepper_run_speed(). + # + # :param motor_id: 0 - 3 + # + # :param speed: 0 - 1000 The desired constant speed in steps per + # second. Positive is clockwise. Speeds of more than 1000 steps per + # second are unreliable. Speed accuracy depends on the Arduino + # crystal. Jitter depends on how frequently you call the + # stepper_run_speed() method. + # The speed will be limited by the current value of + # stepper_set_max_speed(). + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_speed: Invalid motor_id.') + # + # if not 0 < speed <= 1000: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_speed: Speed range is 0 - ' + # '1000.') + # + # self.stepper_info_list[motor_id]['speed'] = speed + # + # speed_msb = speed >> 8 + # speed_lsb = speed & 0xff + # + # command = [PrivateConstants.STEPPER_SET_SPEED, motor_id, speed_msb, speed_lsb] + # await self._send_command(command) + # + # async def stepper_get_speed(self, motor_id): + # """ + # Returns the most recently set speed. + # that was previously set by stepper_set_speed(); + # + # Value is stored in the client, so no callback is required. + # + # :param motor_id: 0 - 3 + # + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_speed: Invalid motor_id.') + # + # return self.stepper_info_list[motor_id]['speed'] + # + # async def stepper_get_distance_to_go(self, motor_id, distance_to_go_callback): + # """ + # Request the distance from the current position to the target position + # from the server. + # + # :param motor_id: 0 - 3 + # + # :param distance_to_go_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=15, motor_id, distance in steps, time_stamp] + # + # A positive distance is clockwise from the current position. + # + # """ + # if not distance_to_go_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_distance_to_go: Invalid motor_id.') + # self.stepper_info_list[motor_id][ + # 'distance_to_go_callback'] = distance_to_go_callback + # command = [PrivateConstants.STEPPER_GET_DISTANCE_TO_GO, motor_id] + # await self._send_command(command) + # + # async def stepper_get_target_position(self, motor_id, target_callback): + # """ + # Request the most recently set target position from the server. + # + # :param motor_id: 0 - 3 + # + # :param target_callback: required callback function to receive report + # + # :return: The distance to go is returned via the callback as a list: + # + # [REPORT_TYPE=16, motor_id, target position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # + # """ + # if not target_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError( + # 'stepper_get_target_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_target_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id][ + # 'target_position_callback'] = target_callback + # + # command = [PrivateConstants.STEPPER_GET_TARGET_POSITION, motor_id] + # await self._send_command(command) + # + # async def stepper_get_current_position(self, motor_id, current_position_callback): + # """ + # Request the current motor position from the server. + # + # :param motor_id: 0 - 3 + # + # :param current_position_callback: required callback function to receive report + # + # :return: The current motor position returned via the callback as a list: + # + # [REPORT_TYPE=17, motor_id, current position in steps, time_stamp] + # + # Positive is clockwise from the 0 position. + # """ + # if not current_position_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError( + # 'stepper_get_current_position Read: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_get_current_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['current_position_callback'] = current_position_callback + # + # command = [PrivateConstants.STEPPER_GET_CURRENT_POSITION, motor_id] + # await self._send_command(command) + # + # async def stepper_set_current_position(self, motor_id, position): + # """ + # Resets the current position of the motor, so that wherever the motor + # happens to be right now is considered to be the new 0 position. Useful + # for setting a zero position on a stepper after an initial hardware + # positioning move. + # + # Has the side effect of setting the current motor speed to 0. + # + # :param motor_id: 0 - 3 + # + # :param position: Position in steps. This is a 32 bit value + # """ + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_current_position: Invalid motor_id.') + # position_bytes = list(position.to_bytes(4, 'big', signed=True)) + # + # command = [PrivateConstants.STEPPER_SET_CURRENT_POSITION, motor_id] + # for value in position_bytes: + # command.append(value) + # await self._send_command(command) + # + # async def stepper_run_speed_to_position(self, motor_id, completion_callback=None): + # """ + # Runs the motor at the currently selected speed until the target position is + # reached. + # + # Does not implement accelerations. + # + # :param motor_id: 0 - 3 + # + # :param completion_callback: call back function to receive motion complete + # notification + # + # callback returns a data list: + # + # [report_type, motor_id, raw_time_stamp] + # + # The report_type = 19 + # """ + # if not completion_callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: A motion complete ' + # 'callback must be ' + # 'specified.') + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_run_speed_to_position: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback + # command = [PrivateConstants.STEPPER_RUN_SPEED_TO_POSITION, motor_id] + # await self._send_command(command) + # + # async def stepper_stop(self, motor_id): + # """ + # Sets a new target position that causes the stepper + # to stop as quickly as possible, using the current speed and + # acceleration parameters. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_stop: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_STOP, motor_id] + # await self._send_command(command) + # + # async def stepper_disable_outputs(self, motor_id): + # """ + # Disable motor pin outputs by setting them all LOW. + # + # Depending on the design of your electronics this may turn off + # the power to the motor coils, saving power. + # + # This is useful to support Arduino low power modes: disable the outputs + # during sleep and then re-enable with enableOutputs() before stepping + # again. + # + # If the enable Pin is defined, sets it to OUTPUT mode and clears + # the pin to disabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_disable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_DISABLE_OUTPUTS, motor_id] + # await self._send_command(command) + # + # async def stepper_enable_outputs(self, motor_id): + # """ + # Enable motor pin outputs by setting the motor pins to OUTPUT + # mode. + # + # If the enable Pin is defined, sets it to OUTPUT mode and sets + # the pin to enabled. + # + # :param motor_id: 0 - 3 + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_enable_outputs: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_ENABLE_OUTPUTS, motor_id] + # await self._send_command(command) + # + # async def stepper_set_min_pulse_width(self, motor_id, minimum_width): + # """ + # Sets the minimum pulse width allowed by the stepper driver. + # + # The minimum practical pulse width is approximately 20 microseconds. + # + # Times less than 20 microseconds will usually result in 20 microseconds or so. + # + # :param motor_id: 0 -3 + # + # :param minimum_width: A 16 bit unsigned value expressed in microseconds. + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Invalid motor_id.') + # + # if not 0 < minimum_width <= 0xff: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_min_pulse_width: Pulse width range = ' + # '0-0xffff.') + # + # width_msb = minimum_width >> 8 + # width_lsb = minimum_width & 0xff + # + # command = [PrivateConstants.STEPPER_SET_MINIMUM_PULSE_WIDTH, motor_id, width_msb, + # width_lsb] + # await self._send_command(command) + # + # async def stepper_set_enable_pin(self, motor_id, pin=0xff): + # """ + # Sets the enable pin number for stepper drivers. + # 0xFF indicates unused (default). + # + # Otherwise, if a pin is set, the pin will be turned on when + # enableOutputs() is called and switched off when disableOutputs() + # is called. + # + # :param motor_id: 0 - 4 + # :param pin: 0-0xff + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Invalid motor_id.') + # + # if not 0 < pin <= 0xff: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_enable_pin: Pulse width range = ' + # '0-0xff.') + # command = [PrivateConstants.STEPPER_SET_ENABLE_PIN, motor_id, pin] + # + # await self._send_command(command) + # + # async def stepper_set_3_pins_inverted(self, motor_id, direction=False, step=False, + # enable=False): + # """ + # Sets the inversion for stepper driver pins. + # + # :param motor_id: 0 - 3 + # + # :param direction: True=inverted or False + # + # :param step: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_3_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_3_PINS_INVERTED, motor_id, direction, + # step, enable] + # + # await self._send_command(command) + # + # async def stepper_set_4_pins_inverted(self, motor_id, pin1_invert=False, + # pin2_invert=False, + # pin3_invert=False, pin4_invert=False, enable=False): + # """ + # Sets the inversion for 2, 3 and 4 wire stepper pins + # + # :param motor_id: 0 - 3 + # + # :param pin1_invert: True=inverted or False + # + # :param pin2_invert: True=inverted or False + # + # :param pin3_invert: True=inverted or False + # + # :param pin4_invert: True=inverted or False + # + # :param enable: True=inverted or False + # """ + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_set_4_pins_inverted: Invalid motor_id.') + # + # command = [PrivateConstants.STEPPER_SET_4_PINS_INVERTED, motor_id, pin1_invert, + # pin2_invert, pin3_invert, pin4_invert, enable] + # + # await self._send_command(command) + # + # async def stepper_is_running(self, motor_id, callback): + # """ + # Checks to see if the motor is currently running to a target. + # + # Callback return True if the speed is not zero or not at the target position. + # + # :param motor_id: 0-4 + # + # :param callback: required callback function to receive report + # + # :return: The current running state returned via the callback as a list: + # + # [REPORT_TYPE=18, motor_id, True or False for running state, time_stamp] + # """ + # if not callback: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError( + # 'stepper_is_running: A callback function must be specified.') + # + # if not self.stepper_info_list[motor_id]['instance']: + # if self.shutdown_on_exception: + # await self.shutdown() + # raise RuntimeError('stepper_is_running: Invalid motor_id.') + # + # self.stepper_info_list[motor_id]['is_running_callback'] = callback + # + # command = [PrivateConstants.STEPPER_IS_RUNNING, motor_id] + # await self._send_command(command) + + async def shutdown(self): + """ + This method attempts an orderly shutdown + If any exceptions are thrown, they are ignored. + + """ + self.shutdown_flag = True + + if self.hard_reset_on_shutdown: + await self.r4_hard_reset() + # stop all reporting - both analog and digital + try: + if self.serial_port: + command = [PrivateConstants.STOP_ALL_REPORTS] + await self._send_command(command) + + await asyncio.sleep(.5) + + await self.serial_port.reset_input_buffer() + await self.serial_port.close() + if self.close_loop_on_shutdown: + self.loop.stop() + elif self.sock: + command = [PrivateConstants.STOP_ALL_REPORTS] + await self._send_command(command) + self.the_task.cancel() + await asyncio.sleep(.5) + if self.close_loop_on_shutdown: + self.loop.stop() + except (RuntimeError, SerialException): + pass + + async def r4_hard_reset(self): + """ + Place the r4 into hard reset + """ + command = [PrivateConstants.BOARD_HARD_RESET, 1] + await self._send_command(command) + + async def disable_all_reporting(self): + """ + Disable reporting for all digital and analog input pins + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DISABLE_ALL, 0] + await self._send_command(command) + + async def disable_analog_reporting(self, pin): + """ + Disables analog reporting for a single analog pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_DISABLE, pin] + await self._send_command(command) + + async def disable_digital_reporting(self, pin): + """ + Disables digital reporting for a single digital pin + + + :param pin: pin number + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_DISABLE, pin] + await self._send_command(command) + + async def enable_analog_reporting(self, pin): + """ + Enables analog reporting for the specified pin. + + :param pin: Analog pin number. For example for A0, the number is 0. + + + """ + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_ANALOG_ENABLE, pin] + await self._send_command(command) + + async def enable_digital_reporting(self, pin): + """ + Enable reporting on the specified digital pin. + + :param pin: Pin number. + """ + + command = [PrivateConstants.MODIFY_REPORTING, + PrivateConstants.REPORTING_DIGITAL_ENABLE, pin] + await self._send_command(command) + + async def enable_scroll_message(self, message, scroll_speed=50): + """ + + :param message: Message with maximum length of 25 + :param scroll_speed: in milliseconds (maximum of 255) + """ + if len(message) > 25: + raise RuntimeError("Scroll message size is maximum of 25 characters.") + + if scroll_speed > 255: + raise RuntimeError("Scroll speed maximum of 255 milliseconds.") + + message = message.encode() + command = [PrivateConstants.SCROLL_MESSAGE_ON, len(message), scroll_speed] + for x in message: + command.append(x) + await self._send_command(command) + + async def disable_scroll_message(self): + """ + Turn off a scrolling message + """ + + command = [PrivateConstants.SCROLL_MESSAGE_OFF] + await self._send_command(command) + + async def _arduino_report_dispatcher(self): + """ + This is a private method. + It continually accepts and interprets data coming from Telemetrix4Arduino,and then + dispatches the correct handler to process the data. + + It first receives the length of the packet, and then reads in the rest of the + packet. A packet consists of a length, report identifier and then the report data. + Using the report identifier, the report handler is fetched from report_dispatch. + + :returns: This method never returns + """ + + while True: + if self.shutdown_flag: + break + try: + if not self.transport_address: + packet_length = await self.serial_port.read() + else: + + packet_length = ord(await self.sock.read()) + except TypeError: + continue + + # get the rest of the packet + if not self.transport_address: + packet = await self.serial_port.read(packet_length) + else: + packet = list(await self.sock.read(packet_length)) + if len(packet) != packet_length: + continue + # print(f'packet.len() {}') + # await asyncio.sleep(.1) + + report = packet[0] + # print(report) + # handle all other messages by looking them up in the + # command dictionary + + await self.report_dispatch[report](packet[1:]) + await asyncio.sleep(self.sleep_tune) + + async def _ble_report_dispatcher(self, sender=None, data=None): + """ + This is a private method called by the incoming data notifier + + Using the report identifier, the report handler is fetched from report_dispatch. + + :param sender: BLE sender ID + :param data: data received over the ble link + + """ + self.the_sender = sender + data = list(data) + report = data[1] + + if report == 5: # get firmware data reply + self.firmware_version = list(data) + print() + # noinspection PyArgumentList + else: + await self.report_dispatch[report](data[2:]) + + ''' + Report message handlers + ''' + + async def _report_loop_data(self, data): + """ + Print data that was looped back + + :param data: byte of loop back data + """ + if self.loop_back_callback: + await self.loop_back_callback(data) + + async def _spi_report(self, report): + report = list(report) + cb_list = [PrivateConstants.SPI_REPORT, report[0]] + report[1:] + + cb_list.append(time.time()) + + await self.spi_callback(cb_list) + + async def _onewire_report(self, report): + report = list(report) + + cb_list = [PrivateConstants.ONE_WIRE_REPORT, report[0]] + report[1:] + cb_list.append(time.time()) + await self.onewire_callback(cb_list) + + async def _report_debug_data(self, data): + """ + Print debug data sent from Arduino + + :param data: data[0] is a byte followed by 2 + bytes that comprise an integer + """ + value = (data[1] << 8) + data[2] + print(f'DEBUG ID: {data[0]} Value: {value}') + + async def _analog_message(self, data): + """ + This is a private message handler method. + It is a message handler for analog messages. + + :param data: message data + + """ + pin = data[0] + value = (data[1] << 8) + data[2] + + time_stamp = time.time() + + # append pin number, pin value, and pin type to return value and return as a list + message = [PrivateConstants.AT_ANALOG, pin, value, time_stamp] + + await self.analog_callbacks[pin](message) + + async def _dht_report(self, data): + """ + This is a private message handler for dht reports + + :param data: data[0] = report error return + No Errors = 0 + + Checksum Error = 1 + + Timeout Error = 2 + + Invalid Value = 999 + + data[1] = pin number + + data[2] = dht type 11 or 22 + + data[3] = humidity positivity flag + + data[4] = temperature positivity value + + data[5] = humidity integer + + data[6] = humidity fractional value + + data[7] = temperature integer + + data[8] = temperature fractional value + """ + data = list(data) + if data[0]: # DHT_ERROR + # error report + # data[0] = report sub type, data[1] = pin, data[2] = error message + if self.dht_callbacks[data[1]]: + # Callback 0=DHT REPORT, DHT_ERROR, PIN, Time + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + time.time()] + await self.dht_callbacks[data[1]](message) + else: + # got valid data DHT_DATA + f_humidity = float(data[5] + data[6] / 100) + if data[3]: + f_humidity *= -1.0 + f_temperature = float(data[7] + data[8] / 100) + if data[4]: + f_temperature *= -1.0 + message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2], + f_humidity, f_temperature, time.time()] + + await self.dht_callbacks[data[1]](message) + + async def _digital_message(self, data): + """ + This is a private message handler method. + It is a message handler for Digital Messages. + + :param data: digital message + + """ + pin = data[0] + value = data[1] + + time_stamp = time.time() + if self.digital_callbacks[pin]: + message = [PrivateConstants.DIGITAL_REPORT, pin, value, time_stamp] + await self.digital_callbacks[pin](message) + + async def _servo_unavailable(self, report): + """ + Message if no servos are available for use. + + :param report: pin number + """ + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'Servo Attach For Pin {report[0]} Failed: No Available Servos') + + async def _i2c_read_report(self, data): + """ + Execute callback for i2c reads. + + :param data: [I2C_READ_REPORT, i2c_port, number of bytes read, address, register, bytes read..., time-stamp] + """ + + # we receive [# data bytes, address, register, data bytes] + # number of bytes of data returned + + # data[0] = number of bytes + # data[1] = i2c_port + # data[2] = number of bytes returned + # data[3] = address + # data[4] = register + # data[5] ... all the data bytes + data = list(data) + cb_list = [PrivateConstants.I2C_READ_REPORT, data[0], data[1]] + data[2:] + cb_list.append(time.time()) + + if cb_list[1]: + await self.i2c_callback2(cb_list) + else: + await self.i2c_callback(cb_list) + + async def _i2c_too_few(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'i2c too few bytes received from i2c port {data[0]} i2c address {data[1]}') + + async def _i2c_too_many(self, data): + """ + I2c reports too few bytes received + + :param data: data[0] = device address + """ + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError( + f'i2c too many bytes received from i2c port {data[0]} i2c address {data[1]}') + + async def _sonar_distance_report(self, report): + """ + + :param report: data[0] = trigger pin, data[1] and data[2] = distance + + callback report format: [PrivateConstants.SONAR_DISTANCE, trigger_pin, distance_value, time_stamp] + """ + report = list(report) + # get callback from pin number + cb = self.sonar_callbacks[report[0]] + + # build report data + cb_list = [PrivateConstants.SONAR_DISTANCE, report[0], + ((report[1] << 8) + report[2]), time.time()] + + await cb(cb_list) + + async def _stepper_distance_to_go_report(self, report): + return # for now + # """ + # Report stepper distance to go. + # + # :param report: data[0] = motor_id, data[1] = steps MSB, data[2] = steps byte 1, + # data[3] = steps bytes 2, data[4] = steps LSB + # + # callback report format: [PrivateConstants.STEPPER_DISTANCE_TO_GO, motor_id + # steps, time_stamp] + # """ + # report = list(report) + # # get callback + # cb = self.stepper_info_list[report[0]]['distance_to_go_callback'] + # + # # isolate the steps bytes and covert list to bytes + # steps = bytes(report[1:]) + # + # # get value from steps + # num_steps = int.from_bytes(steps, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_DISTANCE_TO_GO, report[0], num_steps, + # time.time()] + # + # await cb(cb_list) + # + + async def _stepper_target_position_report(self, report): + return # for now + # """ + # Report stepper target position to go. + # + # :param report: data[0] = motor_id, data[1] = target position MSB, + # data[2] = target position byte MSB+1 + # data[3] = target position byte MSB+2 + # data[4] = target position LSB + # + # callback report format: [PrivateConstants.STEPPER_TARGET_POSITION, motor_id + # target_position, time_stamp] + # """ + # report = list(report) + # # get callback + # cb = self.stepper_info_list[report[0]]['target_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # target = bytes(report[1:]) + # + # # get value from steps + # target_position = int.from_bytes(target, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_TARGET_POSITION, report[0], target_position, + # time.time()] + # + # await cb(cb_list) + # + + async def _stepper_current_position_report(self, report): + return # for now + # """ + # Report stepper current position. + # + # :param report: data[0] = motor_id, data[1] = current position MSB, + # data[2] = current position byte MSB+1 + # data[3] = current position byte MSB+2 + # data[4] = current position LSB + # + # callback report format: [PrivateConstants.STEPPER_CURRENT_POSITION, motor_id + # current_position, time_stamp] + # """ + # report = list(report) + + # # get callback + # cb = self.stepper_info_list[report[0]]['current_position_callback'] + # + # # isolate the steps bytes and covert list to bytes + # position = bytes(report[1:]) + # + # # get value from steps + # current_position = int.from_bytes(position, byteorder='big', signed=True) + # + # cb_list = [PrivateConstants.STEPPER_CURRENT_POSITION, report[0], current_position, + # time.time()] + # + # await cb(cb_list) + # + + async def _stepper_is_running_report(self, report): + return # for now + # """ + # Report if the motor is currently running + # + # :param report: data[0] = motor_id, True if motor is running or False if it is not. + # + # callback report format: [18, motor_id, + # running_state, time_stamp] + # """ + # report = list(report) + + # # get callback + # cb = self.stepper_info_list[report[0]]['is_running_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUNNING_REPORT, report[0], time.time()] + # + # await cb(cb_list) + # + + async def _stepper_run_complete_report(self, report): + return # for now + # """ + # The motor completed it motion + # + # :param report: data[0] = motor_id + # + # callback report format: [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, motor_id, + # time_stamp] + # """ + # report = list(report) + # # get callback + # cb = self.stepper_info_list[report[0]]['motion_complete_callback'] + # + # cb_list = [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, report[0], + # time.time()] + # + # await cb(cb_list) + + async def _features_report(self, report): + self.reported_features = report[0] + + async def _send_command(self, command): + """ + This is a private utility method. + + + :param command: command data in the form of a list + + :returns: number of bytes sent + """ + # the length of the list is added at the head + # the length of the list is added at the head + command.insert(0, len(command)) + send_message = bytes(command) + + if self.transport_type == 1: + try: + await self.serial_port.write(send_message) + except SerialException: + if self.shutdown_on_exception: + await self.shutdown() + raise RuntimeError('write fail in _send_command') + elif self.transport_type == 0: + await self.sock.write(send_message) + else: + await self.ble_instance.write(send_message)