├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── blink.rs ├── button.rs ├── button_async.rs ├── servo.rs └── softpwm.rs └── src ├── debounce.rs ├── devices.rs ├── input_devices.rs ├── lib.rs ├── output_devices.rs └── traits.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | 7 | name: CI 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | toolchain: [stable] 16 | target: [armv7-unknown-linux-gnueabihf, arm-unknown-linux-gnueabihf, aarch64-unknown-linux-gnu] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: ${{ matrix.toolchain }} 22 | target: ${{ matrix.target }} 23 | override: true 24 | - uses: actions-rs/cargo@v1 25 | with: 26 | use-cross: true 27 | command: build 28 | args: --target ${{ matrix.target }} 29 | 30 | cargo-publish: 31 | name: Publish to crates.io 32 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') 33 | runs-on: ubuntu-latest 34 | needs: [build] 35 | steps: 36 | - uses: actions/checkout@v2 37 | - uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: stable 40 | profile: minimal 41 | override: true 42 | - name: Publish 43 | run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | src/led_rppal.rs 5 | src/led_sysfs_gpio.rs 6 | rust_gpiozero.iml 7 | 8 | .idea/workspace.xml 9 | .idea/tasks.xml 10 | .idea/dictionaries 11 | .idea/vcs.xml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.1 4 | 5 | - Change license to MIT OR Apache-2.0 and add support for Raspberry Pi 4 6 | 7 | - Fix clippy and fmt warnings. Thanks @AldaronLau 8 | 9 | - change license to either of MIT OR Apache-2.0. Thanks @owenbrooks 10 | 11 | - upgraded [rppal](https://github.com/golemparts/rppal/) to `0.12.0`. See [Issue #22](https://github.com/rahul-thakoor/rust_gpiozero/issues/22) and [Issue #23](https://github.com/rahul-thakoor/rust_gpiozero/issues/23). Thanks @dmalenic 12 | 13 | ## 0.2.0 14 | 15 | `rust_gpiozero` now uses [rppal](https://github.com/golemparts/rppal/) for gpio access 16 | 17 | - **input_devices** 18 | 19 | - **InputDevice** changes: 20 | 21 | - `pin` is now `u8` 22 | - `new` configures device with the pin pulled to low by default 23 | - Added `new_with_pullup`: create InputDevice with the pin number given with the pin pulled high with an internal resistor by default 24 | - `value` returns a `bool` 25 | 26 | - **DigitalInputDevice** changes: 27 | - `pin` is now `u8` 28 | - `new` configures device with the pin pulled to low by default 29 | - Added `new_with_pullup`: create InputDevice with the pin number given with the pin pulled high with an internal resistor by default 30 | - `value` returns a `bool` 31 | - `wait_for_inactive` takes an argument `timeout` (Option) 32 | - `wait_for_active` takes an argument `timeout` (Option) 33 | - **Button** changes: 34 | - `pin` is now `u8` 35 | - `new` configures device with the pin pulled to `high` by default 36 | - Added `new_with_pulldown`: create InputDevice with the pin number given with the pin pulled low with an internal resistor by default 37 | - `value` returns a `bool` 38 | - `wait_for_inactive` takes an argument `timeout` (Option) 39 | - `wait_for_active` takes an argument `timeout` (Option) 40 | 41 | - **output_devices** 42 | 43 | - **PWMOutputDevice** (New) 44 | - A generic output device configured for software pulse-width modulation (PWM) 45 | - Values can be specified between 0.0 and 1.0 for varying levels of power in the device. 46 | - **PWMLED** (New) 47 | 48 | - Represents a light emitting diode (LED) with variable brightness. 49 | - Values can be specified between 0.0 and 1.0 for varying levels of brightness. 50 | 51 | - **Servo** (New) 52 | - Represents a PWM-controlled servo motor connected to a GPIO pin 53 | - **OutputDevice** changes: 54 | - `pin` is now `u8` 55 | - Added `active_high`: When True, the value property is True when the device's pin is high. When False the value property is True when the device's pin is low (i.e. the value is inverted). 56 | - Added `set_active_high` to set the state for `active_high` 57 | - **DigitalOutputDevice** changes: 58 | - `pin` is now `u8` 59 | - Added `active_high`: When True, the value property is True when the device's pin is high. When False the value property is True when the device's pin is low (i.e. the value is inverted). 60 | - Added `set_active_high` to set the state for `active_high` 61 | - Added `blink` to make the device turn on and off repeatedly in the background. 62 | - Added `set_blink_count`to set the number of times to blink the device 63 | - Added `wait` which blocks until background blinking process is done 64 | - **LED** changes: 65 | - `pin` is now `u8` 66 | - Added `active_high`: When True, the value property is True when the device's pin is high. When False the value property is True when the device's pin is low (i.e. the value is inverted). 67 | - Added `set_active_high` to set the state for `active_high` 68 | - `blink` now takes `f32` for `on_time` and `off_time`. 69 | - Added `set_blink_count`to set the number of times to blink the device 70 | - Added `wait` which blocks until background blinking process is done 71 | - Added `is_lit` which returns True if the device is currently active and False otherwise. 72 | - **Buzzer** changes: 73 | - `pin` is now `u8` 74 | - Added `active_high`: When True, the value property is True when the device's pin is high. When False the value property is True when the device's pin is low (i.e. the value is inverted). 75 | - Added `set_active_high` to set the state for `active_high` 76 | - Added `beep` to make the device turn on and off repeatedly in the background. 77 | - Added `set_beep_count`to set the number of times to beep the device 78 | - Added `wait` which blocks until background beeping process is done 79 | - Removed `blink` method 80 | - **Motor** changes: 81 | - `forward_pin` and `backward_pin`are now `u8` 82 | - Added `set_speed` method: Use `set_speed` to change the speed at which motors should turn. Can be any value between 0.0 and the default 1.0 (maximum speed) 83 | 84 | - **device** 85 | - Renamed `GPIODevice` to `GpioDevice` 86 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at rahul.thakoor@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_gpiozero" 3 | version = "0.2.1" 4 | authors = ["Rahul Thakoor "] 5 | repository = "https://github.com/rahul-thakoor/rust_gpiozero.git" 6 | homepage = "https://github.com/rahul-thakoor/rust_gpiozero.git" 7 | license = "MIT OR Apache-2.0" 8 | readme = "README.md" 9 | description = "A library inspired by gpiozero written in Rust." 10 | keywords = ["embedded-hal", "raspberry","pi", "gpiozero", "rpi"] 11 | exclude = [".idea/*", "doc/**/*.html"] 12 | edition = "2018" 13 | 14 | [features] 15 | 16 | 17 | [dependencies] 18 | rppal = "0.12.0" 19 | 20 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust_gpiozero 2 | 3 | [![CI](https://github.com/rahul-thakoor/rust_gpiozero/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/rahul-thakoor/rust_gpiozero/actions/workflows/ci.yml) 4 | [![Crates.io](https://img.shields.io/crates/v/rust_gpiozero)](https://crates.io/crates/rust_gpiozero) 5 | 6 | A simple interface to GPIO devices with Raspberry Pi. 7 | 8 | This library is based on [GPIOZero](https://gpiozero.readthedocs.io/en/stable/index.html) 9 | library. 10 | 11 | 12 | The idea is to get started with physical computing using Rust with little coding 13 | by hiding the underlying complexity. 14 | 15 | The library uses [BCM Pin numbering](https://pinout.xyz/) 16 | 17 | ### Example : Blinking an LED 18 | 19 | ```rust 20 | 21 | use rust_gpiozero::*; 22 | 23 | fn main() { 24 | // Create a new LED attached to Pin 17 25 | let mut led = LED::new(17); 26 | 27 | // on_time = 2 secs, off_time=3 secs 28 | led.blink(2.0,3.0); 29 | 30 | // prevent program from exiting immediately 31 | led.wait(); 32 | } 33 | 34 | ``` 35 | 36 | 37 | ### Example : Wait for a Button Press 38 | ```rust 39 | 40 | use rust_gpiozero::*; 41 | 42 | fn main() { 43 | // Create a button which is attached to Pin 17 44 | let mut button = Button::new(17); 45 | button.wait_for_press(None); 46 | println!("button pressed"); 47 | } 48 | 49 | ``` 50 | 51 | 52 | Compare this to using the crate `sysfs_gpio` to blink an LED on the Raspberry Pi : 53 | 54 | ```rust 55 | 56 | extern crate sysfs_gpio; 57 | 58 | use sysfs_gpio::{Direction, Pin}; 59 | use std::thread::sleep; 60 | use std::time::Duration; 61 | 62 | fn main() { 63 | let my_led = Pin::new(127); // number depends on chip, etc. 64 | my_led.with_exported(|| { 65 | loop { 66 | my_led.set_value(0).unwrap(); 67 | sleep(Duration::from_millis(200)); 68 | my_led.set_value(1).unwrap(); 69 | sleep(Duration::from_millis(200)); 70 | } 71 | }).unwrap(); 72 | } 73 | 74 | ``` 75 | 76 | 77 | ## Install/Use 78 | 79 | To use `rust_gpiozero`, first add this to your Cargo.toml: 80 | 81 | ```toml 82 | [dependencies] 83 | rust_gpiozero = "^0.2" 84 | ``` 85 | Compiling your project on a Raspberry Pi directly can take significant time depending on the model. Ideally, you would cross compile your project then run it on the Raspberry Pi. 86 | 87 | [More information](https://github.com/japaric/rust-cross) 88 | 89 | ## Features 90 | 91 | The following features are planned : 92 | 93 | - [ ] Support for `embedded-hal` 94 | - [ ] Support for common devices such as Accelerometer, Temperature sensors, etc 95 | 96 | ## Changelog 97 | 98 | [CHANGELOG.md](https://github.com/rahul-thakoor/rust_gpiozero/blob/master/CHANGELOG.md) 99 | 100 | ## License 101 | 102 | Licensed under either of 103 | 104 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 105 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 106 | 107 | at your option. 108 | 109 | ## Contributing 110 | Unless you explicitly state otherwise, any contribution intentionally submitted 111 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 112 | additional terms or conditions. 113 | 114 | Thanks for your interest in `rust_gpiozero`. I am a newbie rustacean and just started using the language! I am using this project to learn more about Rust. Feel free to give feedback or send PRs. Your experiences and feedback will also benefit others who use this library. 115 | 116 | ## Credits 117 | This library would not be possible without the great work of the maintainers of [GPIOZero](https://gpiozero.readthedocs.io/en/stable/index.html) and [rppal](https://github.com/golemparts/rppal) 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /examples/blink.rs: -------------------------------------------------------------------------------- 1 | //! Blinks an LED : on_time: 2 seconds and off_time: 3 seconds 2 | 3 | use rust_gpiozero::*; 4 | 5 | fn main() { 6 | // Create a new LED attached to Pin 17 7 | let mut led = LED::new(17); 8 | 9 | // on_time = 2 secs, off_time=3 secs 10 | led.blink(2.0, 3.0); 11 | 12 | // prevent program from exiting immediately 13 | led.wait(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/button.rs: -------------------------------------------------------------------------------- 1 | //! Display message in console when a Button is pressed 2 | use rust_gpiozero::{Button, Debounce}; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | // Create a button which is attached to Pin 17 7 | let mut button = Button::new(17) 8 | // Add debouncing so that subsequent presses within 100ms don't trigger a press 9 | .debounce(Duration::from_millis(100)); 10 | 11 | button.wait_for_press(None); 12 | println!("button pressed"); 13 | } 14 | -------------------------------------------------------------------------------- /examples/button_async.rs: -------------------------------------------------------------------------------- 1 | //! Display message in console when a Button is pressed 2 | use rust_gpiozero::{Button, Debounce}; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | // Create a button which is attached to Pin 17 7 | let mut button = Button::new(17) 8 | // Add debouncing so that subsequent presses within 100ms don't trigger a press 9 | .debounce(Duration::from_millis(100)); 10 | 11 | // Add an async interrupt to trigger whenever the button is pressed 12 | button 13 | .when_pressed(|_| { 14 | println!("button pressed"); 15 | }) 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /examples/servo.rs: -------------------------------------------------------------------------------- 1 | use rust_gpiozero::*; 2 | use std::io; 3 | use std::io::prelude::*; 4 | use std::sync::atomic::{AtomicBool, Ordering}; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | static RUNNING: AtomicBool = AtomicBool::new(true); 9 | 10 | fn watch_stdin() { 11 | // wait for key press to exit 12 | let _ = io::stdin().read(&mut [0u8]).unwrap(); 13 | RUNNING.store(false, Ordering::Relaxed); 14 | } 15 | 16 | fn main() { 17 | std::thread::spawn(watch_stdin); 18 | 19 | // Create a new Servo attached to Pin 23 20 | let mut servo = Servo::new(23); 21 | 22 | while RUNNING.load(Ordering::Relaxed) { 23 | servo.max(); 24 | thread::sleep(Duration::from_millis(2_000)); 25 | servo.min(); 26 | thread::sleep(Duration::from_millis(2_000)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/softpwm.rs: -------------------------------------------------------------------------------- 1 | use rust_gpiozero::*; 2 | use std::io; 3 | use std::io::prelude::*; 4 | 5 | fn main() { 6 | // Create a new LED attached to Pin 17 7 | let mut led = PWMLED::new(17); 8 | 9 | // blink the LED 5 times 10 | led.set_blink_count(5); 11 | led.blink(2.0, 2.0, 1.0, 1.0); 12 | 13 | // wait for key press to exit 14 | let _ = io::stdin().read(&mut [0u8]).unwrap(); 15 | } 16 | -------------------------------------------------------------------------------- /src/debounce.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::sync::{Arc, Mutex}; 4 | use std::time::{Duration, Instant}; 5 | 6 | use crate::Button; 7 | use rppal::gpio::{self, Level, Trigger}; 8 | 9 | /// Adds `.debounce()` method to [`Button`] for converting to a [`Debounced`] button 10 | pub trait Debounce { 11 | fn debounce(self, duration: Duration) -> Debounced; 12 | } 13 | 14 | impl Debounce for Button { 15 | fn debounce(self, duration: Duration) -> Debounced { 16 | Debounced { 17 | inner: self, 18 | period: duration, 19 | last_trigger: Arc::new(Mutex::new(None)), 20 | } 21 | } 22 | } 23 | 24 | /// Wrapper type for [`Button`] to allow for software [debouncing](https://en.wikipedia.org/wiki/Switch#Contact%20Bounce). Will prevent 25 | /// Subsequent triggers with the given debounce period (E.g. 50-100 milliseconds) 26 | /// 27 | /// Can be used with blocking functions (E.g [`Button::wait_for_press`]): 28 | /// ``` 29 | /// use rust_gpiozero::{Button, Debounce}; 30 | /// use std::time::Duration; 31 | /// 32 | /// // Create a button which is attached to Pin 17 33 | /// let mut button = Button::new(17) 34 | /// // Add debouncing so that subsequent presses within 100ms don't trigger a press 35 | /// .debounce(Duration::from_millis(100)); 36 | /// 37 | /// button.wait_for_press(None); 38 | /// println!("button pressed"); 39 | /// ``` 40 | /// 41 | /// Or async interrupt functions (E.g. [`Button::when_pressed`]): 42 | /// ``` 43 | /// use rust_gpiozero::{Button, Debounce}; 44 | /// use std::time::Duration; 45 | /// 46 | /// // Create a button which is attached to Pin 17 47 | /// let mut button = Button::new(17) 48 | /// // Add debouncing so that subsequent presses within 100ms don't trigger a press 49 | /// .debounce(Duration::from_millis(100)); 50 | /// 51 | /// // Add an async interrupt to trigger whenever the button is pressed 52 | /// button.when_pressed(|_| { 53 | /// println!("button pressed"); 54 | /// }).unwrap(); 55 | /// ``` 56 | 57 | pub struct Debounced { 58 | inner: Button, 59 | period: Duration, 60 | last_trigger: Arc>>, 61 | } 62 | 63 | impl fmt::Debug for Debounced { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | f.debug_struct("Debounced") 66 | .field("pin", &self.inner.pin()) 67 | .field("period", &self.period) 68 | .finish() 69 | } 70 | } 71 | 72 | impl Debounced { 73 | /// Pause the program until the device is deactivated, or the timeout is reached. 74 | /// * `timeout` - Number of seconds to wait before proceeding. If this is None, then wait indefinitely until the device is inactive. 75 | pub fn wait_for_release(&mut self, timeout: Option) { 76 | Debounced::wait_for(self, timeout, false) 77 | } 78 | 79 | /// Pause the program until the device is activated, or the timeout is reached. 80 | /// * `timeout` - Number of seconds to wait before proceeding. If this is None, then wait indefinitely until the device is active. 81 | pub fn wait_for_press(&mut self, timeout: Option) { 82 | Debounced::wait_for(self, timeout, true) 83 | } 84 | 85 | fn wait_for(&mut self, timeout: Option, active: bool) { 86 | let trigger = match active { 87 | true => Trigger::RisingEdge, 88 | false => Trigger::FallingEdge, 89 | }; 90 | let timeout = timeout.map(|seconds| Duration::from_millis((seconds * 1000.0) as u64)); 91 | self.inner.pin.set_interrupt(trigger).unwrap(); 92 | loop { 93 | self.inner.pin.poll_interrupt(true, timeout).unwrap(); 94 | // Check that enough time has passed since the last press 95 | if let Some(last_trigger) = self.last_trigger.lock().unwrap().as_ref() { 96 | // If this press is within the debounce time, continue blocking until the next press 97 | if last_trigger.elapsed() < self.period { 98 | continue; 99 | } 100 | } 101 | // if self.last_trigger is not set, there have been no previous presses so debounce time doesn't matter 102 | break; 103 | } 104 | self.last_trigger.lock().unwrap().replace(Instant::now()); 105 | } 106 | 107 | /// Asynchronously invokes the passed closure everytime the button is pressed, if the debounce period has passed 108 | pub fn when_pressed(&mut self, action: C) -> Result<(), gpio::Error> 109 | where 110 | C: FnMut(Level) + Send + 'static, 111 | { 112 | self.action_on(true, action) 113 | } 114 | 115 | /// Asynchronously invokes the passed closure everytime the button is released, if the debounce period has passed 116 | pub fn when_released(&mut self, action: C) -> Result<(), gpio::Error> 117 | where 118 | C: FnMut(Level) + Send + 'static, 119 | { 120 | self.action_on(false, action) 121 | } 122 | 123 | pub(crate) fn action_on(&mut self, active: bool, mut action: C) -> Result<(), gpio::Error> 124 | where 125 | C: FnMut(Level) + Send + 'static, 126 | { 127 | let period = self.period; 128 | let last_trigger = self.last_trigger.clone(); 129 | self.inner.action_on(active, move |level| { 130 | let mut lt = last_trigger.lock().unwrap(); 131 | if let Some(last) = lt.as_ref() { 132 | if last.elapsed() < period { 133 | // Within debounce period, don't execute action 134 | return; 135 | } 136 | } 137 | lt.replace(Instant::now()); 138 | action(level); 139 | }) 140 | } 141 | } 142 | 143 | impl Deref for Debounced { 144 | type Target = Button; 145 | 146 | fn deref(&self) -> &Self::Target { 147 | &self.inner 148 | } 149 | } 150 | 151 | impl DerefMut for Debounced { 152 | fn deref_mut(&mut self) -> &mut Self::Target { 153 | &mut self.inner 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/devices.rs: -------------------------------------------------------------------------------- 1 | //! Describes generic devices such as `GPIODevice` and `CompositeDevice` 2 | 3 | use rppal::gpio::{Gpio, Level, Pin}; 4 | 5 | /// Represents a single device of any type; GPIO-based, SPI-based, I2C-based, 6 | /// etc. It defines the basic services applicable to all devices 7 | pub trait Device { 8 | /// Shut down the device and release all associated resources. 9 | fn close(self); 10 | 11 | /// Returns ``True`` if the device is currently active and ``False`` otherwise. 12 | fn is_active(&self) -> bool; 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! impl_device { 17 | () => { 18 | /// Returns ``True`` if the device is currently active and ``False`` otherwise. 19 | pub fn is_active(&self) -> bool { 20 | self.value() 21 | } 22 | /// Shut down the device and release all associated resources. 23 | pub fn close(self) { 24 | drop(self) 25 | } 26 | }; 27 | } 28 | 29 | /// Represents a generic GPIO device and provides the services common to all single-pin GPIO devices 30 | #[derive(Debug)] 31 | pub struct GpioDevice { 32 | pin: Pin, 33 | active_state: bool, 34 | inactive_state: bool, 35 | } 36 | 37 | macro_rules! impl_gpio_device { 38 | () => { 39 | /// The `Pin` that the device is connected to. 40 | pub fn pin(&self) -> u8 { 41 | self.pin.pin() 42 | } 43 | }; 44 | } 45 | 46 | impl GpioDevice { 47 | /// Returns a GpioDevice with the pin number given 48 | /// # Arguments 49 | /// 50 | /// * `pin` - The GPIO pin which the device is attached to 51 | pub fn new(pin: u8) -> GpioDevice { 52 | match Gpio::new() { 53 | Err(e) => panic!("{:?}", e), 54 | Ok(gpio) => match gpio.get(pin) { 55 | Err(e) => panic!("{:?}", e), 56 | Ok(pin) => GpioDevice { 57 | pin, 58 | active_state: true, 59 | inactive_state: false, 60 | }, 61 | }, 62 | } 63 | } 64 | 65 | /// Returns a value representing the device's state. 66 | pub fn value(&self) -> bool { 67 | self.state_to_value() 68 | } 69 | 70 | fn state_to_value(&self) -> bool { 71 | match self.pin.read() { 72 | Level::High => self.active_state, 73 | Level::Low => !self.active_state, 74 | } 75 | } 76 | 77 | impl_device!(); 78 | impl_gpio_device!(); 79 | } 80 | -------------------------------------------------------------------------------- /src/input_devices.rs: -------------------------------------------------------------------------------- 1 | //! Input device component interfaces for devices such as `Button` 2 | use rppal::gpio::{self, Gpio, InputPin, Level, Trigger}; 3 | use std::time::Duration; 4 | 5 | /// Represents a generic GPIO input device. 6 | #[derive(Debug)] 7 | pub struct InputDevice { 8 | pin: InputPin, 9 | active_state: bool, 10 | inactive_state: bool, 11 | } 12 | 13 | impl InputDevice { 14 | /// Returns an InputDevice with the pin number given with the pin pulled to low by default 15 | /// `is_active` property is adjusted accordingly so that 16 | /// ``True`` still means active regardless of the :attr:`pull_up` setting 17 | /// # Arguments 18 | /// 19 | /// * `pin` - The GPIO pin which the device is attached to 20 | /// 21 | pub fn new(pin: u8) -> InputDevice { 22 | match Gpio::new() { 23 | Err(e) => panic!("{:?}", e), 24 | Ok(gpio) => match gpio.get(pin) { 25 | Err(e) => panic!("{:?}", e), 26 | Ok(pin) => InputDevice { 27 | pin: pin.into_input_pulldown(), 28 | active_state: true, 29 | inactive_state: false, 30 | }, 31 | }, 32 | } 33 | } 34 | /// Returns an InputDevice with the pin number given with the pin pulled high with an internal resistor by default 35 | /// `is_active` property is adjusted accordingly so that 36 | /// ``True`` still means active regardless of the :attr:`pull_up` setting 37 | /// # Arguments 38 | /// 39 | /// * `pin` - The GPIO pin which the device is attached to 40 | /// 41 | pub fn new_with_pullup(pin: u8) -> InputDevice { 42 | match Gpio::new() { 43 | Err(e) => panic!("{:?}", e), 44 | Ok(gpio) => match gpio.get(pin) { 45 | Err(e) => panic!("{:?}", e), 46 | Ok(pin) => InputDevice { 47 | pin: pin.into_input_pullup(), 48 | active_state: false, 49 | inactive_state: true, 50 | }, 51 | }, 52 | } 53 | } 54 | 55 | impl_device!(); 56 | impl_gpio_device!(); 57 | impl_io_device!(); 58 | } 59 | 60 | macro_rules! impl_events_mixin { 61 | () => { 62 | /// Pause the program until the device is activated, or the timeout is reached. 63 | fn wait_for(&mut self, timeout: Option, active: bool) { 64 | let trigger = match active { 65 | true => Trigger::RisingEdge, 66 | false => Trigger::FallingEdge, 67 | }; 68 | let timeout = timeout.map(|seconds| Duration::from_millis((seconds * 1000.0) as u64)); 69 | self.pin.set_interrupt(trigger).unwrap(); 70 | self.pin.poll_interrupt(true, timeout).unwrap(); 71 | } 72 | }; 73 | } 74 | 75 | /// Represents a generic input device with typical on/off behaviour. 76 | /// Adds machinery to fire the active and inactive events for devices 77 | /// that operate in a typical digital manner: straight forward on / off 78 | /// states with (reasonably) clean transitions between the two. 79 | #[derive(Debug)] 80 | pub struct DigitalInputDevice { 81 | pin: InputPin, 82 | active_state: bool, 83 | inactive_state: bool, 84 | } 85 | 86 | impl DigitalInputDevice { 87 | /// Returns a DigitalInputDevice with the pin number given with the pin pulled to low by default 88 | /// `is_active` property is adjusted accordingly so that 89 | /// ``True`` still means active regardless of the :attr:`pull_up` setting 90 | /// # Arguments 91 | /// 92 | /// * `pin` - The GPIO pin which the device is attached to 93 | /// # Note: BCM pins 2 and 3 are i2c SDA and SCL respectively and include a fixed, 1.8 kohms pull-up to 3.3v 94 | /// These pins are not suitable for use where no pullup resistor is required 95 | /// Source: https://pinout.xyz/pinout/pin5_gpio3 96 | pub fn new(pin: u8) -> DigitalInputDevice { 97 | match Gpio::new() { 98 | Err(e) => panic!("{:?}", e), 99 | Ok(gpio) => match gpio.get(pin) { 100 | Err(e) => panic!("{:?}", e), 101 | Ok(pin) => DigitalInputDevice { 102 | pin: pin.into_input_pulldown(), 103 | active_state: true, 104 | inactive_state: false, 105 | }, 106 | }, 107 | } 108 | } 109 | /// Returns a DigitalInputDevice with the pin number given with the pin pulled high with an internal resistor by default 110 | /// `is_active` property is adjusted accordingly so that 111 | /// ``True`` still means active regardless of the :attr:`pull_up` setting 112 | /// # Arguments 113 | /// 114 | /// * `pin` - The GPIO pin which the device is attached to 115 | /// 116 | pub fn new_with_pullup(pin: u8) -> DigitalInputDevice { 117 | match Gpio::new() { 118 | Err(e) => panic!("{:?}", e), 119 | Ok(gpio) => match gpio.get(pin) { 120 | Err(e) => panic!("{:?}", e), 121 | Ok(pin) => DigitalInputDevice { 122 | pin: pin.into_input_pullup(), 123 | active_state: false, 124 | inactive_state: true, 125 | }, 126 | }, 127 | } 128 | } 129 | 130 | impl_device!(); 131 | impl_gpio_device!(); 132 | impl_io_device!(); 133 | impl_events_mixin!(); 134 | 135 | /// Pause the program until the device is deactivated, or the timeout is reached. 136 | pub fn wait_for_inactive(&mut self, timeout: Option) { 137 | self.wait_for(timeout, false) 138 | } 139 | 140 | /// Pause the program until the device is activated, or the timeout is reached. 141 | pub fn wait_for_active(&mut self, timeout: Option) { 142 | self.wait_for(timeout, true) 143 | } 144 | } 145 | 146 | /// Represents a simple push button or switch. 147 | /// Connect one side of the button to a ground pin, and the other to any GPIO pin. The GPIO pin will be pulled high by default. 148 | /// Alternatively, connect one side of the button to the 3V3 pin, and the other to any GPIO pin, 149 | /// and then create a Button instance with Button::new_with_pulldown 150 | pub struct Button { 151 | pub(crate) pin: InputPin, 152 | active_state: bool, 153 | inactive_state: bool, 154 | } 155 | 156 | impl Button { 157 | /// Returns a Button with the pin number given and the pin pulled high with an internal resistor by default 158 | /// * `pin` - The GPIO pin which the device is attached to 159 | pub fn new(pin: u8) -> Button { 160 | match Gpio::new() { 161 | Err(e) => panic!("{:?}", e), 162 | Ok(gpio) => match gpio.get(pin) { 163 | Err(e) => panic!("{:?}", e), 164 | Ok(pin) => Button { 165 | pin: pin.into_input_pullup(), 166 | active_state: false, 167 | inactive_state: true, 168 | }, 169 | }, 170 | } 171 | } 172 | /// Returns a Button with the pin number given and the pin pulled down with an internal resistor by default 173 | /// * `pin` - The GPIO pin which the device is attached to 174 | pub fn new_with_pulldown(pin: u8) -> Button { 175 | match Gpio::new() { 176 | Err(e) => panic!("{:?}", e), 177 | Ok(gpio) => match gpio.get(pin) { 178 | Err(e) => panic!("{:?}", e), 179 | Ok(pin) => Button { 180 | pin: pin.into_input_pulldown(), 181 | active_state: true, 182 | inactive_state: false, 183 | }, 184 | }, 185 | } 186 | } 187 | 188 | impl_device!(); 189 | impl_gpio_device!(); 190 | impl_io_device!(); 191 | impl_events_mixin!(); 192 | 193 | //// Pause the program until the device is deactivated, or the timeout is reached. 194 | /// * `timeout` - Number of seconds to wait before proceeding. If this is None, then wait indefinitely until the device is inactive. 195 | pub fn wait_for_release(&mut self, timeout: Option) { 196 | self.wait_for(timeout, false) 197 | } 198 | 199 | /// Pause the program until the device is activated, or the timeout is reached. 200 | /// * `timeout` - Number of seconds to wait before proceeding. If this is None, then wait indefinitely until the device is active. 201 | pub fn wait_for_press(&mut self, timeout: Option) { 202 | self.wait_for(timeout, true) 203 | } 204 | 205 | /// Invokes the passed closure everytime the button is pressed 206 | pub fn when_pressed(&mut self, action: C) -> Result<(), gpio::Error> 207 | where 208 | C: FnMut(Level) + Send + 'static, 209 | { 210 | self.action_on(true, action) 211 | } 212 | 213 | /// Invokes the passed closure everytime the button is released 214 | pub fn when_released(&mut self, action: C) -> Result<(), gpio::Error> 215 | where 216 | C: FnMut(Level) + Send + 'static, 217 | { 218 | self.action_on(false, action) 219 | } 220 | 221 | /// Adds an async interrupt for the corresponding trigger type to support `when_pressed`/`when_released` 222 | pub(crate) fn action_on(&mut self, active: bool, action: C) -> Result<(), gpio::Error> 223 | where 224 | C: FnMut(Level) + Send + 'static, 225 | { 226 | let trigger = match active { 227 | true => Trigger::RisingEdge, 228 | false => Trigger::FallingEdge, 229 | }; 230 | self.pin.set_async_interrupt(trigger, action) 231 | } 232 | 233 | /// Removes all previously configured async trigger(s) (E.g. `when_pressed`/`when_released`) 234 | pub fn clear_async_interrupt(&mut self) -> Result<(), gpio::Error> { 235 | self.pin.clear_async_interrupt() 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | #![crate_name = "rust_gpiozero"] 3 | 4 | //! A simple interface to GPIO devices with Raspberry Pi. 5 | //! 6 | //! This library is based on [GPIOZero](https://gpiozero.readthedocs.io/en/stable/index.html) 7 | //! library. 8 | //! 9 | //! _Note: This is a work in progress. The library will eventually support `embedded-hal` based drivers_ 10 | //! 11 | //! 12 | //! The idea is to get started with physical computing using Rust with little coding 13 | //! by hiding the underlying complexity. 14 | //! 15 | //! The library uses [BCM Pin numbering](https://pinout.xyz/) 16 | //! 17 | //! # Example : Blinking an LED 18 | //! 19 | //! ``` 20 | //! use rust_gpiozero::*; 21 | //! 22 | //! // Create a new LED attached to Pin 17 23 | //! let mut led = LED::new(17); 24 | //! 25 | //! // blink the LED 26 | //! // on_time: 2 seconds and off_time: 3 seconds 27 | //! led.blink(2.0,3.0); 28 | //! ``` 29 | 30 | pub use self::devices::*; 31 | pub use self::input_devices::*; 32 | pub use self::output_devices::*; 33 | 34 | #[macro_use] 35 | pub mod devices; 36 | #[macro_use] 37 | pub mod output_devices; 38 | #[macro_use] 39 | pub mod input_devices; 40 | 41 | mod debounce; 42 | pub use debounce::{Debounce, Debounced}; 43 | -------------------------------------------------------------------------------- /src/output_devices.rs: -------------------------------------------------------------------------------- 1 | //! Output device component interfaces for devices such as `LED`, `PWMLED`, etc 2 | use rppal::gpio::{Gpio, IoPin, Level, Mode}; 3 | use std::sync::atomic::{AtomicBool, Ordering}; 4 | use std::sync::Arc; 5 | use std::sync::Mutex; 6 | use std::thread; 7 | use std::thread::JoinHandle; 8 | use std::time::Duration; 9 | 10 | /// Represents a generic GPIO output device. 11 | #[derive(Debug)] 12 | pub struct OutputDevice { 13 | pin: IoPin, 14 | active_state: bool, 15 | inactive_state: bool, 16 | } 17 | #[macro_export] 18 | macro_rules! impl_io_device { 19 | () => { 20 | #[allow(dead_code)] 21 | fn value_to_state(&self, value: bool) -> bool { 22 | if value { 23 | self.active_state 24 | } else { 25 | self.inactive_state 26 | } 27 | } 28 | 29 | fn state_to_value(&self, state: bool) -> bool { 30 | state == self.active_state 31 | } 32 | 33 | /// Returns ``True`` if the device is currently active and ``False`` otherwise. 34 | pub fn value(&self) -> bool { 35 | match self.pin.read() { 36 | Level::Low => self.state_to_value(false), 37 | Level::High => self.state_to_value(true), 38 | } 39 | } 40 | }; 41 | } 42 | 43 | macro_rules! impl_output_device { 44 | () => { 45 | /// Set the state for active_high 46 | pub fn set_active_high(&mut self, value: bool) { 47 | if value { 48 | self.active_state = true; 49 | self.inactive_state = false; 50 | } else { 51 | self.active_state = false; 52 | self.inactive_state = true; 53 | } 54 | } 55 | /// When ``True``, the `value` property is ``True`` when the device's 56 | /// `pin` is high. When ``False`` the `value` property is 57 | /// ``True`` when the device's pin is low (i.e. the value is inverted). 58 | /// Be warned that changing it will invert `value` (i.e. changing this property doesn't change 59 | /// the device's pin state - it just changes how that state is interpreted). 60 | pub fn active_high(&self) -> bool { 61 | self.active_state 62 | } 63 | 64 | /// Turns the device on. 65 | pub fn on(&mut self) { 66 | self.write_state(true) 67 | } 68 | 69 | /// Turns the device off. 70 | pub fn off(&mut self) { 71 | self.write_state(false) 72 | } 73 | /// Reverse the state of the device. If it's on, turn it off; if it's off, turn it on. 74 | pub fn toggle(&mut self) { 75 | if self.is_active() { 76 | self.off() 77 | } else { 78 | self.on() 79 | } 80 | } 81 | fn write_state(&mut self, value: bool) { 82 | if self.value_to_state(value) { 83 | self.pin.set_high() 84 | } else { 85 | self.pin.set_low() 86 | } 87 | } 88 | }; 89 | } 90 | 91 | impl OutputDevice { 92 | /// Returns an OutputDevice with the pin number given 93 | 94 | /// 95 | /// * `pin` - The GPIO pin which the device is attached to 96 | /// 97 | pub fn new(pin: u8) -> OutputDevice { 98 | match Gpio::new() { 99 | Err(e) => panic!("{:?}", e), 100 | Ok(gpio) => match gpio.get(pin) { 101 | Err(e) => panic!("{:?}", e), 102 | Ok(pin) => OutputDevice { 103 | pin: pin.into_io(Mode::Output), 104 | active_state: true, 105 | inactive_state: false, 106 | }, 107 | }, 108 | } 109 | } 110 | 111 | impl_device!(); 112 | impl_gpio_device!(); 113 | impl_io_device!(); 114 | impl_output_device!(); 115 | } 116 | 117 | /// Represents a generic output device with typical on/off behaviour. 118 | /// Extends behaviour with a blink() method which uses a background 119 | /// thread to handle toggling the device state without further interaction. 120 | #[derive(Debug)] 121 | pub struct DigitalOutputDevice { 122 | device: Arc>, 123 | blinking: Arc, 124 | handle: Option>, 125 | blink_count: Option, 126 | } 127 | 128 | macro_rules! impl_digital_output_device { 129 | () => { 130 | fn blinker(&mut self, on_time: f32, off_time: f32, n: Option) { 131 | self.stop(); 132 | 133 | let device = Arc::clone(&self.device); 134 | let blinking = Arc::clone(&self.blinking); 135 | 136 | self.handle = Some(thread::spawn(move || { 137 | blinking.store(true, Ordering::SeqCst); 138 | match n { 139 | Some(end) => { 140 | for _ in 0..end { 141 | if !blinking.load(Ordering::SeqCst) { 142 | device.lock().unwrap().off(); 143 | break; 144 | } 145 | device.lock().unwrap().on(); 146 | thread::sleep(Duration::from_millis((on_time * 1000.0) as u64)); 147 | device.lock().unwrap().off(); 148 | thread::sleep(Duration::from_millis((off_time * 1000.0) as u64)); 149 | } 150 | } 151 | None => loop { 152 | if !blinking.load(Ordering::SeqCst) { 153 | device.lock().unwrap().off(); 154 | break; 155 | } 156 | device.lock().unwrap().on(); 157 | thread::sleep(Duration::from_millis((on_time * 1000.0) as u64)); 158 | device.lock().unwrap().off(); 159 | thread::sleep(Duration::from_millis((off_time * 1000.0) as u64)); 160 | }, 161 | } 162 | })); 163 | } 164 | /// Returns ``True`` if the device is currently active and ``False`` otherwise. 165 | pub fn is_active(&self) -> bool { 166 | Arc::clone(&self.device).lock().unwrap().is_active() 167 | } 168 | /// Turns the device on. 169 | pub fn on(&self) { 170 | self.stop(); 171 | self.device.lock().unwrap().on() 172 | } 173 | /// Turns the device off. 174 | pub fn off(&self) { 175 | self.stop(); 176 | self.device.lock().unwrap().off() 177 | } 178 | 179 | /// Reverse the state of the device. If it's on, turn it off; if it's off, turn it on. 180 | pub fn toggle(&mut self) { 181 | self.device.lock().unwrap().toggle() 182 | } 183 | 184 | /// Returns ``True`` if the device is currently active and ``False`` otherwise. 185 | pub fn value(&self) -> bool { 186 | self.device.lock().unwrap().value() 187 | } 188 | 189 | fn stop(&self) { 190 | self.blinking.clone().store(false, Ordering::SeqCst); 191 | self.device.lock().unwrap().pin.set_low(); 192 | } 193 | 194 | /// When ``True``, the `value` property is ``True`` when the device's 195 | /// `pin` is high. When ``False`` the `value` property is 196 | /// ``True`` when the device's pin is low (i.e. the value is inverted). 197 | /// Be warned that changing it will invert `value` (i.e. changing this property doesn't change 198 | /// the device's pin state - it just changes how that state is interpreted). 199 | pub fn active_high(&self) -> bool { 200 | self.device.lock().unwrap().active_high() 201 | } 202 | 203 | /// Set the state for active_high 204 | pub fn set_active_high(&mut self, value: bool) { 205 | self.device.lock().unwrap().set_active_high(value) 206 | } 207 | 208 | /// The `Pin` that the device is connected to. 209 | pub fn pin(&self) -> u8 { 210 | self.device.lock().unwrap().pin.pin() 211 | } 212 | 213 | /// Shut down the device and release all associated resources. 214 | pub fn close(self) { 215 | drop(self) 216 | } 217 | 218 | /// Block until background process is done 219 | pub fn wait(&mut self) { 220 | self.handle 221 | .take() 222 | .expect("Called stop on non-running thread") 223 | .join() 224 | .expect("Could not join spawned thread"); 225 | } 226 | }; 227 | } 228 | 229 | impl DigitalOutputDevice { 230 | pub fn new(pin: u8) -> DigitalOutputDevice { 231 | DigitalOutputDevice { 232 | device: Arc::new(Mutex::new(OutputDevice::new(pin))), 233 | blinking: Arc::new(AtomicBool::new(false)), 234 | handle: None, 235 | blink_count: None, 236 | } 237 | } 238 | 239 | impl_digital_output_device!(); 240 | 241 | /// Make the device turn on and off repeatedly in the background. 242 | /// Use `set_blink_count` to set the number of times to blink the device 243 | /// * `on_time` - Number of seconds on 244 | /// * `off_time` - Number of seconds off 245 | /// 246 | pub fn blink(&mut self, on_time: f32, off_time: f32) { 247 | match self.blink_count { 248 | None => self.blinker(on_time, off_time, None), 249 | Some(n) => self.blinker(on_time, off_time, Some(n)), 250 | } 251 | } 252 | /// Set the number of times to blink the device 253 | /// * `n` - Number of times to blink 254 | pub fn set_blink_count(&mut self, n: i32) { 255 | self.blink_count = Some(n) 256 | } 257 | } 258 | 259 | /// Represents a light emitting diode (LED) 260 | /// 261 | /// # Example 262 | /// Connect LED as shown below, with cathode(short leg) connected to GND 263 | /// 264 | /// ```shell 265 | /// Resistor LED 266 | /// Pin 14 o--/\/\/---->|------o GND 267 | /// ``` 268 | /// 269 | 270 | #[derive(Debug)] 271 | pub struct LED { 272 | device: Arc>, 273 | blinking: Arc, 274 | handle: Option>, 275 | blink_count: Option, 276 | } 277 | 278 | impl LED { 279 | pub fn new(pin: u8) -> LED { 280 | LED { 281 | device: Arc::new(Mutex::new(OutputDevice::new(pin))), 282 | blinking: Arc::new(AtomicBool::new(false)), 283 | handle: None, 284 | blink_count: None, 285 | } 286 | } 287 | 288 | impl_digital_output_device!(); 289 | 290 | /// Returns True if the device is currently active and False otherwise. 291 | pub fn is_lit(&self) -> bool { 292 | self.is_active() 293 | } 294 | 295 | /// Make the device turn on and off repeatedly in the background. 296 | /// Use `set_blink_count` to set the number of times to blink the device 297 | /// * `on_time` - Number of seconds on 298 | /// * `off_time` - Number of seconds off 299 | /// 300 | pub fn blink(&mut self, on_time: f32, off_time: f32) { 301 | match self.blink_count { 302 | None => self.blinker(on_time, off_time, None), 303 | Some(n) => self.blinker(on_time, off_time, Some(n)), 304 | } 305 | } 306 | /// Set the number of times to blink the device 307 | /// * `n` - Number of times to blink 308 | pub fn set_blink_count(&mut self, n: i32) { 309 | self.blink_count = Some(n) 310 | } 311 | } 312 | 313 | /// Represents a digital buzzer component. 314 | /// 315 | /// Connect the cathode (negative pin) of the buzzer to a ground pin; 316 | /// connect the other side to any GPIO pin. 317 | 318 | #[derive(Debug)] 319 | pub struct Buzzer { 320 | device: Arc>, 321 | blinking: Arc, 322 | handle: Option>, 323 | blink_count: Option, 324 | } 325 | 326 | impl Buzzer { 327 | pub fn new(pin: u8) -> Buzzer { 328 | Buzzer { 329 | device: Arc::new(Mutex::new(OutputDevice::new(pin))), 330 | blinking: Arc::new(AtomicBool::new(false)), 331 | handle: None, 332 | blink_count: None, 333 | } 334 | } 335 | 336 | impl_digital_output_device!(); 337 | 338 | /// Make the device turn on and off repeatedly in the background. 339 | /// Use `set_beep_count` to set the number of times to beep the device 340 | /// * `on_time` - Number of seconds on 341 | /// * `off_time` - Number of seconds off 342 | /// 343 | pub fn beep(&mut self, on_time: f32, off_time: f32) { 344 | match self.blink_count { 345 | None => self.blinker(on_time, off_time, None), 346 | Some(n) => self.blinker(on_time, off_time, Some(n)), 347 | } 348 | } 349 | /// Set the number of times to beep the device 350 | /// * `n` - Number of times to beep 351 | pub fn set_beep_count(&mut self, n: i32) { 352 | self.blink_count = Some(n) 353 | } 354 | } 355 | 356 | /// Generic output device configured for software pulse-width modulation (PWM). 357 | /// The pulse width of the signal will be 100μs with a value range of [0,100] (where 0 is a constant low and 100 is a constant high) resulting in a frequenzy of 100 Hz. 358 | pub struct PWMOutputDevice { 359 | device: Arc>, 360 | blinking: Arc, 361 | handle: Option>, 362 | blink_count: Option, 363 | active_state: bool, 364 | inactive_state: bool, 365 | } 366 | 367 | macro_rules! impl_pwm_device { 368 | () => { 369 | /// Set the duty cycle of the PWM device. 0.0 is off, 1.0 is fully on. 370 | /// Values in between may be specified for varying levels of power in the device. 371 | pub fn set_value(&mut self, duty: f64) { 372 | self.write_state(duty) 373 | } 374 | /// Set the number of times to blink the device 375 | /// * `n` - Number of times to blink 376 | pub fn set_blink_count(&mut self, n: i32) { 377 | self.blink_count = Some(n) 378 | } 379 | 380 | fn blinker( 381 | &mut self, 382 | on_time: f32, 383 | off_time: f32, 384 | fade_in_time: f32, 385 | fade_out_time: f32, 386 | n: Option, 387 | ) { 388 | let mut sequence: Vec<(f32, f32)> = Vec::new(); 389 | let fps = 25.0; 390 | // create sequence for fading in 391 | if fade_in_time > 0.0 { 392 | for i in 0..fps as i32 * fade_in_time as i32 { 393 | sequence.push((i as f32 * (1.0 / fps) / fade_in_time, 1.0 / fps)) 394 | } 395 | } 396 | 397 | // allow to stay on for on_time 398 | sequence.push((1.0, on_time)); 399 | 400 | // create sequence for fading out 401 | if fade_out_time > 0.0 { 402 | for i in 0..fps as i32 * fade_out_time as i32 { 403 | sequence.push((1.0 - (i as f32 * (1.0 / fps) / fade_out_time), 1.0 / fps)) 404 | } 405 | } 406 | 407 | // allow to stay off for off_time 408 | sequence.push((0.0, off_time)); 409 | 410 | let device = Arc::clone(&self.device); 411 | let blinking = Arc::clone(&self.blinking); 412 | 413 | self.handle = Some(thread::spawn(move || { 414 | blinking.store(true, Ordering::SeqCst); 415 | match n { 416 | Some(end) => { 417 | for _ in 0..end { 418 | for (value, delay) in &sequence { 419 | if !blinking.load(Ordering::SeqCst) { 420 | // device.lock().unwrap().off(); 421 | break; 422 | } 423 | device 424 | .lock() 425 | .unwrap() 426 | .pin 427 | .set_pwm_frequency(100.0, f64::from(*value)) 428 | .unwrap(); 429 | thread::sleep(Duration::from_millis((delay * 1000 as f32) as u64)); 430 | } 431 | } 432 | } 433 | None => loop { 434 | for (value, delay) in &sequence { 435 | if !blinking.load(Ordering::SeqCst) { 436 | // device.lock().unwrap().off(); 437 | break; 438 | } 439 | device 440 | .lock() 441 | .unwrap() 442 | .pin 443 | .set_pwm_frequency(100.0, f64::from(*value)) 444 | .unwrap(); 445 | thread::sleep(Duration::from_millis((delay * 1000 as f32) as u64)); 446 | } 447 | }, 448 | } 449 | })); 450 | } 451 | 452 | fn stop(&mut self) { 453 | self.blinking.clone().store(false, Ordering::SeqCst); 454 | if self.device.lock().unwrap().pin.clear_pwm().is_err() { 455 | println!("Could not clear pwm for pin"); 456 | }; 457 | } 458 | 459 | fn write_state(&mut self, value: f64) { 460 | if !(0.0..=1.0).contains(&value) { 461 | println!("Value must be between 0.0 and 1.0"); 462 | return; 463 | } 464 | self.stop(); 465 | if self.active_high() { 466 | self.device 467 | .lock() 468 | .unwrap() 469 | .pin 470 | .set_pwm_frequency(100.0, value) 471 | .unwrap() 472 | } else { 473 | self.device 474 | .lock() 475 | .unwrap() 476 | .pin 477 | .set_pwm_frequency(100.0, 1.0 - value) 478 | .unwrap() 479 | } 480 | } 481 | 482 | /// Set the state for active_high 483 | pub fn set_active_high(&mut self, value: bool) { 484 | if value { 485 | self.active_state = true; 486 | self.inactive_state = false; 487 | } else { 488 | self.active_state = false; 489 | self.inactive_state = true; 490 | } 491 | } 492 | /// When ``True``, the `value` property is ``True`` when the device's 493 | /// `pin` is high. When ``False`` the `value` property is 494 | /// ``True`` when the device's pin is low (i.e. the value is inverted). 495 | /// Be warned that changing it will invert `value` (i.e. changing this property doesn't change 496 | /// the device's pin state - it just changes how that state is interpreted). 497 | pub fn active_high(&self) -> bool { 498 | self.active_state 499 | } 500 | 501 | /// Turns the device on. 502 | pub fn on(&mut self) { 503 | self.write_state(1.0) 504 | } 505 | 506 | /// Turns the device off. 507 | pub fn off(&mut self) { 508 | self.write_state(0.0) 509 | } 510 | }; 511 | } 512 | 513 | impl PWMOutputDevice { 514 | /// Returns a PWMOutputDevice with the pin number given 515 | /// 516 | /// * `pin` - The GPIO pin which the device is attached to 517 | /// 518 | pub fn new(pin: u8) -> PWMOutputDevice { 519 | PWMOutputDevice { 520 | device: Arc::new(Mutex::new(OutputDevice::new(pin))), 521 | blinking: Arc::new(AtomicBool::new(false)), 522 | handle: None, 523 | blink_count: None, 524 | active_state: true, 525 | inactive_state: false, 526 | } 527 | } 528 | 529 | impl_pwm_device!(); 530 | 531 | /// Make the device turn on and off repeatedly 532 | /// * `on_time` - Number of seconds on 533 | /// * `off_time` - Number of seconds off 534 | /// * `fade_in_time` - Number of seconds to spend fading in 535 | /// * `fade_out_time` - Number of seconds to spend fading out 536 | /// 537 | pub fn blink(&mut self, on_time: f32, off_time: f32, fade_in_time: f32, fade_out_time: f32) { 538 | match self.blink_count { 539 | None => self.blinker(on_time, off_time, fade_in_time, fade_out_time, None), 540 | Some(n) => self.blinker(on_time, off_time, fade_in_time, fade_out_time, Some(n)), 541 | } 542 | } 543 | 544 | /// Make the device fade in and out repeatedly. 545 | /// * `fade_in_time` - Number of seconds to spend fading in 546 | /// * `fade_out_time` - Number of seconds to spend fading out 547 | /// 548 | pub fn pulse(&mut self, fade_in_time: f32, fade_out_time: f32) { 549 | self.blink(0.0, 0.0, fade_in_time, fade_out_time) 550 | } 551 | } 552 | 553 | /// Represents a light emitting diode (LED) with variable brightness. 554 | /// A typical configuration of such a device is to connect a GPIO pin 555 | /// to the anode (long leg) of the LED, and the cathode (short leg) to ground, 556 | /// with an optional resistor to prevent the LED from burning out. 557 | pub struct PWMLED(PWMOutputDevice); 558 | 559 | impl PWMLED { 560 | /// Returns a PMWLED with the pin number given 561 | /// 562 | /// * `pin` - The GPIO pin which the device is attached to 563 | /// 564 | pub fn new(pin: u8) -> PWMLED { 565 | PWMLED(PWMOutputDevice::new(pin)) 566 | } 567 | 568 | /// Make the device turn on and off repeatedly 569 | /// * `on_time` - Number of seconds on 570 | /// * `off_time` - Number of seconds off 571 | /// * `fade_in_time` - Number of seconds to spend fading in 572 | /// * `fade_out_time` - Number of seconds to spend fading out 573 | /// 574 | pub fn blink(&mut self, on_time: f32, off_time: f32, fade_in_time: f32, fade_out_time: f32) { 575 | self.0.blink(on_time, off_time, fade_in_time, fade_out_time) 576 | } 577 | 578 | /// Turns the device on. 579 | pub fn on(&mut self) { 580 | self.0.on(); 581 | } 582 | 583 | /// Turns the device off. 584 | pub fn off(&mut self) { 585 | self.0.off(); 586 | } 587 | 588 | /// Make the device fade in and out repeatedly. 589 | /// * `fade_in_time` - Number of seconds to spend fading in 590 | /// * `fade_out_time` - Number of seconds to spend fading out 591 | /// 592 | pub fn pulse(&mut self, fade_in_time: f32, fade_out_time: f32) { 593 | self.0.pulse(fade_in_time, fade_out_time); 594 | } 595 | 596 | /// Set the duty cycle of the PWM device. 0.0 is off, 1.0 is fully on. 597 | /// Values in between may be specified for varying levels of power in the device. 598 | pub fn set_value(&mut self, value: f64) { 599 | self.0.set_value(value); 600 | } 601 | 602 | /// Set the number of times to blink the device 603 | /// * `n` - Number of times to blink 604 | pub fn set_blink_count(&mut self, n: i32) { 605 | self.0.blink_count = Some(n) 606 | } 607 | } 608 | 609 | struct MotorCompositeDevice(PWMOutputDevice, PWMOutputDevice); 610 | 611 | /// Represents a generic motor connected 612 | /// to a bi-directional motor driver circuit (i.e. an H-bridge). 613 | /// Attach an H-bridge motor controller to your Pi; connect a power source (e.g. a battery pack or the 5V pin) 614 | /// to the controller; connect the outputs of the controller board to the two terminals of the motor; connect the inputs of the controller board to two GPIO pins. 615 | pub struct Motor { 616 | devices: MotorCompositeDevice, 617 | speed: f64, 618 | } 619 | 620 | impl Motor { 621 | /// creates a new Motor instance 622 | /// * `forward_pin` - The GPIO pin that the forward input of the motor driver chip is connected to 623 | /// * `backward` - The GPIO pin that the backward input of the motor driver chip is connected to 624 | pub fn new(forward_pin: u8, backward_pin: u8) -> Motor { 625 | let forward = PWMOutputDevice::new(forward_pin); 626 | let backward = PWMOutputDevice::new(backward_pin); 627 | Motor { 628 | devices: MotorCompositeDevice(forward, backward), 629 | speed: 1.0, 630 | } 631 | } 632 | 633 | /// Drive the motor forwards at the current speed. 634 | /// You can change the speed using `set_speed` before calling `forward` 635 | pub fn forward(&mut self) { 636 | self.devices.1.off(); 637 | self.devices.0.set_value(self.speed); 638 | } 639 | 640 | /// Drive the motor backwards. 641 | /// You can change the speed using `set_speed` before calling `backward` 642 | pub fn backward(&mut self) { 643 | self.devices.0.off(); 644 | self.devices.1.set_value(self.speed); 645 | } 646 | 647 | /// Stop the motor. 648 | pub fn stop(&mut self) { 649 | self.devices.0.off(); 650 | self.devices.1.off(); 651 | } 652 | 653 | /// The speed at which the motor should turn. 654 | /// Can be any value between 0.0 (stopped) and the default 1.0 (maximum speed) 655 | pub fn set_speed(&mut self, speed: f64) { 656 | if !(0.0..=1.0).contains(&speed) { 657 | println!("Speed must be between 0.0 and 1.0"); 658 | return; 659 | } 660 | self.speed = speed 661 | } 662 | } 663 | 664 | /// Represents a PWM-controlled servo motor connected to a GPIO pin. 665 | //reference :https://github.com/golemparts/rppal/blob/master/examples/gpio_servo_softpwm.rs 666 | pub struct Servo { 667 | pin: IoPin, 668 | min_pulse_width: u64, 669 | max_pulse_width: u64, 670 | frame_width: u64, 671 | } 672 | 673 | impl Servo { 674 | /// Returns a Servo with the pin number given with default `min_pulse_width` of 1ms, 675 | /// `max_pulse_width` of 2ms and `frame_width` of 20ms 676 | /// 677 | /// * `pin` - The GPIO pin which the device is attached to 678 | /// 679 | pub fn new(pin: u8) -> Servo { 680 | match Gpio::new() { 681 | Err(e) => panic!("{:?}", e), 682 | Ok(gpio) => match gpio.get(pin) { 683 | Err(e) => panic!("{:?}", e), 684 | Ok(pin) => Servo { 685 | pin: pin.into_io(Mode::Output), 686 | min_pulse_width: 1000, 687 | max_pulse_width: 2000, 688 | frame_width: 20, 689 | }, 690 | }, 691 | } 692 | } 693 | 694 | /// Set the servo to its minimum position. 695 | pub fn min(&mut self) { 696 | if self 697 | .pin 698 | .set_pwm( 699 | Duration::from_millis(self.frame_width), 700 | Duration::from_micros(self.min_pulse_width), 701 | ) 702 | .is_err() 703 | { 704 | println!("Failed to set servo to minimum position") 705 | } 706 | } 707 | 708 | /// Set the servo to its maximum position. 709 | pub fn max(&mut self) { 710 | if self 711 | .pin 712 | .set_pwm( 713 | Duration::from_millis(self.frame_width), 714 | Duration::from_micros(self.max_pulse_width), 715 | ) 716 | .is_err() 717 | { 718 | println!("Failed to set servo to maximum position") 719 | } 720 | } 721 | 722 | /// Set the servo to its neutral position. 723 | pub fn mid(&mut self) { 724 | let mid_value = (self.min_pulse_width + self.max_pulse_width) / 2; 725 | if self 726 | .pin 727 | .set_pwm( 728 | Duration::from_millis(self.frame_width), 729 | Duration::from_micros(mid_value), 730 | ) 731 | .is_err() 732 | { 733 | println!("Failed to set servo to neutral position") 734 | } 735 | } 736 | 737 | /// Set servo to any position between min and max. 738 | /// value must be between -1 (the minimun position) and +1 (the maximum position). 739 | pub fn set_position(&mut self, value: f64) { 740 | if value >= -1.0 && value <= 1.0 { 741 | // Map value form [-1, 1] to [min_pulse_width, max_pulse_width] linearly 742 | let range: f64 = (self.max_pulse_width - self.min_pulse_width) as f64; 743 | let pulse_width: u64 = self.min_pulse_width + (((value + 1.0)/2.0) * range).round() as u64; 744 | if self 745 | .pin 746 | .set_pwm( 747 | Duration::from_millis(self.frame_width), 748 | Duration::from_micros(pulse_width), 749 | ) 750 | .is_err() 751 | { 752 | println!("Failed to set servo to a new position"); 753 | } 754 | } 755 | else { 756 | println!("set_position value must be between -1 and 1"); 757 | } 758 | } 759 | 760 | /// Set the servo's minimum pulse width 761 | pub fn set_min_pulse_width(&mut self, value: u64) { 762 | if value >= self.max_pulse_width { 763 | println!("min_pulse_width must be less than max_pulse_width"); 764 | } else { 765 | self.min_pulse_width = value; 766 | } 767 | } 768 | 769 | /// Set the servo's maximum pulse width 770 | pub fn set_max_pulse_width(&mut self, value: u64) { 771 | if value >= self.frame_width * 1000 { 772 | println!("max_pulse_width must be less than frame_width"); 773 | } else { 774 | self.max_pulse_width = value; 775 | } 776 | } 777 | 778 | /// Set the servo's frame width(The time between control pulses, measured in milliseconds.) 779 | pub fn set_frame_width(&mut self, value: u64) { 780 | self.frame_width = value; 781 | } 782 | 783 | /// Get the servo's minimum pulse width 784 | pub fn get_min_pulse_width(&mut self) -> u64 { 785 | self.min_pulse_width 786 | } 787 | 788 | /// Get the servo's maximum pulse width 789 | pub fn get_max_pulse_width(&mut self) -> u64 { 790 | self.max_pulse_width 791 | } 792 | 793 | /// Get the servo's frame width(The time between control pulses, measured in milliseconds.) 794 | pub fn get_frame_width(&mut self) -> u64 { 795 | self.frame_width 796 | } 797 | 798 | pub fn detach(&mut self) { 799 | if self 800 | .pin 801 | .clear_pwm() 802 | .is_err() 803 | { 804 | println!("Failed to detach servo") 805 | } 806 | } 807 | } 808 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | //! Adds important functionalities 2 | use sysfs_gpio::Pin; 3 | 4 | /// Represents a single device of any type; GPIO-based, SPI-based, I2C-based, 5 | /// etc. It defines the basic services applicable to all devices 6 | pub trait Device { 7 | /// Get the pin 8 | fn pin(&self) -> Pin; 9 | 10 | /// Shut down the device and release all associated resources. 11 | fn close(&self) { 12 | let pin = self.pin(); 13 | if pin.is_exported() { 14 | //TODO implement better error handling 15 | pin.unexport().expect("Could not close device"); 16 | } 17 | } 18 | 19 | /// Returns a value representing the device's state. 20 | fn value(&self) -> i8; 21 | 22 | /// Returns ``True`` if the device is currently active and ``False``otherwise. 23 | fn is_active(&self) -> bool { 24 | let value = self.value(); 25 | value >= 1 26 | } 27 | } 28 | 29 | /// Adds edge-detected `when_activated` and `when_deactivated` 30 | /// events to a device based on changes to the `is_active` 31 | /// property common to all devices. 32 | pub trait EventsTrait: Device { 33 | /// Pause the program until the device is activated 34 | fn wait_for_active(&self) { 35 | loop { 36 | if self.is_active() { 37 | break; 38 | } 39 | } 40 | } 41 | 42 | /// Pause the program until the device is deactivated 43 | fn wait_for_inactive(&self) { 44 | loop { 45 | if !self.is_active() { 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | 52 | /// Represents a device composed of multiple devices like simple HATs, 53 | /// H-bridge motor controllers, robots composed of multiple motors, etc. 54 | pub trait CompositeDevices { 55 | /// Shut down the device and release all associated resources. 56 | fn close(&self); 57 | } 58 | --------------------------------------------------------------------------------