├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── report-a-bug.md │ └── request-a-feature.md ├── dependabot.yml └── workflows │ ├── CI.yml │ ├── CodeQL.yml │ ├── Sonar.yml │ └── nuget.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Examples ├── CLI │ ├── CLI.csproj │ └── Program.cs ├── Components │ ├── DotMatrix │ │ ├── DotMatrix.csproj │ │ └── Program.cs │ ├── Motor │ │ ├── Motor.csproj │ │ └── Program.cs │ ├── RGBLED │ │ ├── Program.cs │ │ └── RGBLED.csproj │ ├── RotaryEncoder │ │ ├── Program.cs │ │ └── RotaryEncoder.csproj │ ├── SevenSegmentDisplay │ │ ├── Program.cs │ │ └── SevenSegmentDisplay.csproj │ └── ShiftRegister │ │ ├── Program.cs │ │ └── ShiftRegister.csproj ├── Input │ ├── Input.csproj │ └── Program.cs ├── PWM │ ├── PWM.csproj │ └── Program.cs └── Web │ ├── Controllers │ └── PiController.cs │ ├── Program.cs │ ├── Startup.cs │ ├── Web.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ └── index.html ├── LICENSE ├── README.md ├── SECURITY.md ├── SimpleGPIO.Tests ├── Boards │ ├── BroadcomBoardTests.cs │ ├── BroadcomStub.cs │ └── RaspberryPiTests.cs ├── Components │ ├── DotMatrixTests.cs │ ├── MotorTests.cs │ ├── RGBLEDTests.cs │ ├── RotaryEncoderTests.cs │ ├── SevenSegmentDisplayTests.cs │ └── ShiftRegisterTests.cs ├── GPIO │ ├── PinStub.cs │ └── SystemPinInterfaceTests.cs ├── IO │ └── IOHelpersTests.cs ├── MathTests.cs ├── Power │ ├── PowerHelpersTests.cs │ └── PowerModeTests.cs ├── SPI │ ├── InputStub.cs │ └── InputTests.cs └── SimpleGPIO.Tests.csproj ├── SimpleGPIO.sln └── SimpleGPIO ├── Boards ├── BroadcomBoard.cs └── RaspberryPi.cs ├── Components ├── DotMatrix.cs ├── Motor.cs ├── RGBLED.cs ├── RotaryEncoder.cs ├── SevenSegmentDisplay.cs └── ShiftRegister.cs ├── Device ├── GpioControllerWrapper.cs ├── IGpioController.cs ├── IPwmChannel.cs ├── PinInterfaceFactory.cs └── PwmChannelWrapper.cs ├── GPIO ├── IPinInterface.cs ├── PinInterface.cs └── SystemPinInterface.cs ├── IO ├── Direction.cs ├── IOHelpers.cs └── IOMode.cs ├── IsExternalInit.cs ├── Math.cs ├── Power ├── Differential.cs ├── Direct.cs ├── IPowerMode.cs ├── PowerHelpers.cs ├── PowerMode.cs ├── PowerValue.cs └── Voltage.cs ├── SPI └── Input.cs └── SimpleGPIO.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | trim_trailing_whitespace = true 7 | insert_final_newline = false 8 | max_line_length = off 9 | 10 | [{*.yml,*.yaml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.cs] 15 | csharp_prefer_braces = true 16 | csharp_style_expression_bodied_methods = true 17 | csharp_style_expression_bodied_constructors = true 18 | csharp_style_expression_bodied_operators = true 19 | csharp_style_expression_bodied_properties = true 20 | csharp_style_expression_bodied_indexers = true 21 | csharp_style_expression_bodied_accessors = true 22 | csharp_style_expression_bodied_lambdas = true 23 | csharp_style_expression_bodied_local_functions = true 24 | csharp_style_pattern_local_over_anonymous_function = false 25 | csharp_style_namespace_declarations = file_scoped:warning 26 | dotnet_diagnostic.S101.severity = suggestion 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report-a-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a Bug 3 | about: Describe a problem 4 | 5 | --- 6 | 7 | **Steps to reproduce** 8 | 1. login as X 9 | 1. click X 10 | 1. etc. 11 | 12 | **Expected behavior** 13 | - this should happen 14 | - and this 15 | - etc. 16 | 17 | **Observed behavior** 18 | - this happened instead 19 | - field X got set wrong 20 | - etc. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request-a-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request a Feature 3 | about: Suggest new functionality for the app 4 | 5 | --- 6 | 7 | As a ___type_of_user___, I want to ___accomplish_this_goal___, so that ___reason_why_this_would_help___. 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [ push, pull_request ] 3 | 4 | jobs: 5 | Build: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - name: Setup .NET 12 | uses: actions/setup-dotnet@v4 13 | with: 14 | dotnet-version: 9.0.x 15 | 16 | - name: Restore 17 | run: dotnet restore 18 | 19 | - name: Build 20 | run: dotnet build --no-restore 21 | 22 | - name: Test 23 | run: dotnet test --no-build --verbosity normal -------------------------------------------------------------------------------- /.github/workflows/CodeQL.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '45 7 * * *' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | - name: Setup .NET 54 | uses: actions/setup-dotnet@v4 55 | with: 56 | dotnet-version: 9.0.x 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@v3 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 https://git.io/JvXDl 65 | 66 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 67 | # and modify them (or add more) to build your code if your project 68 | # uses a compiled language 69 | 70 | #- run: | 71 | # make bootstrap 72 | # make release 73 | 74 | - name: Perform CodeQL Analysis 75 | uses: github/codeql-action/analyze@v3 76 | -------------------------------------------------------------------------------- /.github/workflows/Sonar.yml: -------------------------------------------------------------------------------- 1 | name: Sonar 2 | on: push 3 | 4 | jobs: 5 | Code-Quality: 6 | runs-on: ubuntu-latest 7 | if: github.actor != 'dependabot[bot]' 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: 9.0.x 19 | 20 | - name: Install Java 21 | uses: actions/setup-java@v4 22 | with: 23 | distribution: microsoft 24 | java-version: 21 25 | 26 | - name: Install Sonar Scanner 27 | run: dotnet tool install --global dotnet-sonarscanner 28 | 29 | - name: Install dependencies 30 | run: dotnet restore 31 | 32 | - name: Start Sonar Analysis 33 | run: dotnet-sonarscanner begin /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /o:"ecoapm" /k:"ecoAPM_SimpleGPIO" /d:sonar.cs.vstest.reportsPaths="SimpleGPIO.Tests/**/results.trx" /d:sonar.cs.opencover.reportsPaths="SimpleGPIO.Tests/**/coverage.opencover.xml" /d:sonar.coverage.exclusions="Examples/**,SimpleGPIO/Device/**" 34 | 35 | - name: Build 36 | run: dotnet build --no-restore 37 | env: 38 | SONAR_DOTNET_ENABLE_CONCURRENT_EXECUTION: true 39 | 40 | - name: Test 41 | run: dotnet test --no-build --logger "trx;LogFileName=results.trx" --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover 42 | 43 | - name: Finish Sonar Analysis 44 | run: dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" 45 | env: 46 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.github/workflows/nuget.yml: -------------------------------------------------------------------------------- 1 | name: NuGet 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | Publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | ref: ${{ github.ref }} 14 | 15 | - uses: actions/setup-dotnet@v4 16 | with: 17 | dotnet-version: 9.0.x 18 | 19 | - name: Run tests 20 | run: dotnet test 21 | 22 | - name: Package 23 | run: dotnet pack -c Release -p:ContinuousIntegrationBuild=true 24 | 25 | - name: Publish 26 | run: dotnet nuget push SimpleGPIO/bin/Release/SimpleGPIO.$(echo ${{ github.ref }} | sed 's/refs\/tags\///').nupkg -k ${{ secrets.NUGET_TOKEN }} -s https://api.nuget.org/v3/index.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | /.vscode 332 | *.DotSettings 333 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | Steve@ecoAPM.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # ecoAPM Contribution Guidelines 2 | 3 | First of all, thank you for your interest in contributing! 4 | 5 | This document represents a general set of guidelines to help make the process of community contributions as smooth as possible for all parties involved. 6 | 7 | #### Please read the [Code of Conduct](CODE_OF_CONDUCT.md) prior to participating 8 | - This is the standard "Contributor Covenant" used throughout all ecoAPM codebases, and widely across the OSS landscape 9 | - Building a strong, professional, caring, and empathetic community is paramount in our goal as an OSS company 10 | 11 | #### Discussions about changes should happen in an issue before creating a pull request 12 | - While a change may make sense for a specific use case, it may not match the larger goals of the project as initially formulated by the original contributor 13 | - Prior discussion can help give direction to how a feature or bug fix could best be implemented to meet everyone's needs 14 | 15 | #### Follow the standard issue template formats for reporting bugs and requesting new features 16 | - These make reading, understanding, and triaging issues much easier 17 | 18 | #### Commit quality code with detailed documentation to help maximize PR review effectiveness 19 | - All new or modified functionality should have unit tests covering the logic involved 20 | - All PR checks (e.g. automated tests, code quality analysis, etc.) should be passing before a PR is reviewed 21 | - Commit messages should be English (Canadian/UK/US are all acceptable) in the present tense using an imperative form (see existing commits for examples) 22 | - Please do not reference GitHub issue numbers or PR numbers in git commit messages 23 | 24 | #### Multiple smaller, atomic PRs are preferable to single larger monolithic PRs 25 | - This may take longer to get the full changeset merged, but will provide for a much smoother feedback process 26 | - Please reference any related issue numbers in the body of all PR descriptions so that GitHub links them together 27 | -------------------------------------------------------------------------------- /Examples/CLI/CLI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | linux-arm 7 | latest 8 | enable 9 | enable 10 | SimpleGPIO.Examples.CLI 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Examples/CLI/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | 3 | var pi = new RaspberryPi(); 4 | var redLED = pi.Pin16; 5 | redLED.TurnOn(); 6 | 7 | await Task.Delay(TimeSpan.FromSeconds(5)); 8 | 9 | // I always pick up my playthings, and so 10 | // I will show you another good trick that I know 11 | pi.Dispose(); -------------------------------------------------------------------------------- /Examples/Components/DotMatrix/DotMatrix.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | enable 6 | enable 7 | SimpleGPIO.Examples.Components.DotMatrix 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Components/DotMatrix/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | using SimpleGPIO.Components; 3 | using SimpleGPIO.Power; 4 | 5 | using var pi = new RaspberryPi(); 6 | var matrix = new DotMatrix( 7 | new DotMatrix.PinSet 8 | { 9 | //rows 10 | Pin1 = pi.GPIO5, Pin2 = pi.GPIO7, Pin3 = pi.GPIO12, Pin4 = pi.GPIO13, Pin5 = pi.GPIO8, Pin6 = pi.GPIO15, Pin7 = pi.GPIO6, Pin8 = pi.GPIO3, 11 | 12 | //columns 13 | Pin9 = pi.GPIO1, Pin10 = pi.GPIO14, Pin11 = pi.GPIO16, Pin12 = pi.GPIO4, Pin13 = pi.GPIO11, Pin14 = pi.GPIO2, Pin15 = pi.GPIO17, Pin16 = pi.GPIO18 14 | } 15 | ); 16 | 17 | matrix.SetAllRows(PowerValue.Off); 18 | matrix.SetAllColumns(PowerValue.On); 19 | 20 | for (var x = 0; x < 8; x++) 21 | { 22 | if (x > 0) 23 | matrix.Row[x - 1].TurnOff(); 24 | 25 | matrix.Row[x].TurnOn(); 26 | await Task.Delay(100); 27 | } 28 | 29 | matrix.SetAllColumns(PowerValue.Off); 30 | matrix.SetAllRows(PowerValue.On); 31 | 32 | for (var x = 0; x < 8; x++) 33 | { 34 | if (x > 0) 35 | matrix.Column[x - 1].TurnOff(); 36 | 37 | matrix.Column[x].TurnOn(); 38 | await Task.Delay(100); 39 | } 40 | 41 | for (var x = 0; x < 3; x++) 42 | { 43 | matrix.SetAll(PowerValue.Off); 44 | await Task.Delay(100); 45 | 46 | matrix.SetRows(new DotMatrix.PowerSet { Item1 = PowerValue.Off, Item2 = PowerValue.Off, Item3 = PowerValue.Off, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.Off, Item7 = PowerValue.Off, Item8 = PowerValue.Off }); 47 | matrix.SetColumns(new DotMatrix.PowerSet { Item1 = PowerValue.Off, Item2 = PowerValue.Off, Item3 = PowerValue.Off, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.Off, Item7 = PowerValue.Off, Item8 = PowerValue.Off }); 48 | await Task.Delay(100); 49 | 50 | matrix.SetRows(new DotMatrix.PowerSet { Item1 = PowerValue.Off, Item2 = PowerValue.Off, Item3 = PowerValue.On, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.On, Item7 = PowerValue.Off, Item8 = PowerValue.Off }); 51 | matrix.SetColumns(new DotMatrix.PowerSet { Item1 = PowerValue.Off, Item2 = PowerValue.Off, Item3 = PowerValue.On, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.On, Item7 = PowerValue.Off, Item8 = PowerValue.Off }); 52 | await Task.Delay(100); 53 | 54 | matrix.SetRows(new DotMatrix.PowerSet { Item1 = PowerValue.Off, Item2 = PowerValue.On, Item3 = PowerValue.On, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.On, Item7 = PowerValue.On, Item8 = PowerValue.Off }); 55 | matrix.SetColumns(new DotMatrix.PowerSet { Item1 = PowerValue.Off, Item2 = PowerValue.On, Item3 = PowerValue.On, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.On, Item7 = PowerValue.On, Item8 = PowerValue.Off }); 56 | await Task.Delay(100); 57 | 58 | matrix.SetRows(new DotMatrix.PowerSet { Item1 = PowerValue.On, Item2 = PowerValue.On, Item3 = PowerValue.On, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.On, Item7 = PowerValue.On, Item8 = PowerValue.On }); 59 | matrix.SetColumns(new DotMatrix.PowerSet { Item1 = PowerValue.On, Item2 = PowerValue.On, Item3 = PowerValue.On, Item4 = PowerValue.On, Item5 = PowerValue.On, Item6 = PowerValue.On, Item7 = PowerValue.On, Item8 = PowerValue.On }); 60 | await Task.Delay(100); 61 | } 62 | 63 | await Task.Delay(100); 64 | matrix.SetAll(PowerValue.Off); -------------------------------------------------------------------------------- /Examples/Components/Motor/Motor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | enable 6 | enable 7 | SimpleGPIO.Examples.Components.Motor 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Components/Motor/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | using SimpleGPIO.Components; 3 | 4 | using var pi = new RaspberryPi(); 5 | var enabledPin = pi.Pin11; 6 | var clockwisePin = pi.Pin13; 7 | var counterclockwisePin = pi.Pin15; 8 | var motor = new Motor(enabledPin, clockwisePin, counterclockwisePin); 9 | 10 | await motor.TurnClockwiseFor(TimeSpan.FromSeconds(2)); 11 | //give it a second to fully stop before reversing 12 | await Task.Delay(TimeSpan.FromSeconds(1)); 13 | 14 | await motor.TurnCounterclockwiseFor(TimeSpan.FromSeconds(1), true); 15 | //give it some cooldown time before disposing, 16 | //as counterclockwisePin turning off will abruptly stop the motor 17 | await Task.Delay(TimeSpan.FromSeconds(2)); -------------------------------------------------------------------------------- /Examples/Components/RGBLED/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using SimpleGPIO.Boards; 3 | using SimpleGPIO.Components; 4 | 5 | using var pi = new RaspberryPi(); 6 | var led = new RGBLED(pi.GPIO5, pi.GPIO6, pi.GPIO13); 7 | var delay = TimeSpan.FromSeconds(0.5); 8 | 9 | led.SetColor(Color.Red); 10 | await Task.Delay(delay); 11 | 12 | led.SetColor(Color.Lime); 13 | await Task.Delay(delay); 14 | 15 | led.SetColor(Color.Blue); 16 | await Task.Delay(delay); 17 | 18 | await led.FadeTo(Color.Yellow, delay); 19 | await led.FadeTo(Color.Magenta, delay); 20 | await led.FadeOut(delay); -------------------------------------------------------------------------------- /Examples/Components/RGBLED/RGBLED.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | enable 6 | enable 7 | SimpleGPIO.Examples.Components.RGBLED 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Components/RotaryEncoder/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | using SimpleGPIO.Components; 3 | 4 | using var pi = new RaspberryPi(); 5 | var dial = new RotaryEncoder(pi.Pin11, pi.Pin13); 6 | 7 | var x = 0; 8 | dial.OnIncrease(() => Console.WriteLine(++x)); 9 | dial.OnDecrease(() => Console.WriteLine(--x)); 10 | 11 | Console.WriteLine("Press any key to exit..."); 12 | Console.ReadKey(); -------------------------------------------------------------------------------- /Examples/Components/RotaryEncoder/RotaryEncoder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Examples/Components/SevenSegmentDisplay/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | using SimpleGPIO.Components; 3 | 4 | using var pi = new RaspberryPi(); 5 | 6 | var segments = new SevenSegmentDisplay.PinSet 7 | { 8 | Center = pi.GPIO12, 9 | UpperLeft = pi.GPIO23, 10 | Top = pi.GPIO24, 11 | UpperRight = pi.GPIO25, 12 | LowerLeft = pi.GPIO17, 13 | Bottom = pi.GPIO27, 14 | LowerRight = pi.GPIO22, 15 | Decimal = pi.GPIO5 16 | }; 17 | var display = new SevenSegmentDisplay(segments); 18 | 19 | Console.WriteLine("Enter characters, or press enter to exit"); 20 | var keyInfo = new ConsoleKeyInfo(); 21 | do 22 | { 23 | display.Show(keyInfo.KeyChar); 24 | keyInfo = Console.ReadKey(); 25 | } while (keyInfo.Key != ConsoleKey.Enter); 26 | 27 | Console.WriteLine(); -------------------------------------------------------------------------------- /Examples/Components/SevenSegmentDisplay/SevenSegmentDisplay.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | enable 6 | enable 7 | SimpleGPIO.Examples.Components.SevenSegmentDisplay 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Components/ShiftRegister/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | using SimpleGPIO.Components; 3 | 4 | using var pi = new RaspberryPi(); 5 | var register = new ShiftRegister(pi.Pin13, pi.Pin11, pi.Pin15, pi.Pin16, pi.Pin18); 6 | 7 | for (byte x = 0; x < byte.MaxValue; x++) 8 | { 9 | Console.WriteLine(x); 10 | register.SetValue(x); 11 | await Task.Delay(200); 12 | } 13 | 14 | await Task.Delay(400); 15 | register.Clear(); -------------------------------------------------------------------------------- /Examples/Components/ShiftRegister/ShiftRegister.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | enable 6 | enable 7 | SimpleGPIO.Examples.Components.ShiftRegister 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Input/Input.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | enable 6 | enable 7 | SimpleGPIO.Examples.Input 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Input/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | 3 | using var pi = new RaspberryPi(); 4 | var button = pi.Pin11; 5 | var led = pi.Pin16; 6 | button.OnPowerOn(() => led.Toggle()); 7 | 8 | Console.WriteLine("Press any key to exit..."); 9 | Console.ReadKey(); -------------------------------------------------------------------------------- /Examples/PWM/PWM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SimpleGPIO.Examples.PWM 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Examples/PWM/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | 3 | var pi = new RaspberryPi(); 4 | var led = pi.GPIO17; 5 | var delay = TimeSpan.FromSeconds(1); 6 | 7 | await led.FadeIn(delay); 8 | 9 | led.Strength = 25; 10 | await Task.Delay(delay); 11 | 12 | await led.FadeTo(75, delay); 13 | 14 | led.Strength = 50; 15 | await Task.Delay(delay); 16 | 17 | await led.FadeOut(delay); 18 | pi.Dispose(); -------------------------------------------------------------------------------- /Examples/Web/Controllers/PiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using SimpleGPIO.Boards; 3 | using SimpleGPIO.GPIO; 4 | 5 | namespace SimpleGPIO.Examples.Web.Controllers; 6 | 7 | [ApiController] 8 | [Route("")] 9 | public sealed class PiController : ControllerBase 10 | { 11 | private readonly IPinInterface _redLED; 12 | private readonly IPinInterface _yellowLED; 13 | private readonly IPinInterface _greenLED; 14 | 15 | public PiController(RaspberryPi pi) 16 | { 17 | _redLED = pi.Pin16; 18 | _yellowLED = pi.Pin18; 19 | _greenLED = pi.Pin22; 20 | } 21 | 22 | [HttpPost("red")] 23 | public void Red() => _redLED.Toggle(); 24 | 25 | [HttpPost("yellow")] 26 | public void Yellow() => _yellowLED.Toggle(); 27 | 28 | [HttpPost("green")] 29 | public void Green() => _greenLED.Toggle(); 30 | } -------------------------------------------------------------------------------- /Examples/Web/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | 3 | namespace SimpleGPIO.Examples.Web; 4 | 5 | public static class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | WebHost.CreateDefaultBuilder(args) 10 | .UseStartup().Build().Run(); 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/Web/Startup.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | 3 | namespace SimpleGPIO.Examples.Web; 4 | 5 | public sealed class Startup 6 | { 7 | public IConfiguration Configuration { get; } 8 | 9 | public Startup(IConfiguration configuration) => Configuration = configuration; 10 | 11 | public static void ConfigureServices(IServiceCollection services) 12 | { 13 | services.AddMvc(); 14 | services.AddSingleton(); 15 | } 16 | 17 | public static void Configure(IApplicationBuilder app) 18 | { 19 | app.UseDefaultFiles(); 20 | app.UseStaticFiles(); 21 | app.UseRouting(); 22 | app.UseEndpoints(c => c.MapControllers()); 23 | } 24 | } -------------------------------------------------------------------------------- /Examples/Web/Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | enable 5 | enable 6 | SimpleGPIO.Examples.Web 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Examples/Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Web/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SimpleGPIO Web Example 6 | 7 | 8 | 9 | 10 |
11 |

SimpleGPIO Web Example

12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ecoAPM LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimpleGPIO 2 | 3 | A simple, low-ceremony GPIO library for all your IoT needs 4 | 5 | [![NuGet version](https://img.shields.io/nuget/v/SimpleGPIO?logo=nuget&label=Install)](https://nuget.org/packages/SimpleGPIO) 6 | [![CI](https://github.com/ecoAPM/SimpleGPIO/actions/workflows/CI.yml/badge.svg)](https://github.com/ecoAPM/SimpleGPIO/actions/workflows/CI.yml) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ecoAPM_SimpleGPIO&metric=coverage)](https://sonarcloud.io/dashboard?id=ecoAPM_SimpleGPIO) 8 | 9 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ecoAPM_SimpleGPIO&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=ecoAPM_SimpleGPIO) 10 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=ecoAPM_SimpleGPIO&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=ecoAPM_SimpleGPIO) 11 | [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=ecoAPM_SimpleGPIO&metric=security_rating)](https://sonarcloud.io/dashboard?id=ecoAPM_SimpleGPIO) 12 | 13 | ## Overview 14 | 15 | `SimpleGPIO` takes a high-level, object-oriented approach to IoT programming, in the same way that high-level programming languages provide features that help abstract what's happening on the metal away from the code. 16 | 17 | ## Requirements 18 | 19 | `SimpleGPIO` is a .NET Standard 2.0 library, and therefore should work with all actively supported .NET runtimes. 20 | 21 | ## Installation 22 | 23 | Simply add the `SimpleGPIO` library to your project from NuGet. 24 | 25 | ## Initialization 26 | 27 | Instantiate a new board to be able to access its GPIO header: 28 | ```C# 29 | var pi = new RaspberryPi(); 30 | ``` 31 | 32 | If you're using a dependency injection container, you can register the board as a singleton to be used elsewhere in the application: 33 | ```C# 34 | services.AddSingleton(); 35 | ``` 36 | 37 | ## Accessing GPIO pins 38 | 39 | GPIO pins can be accessed by both their physical location on the board, and/or their Broadcom identifier GPIO#. 40 | ```C# 41 | var redLED = pi.Pin16; 42 | var sameRedLED = pi.GPIO23; 43 | ``` 44 | 45 | ## Moving electrons 46 | 47 | `SimpleGPIO` provides many ways to turn power on or off, depending on your preferences. 48 | 49 | The simplest way is to use the built-in helper methods: 50 | ```C# 51 | redLED.TurnOn(); 52 | redLED.TurnOff(); 53 | ``` 54 | 55 | If you prefer assigning values: 56 | ```C# 57 | redLED.Power = PowerValue.On; 58 | redLED.Power = PowerValue.Off; 59 | ``` 60 | 61 | At the lowest level, you can directly set the voltage going out of the pin: 62 | ```C# 63 | redLED.Voltage = Voltage.High; //on 64 | redLED.Voltage = Voltage.Low; //off 65 | ``` 66 | 67 | ### Power Modes 68 | 69 | All of the above examples assume the default `Direct` power mode, where the positive terminal of the LED is connected to the GPIO pin, and the negative terminal is connected to the ground pin. 70 | 71 | If, instead, you want to supply constant power by, e.g. the 3v3 pin, and the have the GPIO pin supply (or not supply) resistance, you can use the `Differential` power mode, where `PowerValue.On == Voltage.Low` and `PowerValue.Off == Voltage.High`: 72 | ```C# 73 | var yellowLED = pi.Pin18; 74 | yellowLED.PowerMode = PowerMode.Differential; 75 | yellowLED.TurnOn(); 76 | ``` 77 | 78 | ## Timed Power 79 | 80 | Pins can be turned on or off for specific lengths of time via the following: 81 | ```C# 82 | var led = pi.Pin18; 83 | led.TurnOnFor(TimeSpan.FromSeconds(1)); //will turn off after 1 second 84 | 85 | led.TurnOn(); 86 | led.TurnOffFor(TimeSpan.FromSeconds(0.5)); //will turn back on after 0.5 seconds 87 | ``` 88 | 89 | ## Techno Dance Parties 90 | 91 | There are some helper methods for toggling values. If power is currently on, toggling it will turn it off; if power is off, toggling will turn it on: 92 | ```C# 93 | redLED.Toggle(); 94 | ``` 95 | 96 | If you want to repeat the toggle at a given frequency, for a set amount of time, pass in the frequency and a `TimeSpan` as parameters: 97 | ```C# 98 | redLED.Toggle(3, TimeSpan.FromSeconds(5)); 99 | ``` 100 | This will flash the red LED 3 times per second, for 5 seconds. 101 | 102 | Alternatively, you can toggle power a set number of times by passing in a number as the second parameter. The following will flash the red LED 3 times over 1.5 seconds: 103 | ```C# 104 | redLED.Toggle(2, 3); 105 | ``` 106 | 107 | ### Pulse Width Modulation 108 | 109 | Pins can have their "strength" set using Pulse Width Modulation (PWM) via the `Strength` property on the pin: 110 | ```C# 111 | var led = pi.Pin18; 112 | led.Strength = 50; //valid range: 0-100 113 | ``` 114 | 115 | There are also several helper methods to make smooth transitions easier: 116 | ```C# 117 | led.FadeIn(TimeSpan.FromSeconds(1)); 118 | led.FadeOut(TimeSpan.FromSeconds(2)); 119 | led.FadeTo(50,TimeSpan.FromSeconds(0.5)); 120 | 121 | led.Pulse(TimeSpan.FromSeconds(1)); 122 | led.Pulse(50, TimeSpan.FromSeconds(1)); 123 | ``` 124 | 125 | ## What about inputs? 126 | 127 | Input components such as buttons can be declared the same way as output components, and the `Power` and `Voltage` can be read from the new variable: 128 | ```C# 129 | var button = pi.Pin11; 130 | var isPressed = button.Power == PowerValue.On; 131 | ``` 132 | 133 | The `Direct` Power Mode for an input component expects power from e.g. the 3v3 pin, so that electricity flows through to the GPIO pin when the button is depressed. 134 | 135 | ## Reacting to Change 136 | 137 | Three methods are provided on a pin that accept an `Action` as a parameter, so that when that pin's state changes, some subsequent steps can be performed: 138 | ```C# 139 | var button = pi.Pin11; 140 | var redLED = pi.Pin16; 141 | var buzzer = pi.Pin18; 142 | 143 | button.OnPowerOn(() => redLED.TurnOn()); 144 | button.OnPowerOff(() => redLED.TurnOff()); 145 | redLED.OnPowerChange(() => buzzer.Toggle(1, 1)); 146 | ``` 147 | 148 | Whenever the button is pressed down, the LED will turn on. When the button is released, the LED will turn off. Whenever the LED turns on or off, the buzzer will beep for half a second (reminder why: because Toggle will complete a single cycle at 1Hz, which means 0.5s on, then 0.5s off). 149 | 150 | ## Cleaning up 151 | 152 | If you want to turn off everything that was turned on while your application was running, simply `Dispose()` of your `RaspberryPi` at the end of your code. 153 | 154 | ```C# 155 | pi.Dispose(); 156 | ``` 157 | 158 | This will turn off and close all open GPIO pins. As with all `IDisposable`s, this also works if you wrap the `RaspberryPi` you're using in a `using(){}` block. 159 | 160 | ## Components 161 | 162 | Several components have been implemented to help make development easier. 163 | 164 | ### RGB LED 165 | 166 | The RGB LED contains a separate pin for red, green, and blue, which can be combined to show different colors. 167 | 168 | ```C# 169 | var redPin = pi.Pin11; 170 | var greenPin = pi.Pin16; 171 | var bluePin = pi.Pin18; 172 | 173 | var rgbLED = new RGBLED(redPin, greenPin, bluePin); 174 | ``` 175 | 176 | Colors can then be set using the `SetColor()` method: 177 | ```C# 178 | rgbLED.SetColor(Color.Red); 179 | rgbLED.SetColor(Color.Yellow); 180 | rgbLED.SetColor(Color.Purple); 181 | ``` 182 | 183 | Several helpers also exist: 184 | ```C# 185 | rgbLED.FadeTo(Color.White, TimeSpan.FromSeconds(1)); 186 | rgbLED.Pulse(Color.Green, TimeSpan.FromSeconds(0.5)); 187 | rgbLED.TurnOff(); // same as rgbLED.SetColor(Color.Black); 188 | ``` 189 | 190 | See [the example](Examples/Components/RGBLED/Program.cs) for more details. 191 | 192 | ### Rotary Encoder 193 | 194 | Rotary encoders have actions that can be performed when the dial is turned. 195 | 196 | ```C# 197 | var dial = new RotaryEncoder(clockPin, dataPin); 198 | dial.OnIncrease(() => Console.WriteLine("up")); 199 | dial.OnDecrease(() => Console.WriteLine("down")); 200 | ``` 201 | 202 | Built-in button functionality is not yet supported. 203 | 204 | See [the example](Examples/Components/RotartEncoder/Program.cs) for more details. 205 | 206 | ### Seven-Segment Display 207 | 208 | Seven-segment displays are currently supported for direct connections to GPIO pins (support for shift register input coming soon) and can be passed a character (all ASCII letters, numbers, and several other symbols) 209 | 210 | ```C# 211 | var segments = new PinSet 212 | { 213 | Center = centerPin, 214 | UpperLeft = upperLeftPin, 215 | Top = topPin, 216 | UpperRight = upperRightPin, 217 | LowerLeft = lowerLeftPin, 218 | Bottom = bottomPin, 219 | LowerRight = lowerRightPin, 220 | Decimal = decimalPin //optional 221 | }; 222 | var display = new SevenSegmentDisplay(segments); 223 | display.Show('A'); 224 | display.Show('B'); 225 | display.Show('C'); 226 | display.Show('1'); 227 | display.Show('2'); 228 | display.Show('3'); 229 | ``` 230 | 231 | Custom characters can also be displayed passing a `PowerSet` to `SetPowerValues()`: 232 | 233 | ```C# 234 | var custom = new PowerSet 235 | { 236 | Center = PowerValue.On, 237 | UpperLeft = PowerValue.Off, 238 | Top = PowerValue.On, 239 | UpperRight = PowerValue.Off, 240 | LowerLeft = PowerValue.On, 241 | Bottom = PowerValue.Off, 242 | LowerRight = PowerValue.On, 243 | Decimal = PowerValue.Off //optional 244 | }; 245 | display.SetPowerValues(custom); 246 | ``` 247 | 248 | See [the example](Examples/Components/SevenSegmentDisplay/Program.cs) for more details. 249 | 250 | ### Dot Matrix Display 251 | 252 | The dot matrix display required 16 inputs, one for each row and one for each column. Like the seven segment display, these are initialized via a `PinSet`, with pins numbered counter-clockwise starting from the bottom left: 253 | ```C# 254 | var set = new DotMatrix.PinSet 255 | { 256 | //rows 257 | Pin1 = pi.GPIO5, Pin2 = pi.GPIO7, Pin3 = pi.GPIO12, Pin4 = pi.GPIO13, Pin5 = pi.GPIO8, Pin6 = pi.GPIO15, Pin7 = pi.GPIO6, Pin8 = pi.GPIO3, 258 | 259 | //columns 260 | Pin9 = pi.GPIO1, Pin10 = pi.GPIO14, Pin11 = pi.GPIO16, Pin12 = pi.GPIO4, Pin13 = pi.GPIO11, Pin14 = pi.GPIO2, Pin15 = pi.GPIO17, Pin16 = pi.GPIO18 261 | }; 262 | var matrix = new DotMatrix(set); 263 | ``` 264 | 265 | You can then set rows or columns individually or all together: 266 | ```C# 267 | matrix.SetAllRows(PowerValue.On); 268 | matrix.SetAllColumns(PowerValue.Off); 269 | 270 | matrix.SetRows(new DotMatrix.PowerSet { ... }); 271 | matrix.SetColumns(new DotMatrix.PowerSet { ... }); 272 | ``` 273 | 274 | See [the example](Examples/Components/DotMatrix/Program.cs) for more details. 275 | 276 | ### Bidirectional Motor 277 | 278 | The wiring required to safely run a motor is rather complicated. The code, however, can be quite eloquent. The `Motor` component assumes an L293D-compatible driver. 279 | 280 | ```C# 281 | var enabledPin = pi.Pin11; 282 | var clockwisePin = pi.Pin13; //name assumes connected to L293D pin 1A 283 | var counterclockwisePin = pi.Pin15; // name assumes connected to L293D pin 2A 284 | var motor = new Motor(enabledPin, clockwisePin, counterclockwisePin); 285 | motor.Direction = Rotation.Clockwise; 286 | motor.Start(); 287 | motor.Stop(); 288 | 289 | motor.Direction = Rotation.Counterclockwise; 290 | motor.Start(); 291 | motor.Coast(); 292 | 293 | motor.TurnClockwise(); 294 | motor.TurnCounterclockwise(); 295 | motor.TurnClockwiseFor(TimeSpan.FromSeconds(1)); 296 | motor.TurnCounterclockwiseFor(TimeSpan.FromSeconds(2), true); //optional parameter to coast instead of stop 297 | ``` 298 | 299 | If you never need to coast (only stop) and your `enabled` pin is always on (e.g. 3.3 or 5V), you can pass `null` as the first constructor parameter. 300 | 301 | If using all 4 inputs on a single driver, declare another `Motor` to handle inputs 3 and 4. 302 | 303 | To drive a single-direction motor (by only having input 1 connected), simply pass `null` as the `counterclockwisePin` to the `Motor` constructor. Counterclockwise methods are not expected to function under this condition. 304 | 305 | See [the example](Examples/Components/Motor/Program.cs) for more details. 306 | 307 | ### Shift Register 308 | 309 | A shift register allows you to control more outputs than you have inputs. The `ShiftRegister` component abstracts the implementation details of the 595-style integrated circuit away, so you can simply send the data you want as a `byte`! 310 | 311 | ```C# 312 | var enabledPin = pi.Pin11; 313 | var dataPin = pi.Pin13; 314 | var shiftPin = pi.Pin15; 315 | var outputPin = pi.Pin16; //aka "latch" 316 | var clearPin = pi.Pin18; 317 | var register = new ShiftRegister(enabledPin, dataPin, shiftPin, outputPin, clearPin); 318 | 319 | register.SetValue(255); //sets all 8 bits to On 320 | register.SetValue(0b11111111); //does the same 321 | 322 | //these two are also identical to each other 323 | register.SetValue(0b10101010); 324 | var values = new PowerSet 325 | { 326 | A = PowerValue.On, 327 | B = PowerValue.Off, 328 | C = PowerValue.On, 329 | D = PowerValue.Off, 330 | E = PowerValue.On, 331 | F = PowerValue.Off, 332 | G = PowerValue.On, 333 | H = PowerValue.Off 334 | }; 335 | register.SetPowerValues(values); 336 | 337 | //you can also accomplish this more manually 338 | dataPin.TurnOn(); 339 | shiftPin.Spike(); 340 | dataPin.TurnOff(); 341 | shiftPin.Spike(); 342 | outputPin.Spike(); 343 | ``` 344 | 345 | Similar to the `Motor` component, the `enabled` and `clear` parameters are optional; if you choose to have the register always on/enabled by connecting it to the 3.3 or 5V rail, set the first param to `null`. If you never need to clear, and that pin is also connected to 3.3 or 5V, just leave the last param out. 346 | 347 | See [the example](Examples/Components/ShiftRegister/Program.cs) for more details. 348 | 349 | ## "Wow, this is great! How can I help?" 350 | 351 | First, thank you for your enthusiasm! I'd love feedback on how you felt using this. If you had an awesome experience, let me know on [Twitter](https://twitter.com/intent/tweet?text=.@stevedesmond_ca&hashtags=SimpleGPIO). If you had any problems, feel free to [file an issue](https://github.com/stevedesmond-ca/SimpleGPIO/issues/new). 352 | 353 | If you're looking to contribute code, but don't have any ideas of your own, there are some specific things I'd love help with over in the Issues tab! 354 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security updates are generally only applied to the newest release, and backported on an as-needed basis where appropriate. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Unless a vulnerability is deemed critical or exposes PII, please create an issue in this repository using the "Report a Bug" template. 10 | 11 | For critical vulnerabilities, or those that expose PII, please email info@ecoAPM.com so that the issue can be fixed confidentially, prior to public disclosure. 12 | -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Boards/BroadcomBoardTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute; 2 | using SimpleGPIO.GPIO; 3 | using SimpleGPIO.Tests.GPIO; 4 | using Xunit; 5 | 6 | namespace SimpleGPIO.Tests.Boards; 7 | 8 | public sealed class BroadcomBoardTests 9 | { 10 | [Fact] 11 | public void CanGetPinInterface() 12 | { 13 | //arrange 14 | var newPin = Substitute.For>(); 15 | var board = new BroadcomStub(newPin); 16 | 17 | //act 18 | var pin0 = board.GPIO0; 19 | 20 | //assert 21 | Assert.IsAssignableFrom(pin0); 22 | } 23 | 24 | [Fact] 25 | public void PinInterfaceIsCached() 26 | { 27 | //arrange 28 | var newPin = Substitute.For>(); 29 | var board = new BroadcomStub(newPin); 30 | var pin0 = board.GPIO0; 31 | 32 | //act 33 | var pin1 = board.GPIO0; 34 | 35 | //assert 36 | Assert.Equal(pin0, pin1); 37 | newPin.Received(1).Invoke(0); 38 | } 39 | 40 | [Fact] 41 | public void PinsAreMappedCorrectly() 42 | { 43 | //arrange 44 | var newPin = Substitute.For>(); 45 | newPin.Invoke(Arg.Any()).Returns(p => new PinStub(p.Arg())); 46 | var board = new BroadcomStub(newPin); 47 | var pins = new PinStub[28]; 48 | 49 | //act 50 | for (var x = 0; x < 28; x++) 51 | pins[x] = (PinStub)board.GetType().GetProperty($"GPIO{x}")?.GetValue(board)!; 52 | 53 | //assert 54 | for (var x = 0; x < 28; x++) 55 | Assert.Equal(x, pins[x].Pin); 56 | } 57 | 58 | [Fact] 59 | public void DisposesAllPins() 60 | { 61 | //arrange 62 | var newPin = Substitute.For>(); 63 | var pin = Substitute.For(); 64 | newPin.Invoke(Arg.Any()).Returns(_ => pin); 65 | var board = new BroadcomStub(newPin); 66 | for (var x = 0; x < 28; x++) 67 | board.GetType().GetProperty($"GPIO{x}")?.GetValue(board); 68 | 69 | //act 70 | board.Dispose(); 71 | 72 | //assert 73 | pin.Received(28).Dispose(); 74 | } 75 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Boards/BroadcomStub.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Boards; 2 | using SimpleGPIO.GPIO; 3 | 4 | namespace SimpleGPIO.Tests.Boards; 5 | 6 | internal sealed class BroadcomStub : BroadcomBoard 7 | { 8 | public BroadcomStub(Func pinInterfaceFactory) : base(pinInterfaceFactory) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Boards/RaspberryPiTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute; 2 | using SimpleGPIO.Boards; 3 | using SimpleGPIO.GPIO; 4 | using SimpleGPIO.Tests.GPIO; 5 | using Xunit; 6 | 7 | namespace SimpleGPIO.Tests.Boards; 8 | 9 | public sealed class RaspberryPiTests 10 | { 11 | // courtesy of the "pinout" python command 12 | // with some regex magic 13 | [Theory] 14 | [InlineData(3, 2)] 15 | [InlineData(5, 3)] 16 | [InlineData(7, 4)] 17 | [InlineData(8, 14)] 18 | [InlineData(10, 15)] 19 | [InlineData(11, 17)] 20 | [InlineData(12, 18)] 21 | [InlineData(13, 27)] 22 | [InlineData(15, 22)] 23 | [InlineData(16, 23)] 24 | [InlineData(18, 24)] 25 | [InlineData(19, 10)] 26 | [InlineData(21, 9)] 27 | [InlineData(22, 25)] 28 | [InlineData(23, 11)] 29 | [InlineData(24, 8)] 30 | [InlineData(26, 7)] 31 | [InlineData(27, 0)] 32 | [InlineData(28, 1)] 33 | [InlineData(29, 5)] 34 | [InlineData(31, 6)] 35 | [InlineData(32, 12)] 36 | [InlineData(33, 13)] 37 | [InlineData(35, 19)] 38 | [InlineData(36, 16)] 39 | [InlineData(37, 26)] 40 | [InlineData(38, 20)] 41 | [InlineData(40, 21)] 42 | public void PinsAreMappedCorrectly(byte physical, byte bcm) 43 | { 44 | //arrange 45 | var newPin = Substitute.For>(); 46 | newPin.Invoke(Arg.Any()).Returns(p => new PinStub(p.Arg())); 47 | var board = new RaspberryPi(newPin); 48 | 49 | //act 50 | var pin = (PinStub)board.GetType().GetProperty($"Pin{physical}")?.GetValue(board)!; 51 | 52 | //assert 53 | Assert.Equal(bcm, pin.Pin); 54 | } 55 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Components/DotMatrixTests.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Components; 2 | using SimpleGPIO.Power; 3 | using SimpleGPIO.Tests.GPIO; 4 | using Xunit; 5 | 6 | namespace SimpleGPIO.Tests.Components; 7 | 8 | public sealed class DotMatrixTests 9 | { 10 | private static DotMatrix.PinSet StubPinSet(PinStub[] pins) 11 | { 12 | return new DotMatrix.PinSet 13 | { 14 | Pin1 = pins[1], 15 | Pin2 = pins[2], 16 | Pin3 = pins[3], 17 | Pin4 = pins[4], 18 | Pin5 = pins[5], 19 | Pin6 = pins[6], 20 | Pin7 = pins[7], 21 | Pin8 = pins[8], 22 | 23 | Pin9 = pins[9], 24 | Pin10 = pins[10], 25 | Pin11 = pins[11], 26 | Pin12 = pins[12], 27 | Pin13 = pins[13], 28 | Pin14 = pins[14], 29 | Pin15 = pins[15], 30 | Pin16 = pins[16] 31 | }; 32 | } 33 | 34 | private static PinStub[] StubPins() 35 | { 36 | const byte numPins = 16; 37 | var pins = new PinStub[numPins + 1]; 38 | for (byte x = 1; x <= numPins; x++) 39 | pins[x] = new PinStub(x); 40 | 41 | return pins; 42 | } 43 | 44 | [Fact] 45 | public void RowsAndColumnsAreSetFromInput() 46 | { 47 | //arrange 48 | var pins = StubPins(); 49 | var pinSet = StubPinSet(pins); 50 | 51 | //act 52 | var matrix = new DotMatrix(pinSet); 53 | 54 | //assert 55 | Assert.Equal(pins[9], matrix.Row[0]); 56 | Assert.Equal(pins[14], matrix.Row[1]); 57 | Assert.Equal(pins[8], matrix.Row[2]); 58 | Assert.Equal(pins[12], matrix.Row[3]); 59 | Assert.Equal(pins[1], matrix.Row[4]); 60 | Assert.Equal(pins[7], matrix.Row[5]); 61 | Assert.Equal(pins[2], matrix.Row[6]); 62 | Assert.Equal(pins[5], matrix.Row[7]); 63 | 64 | Assert.Equal(pins[13], matrix.Column[0]); 65 | Assert.Equal(pins[3], matrix.Column[1]); 66 | Assert.Equal(pins[4], matrix.Column[2]); 67 | Assert.Equal(pins[10], matrix.Column[3]); 68 | Assert.Equal(pins[6], matrix.Column[4]); 69 | Assert.Equal(pins[11], matrix.Column[5]); 70 | Assert.Equal(pins[15], matrix.Column[6]); 71 | Assert.Equal(pins[16], matrix.Column[7]); 72 | } 73 | 74 | [Fact] 75 | public void CanTurnOnAllRows() 76 | { 77 | //arrange 78 | var pins = StubPins(); 79 | var pinSet = StubPinSet(pins); 80 | var matrix = new DotMatrix(pinSet); 81 | 82 | //act 83 | matrix.SetAllRows(PowerValue.On); 84 | 85 | //assert 86 | for (var x = 0; x < 8; x++) 87 | Assert.Equal(PowerValue.On, matrix.Row[x].Power); 88 | } 89 | 90 | [Fact] 91 | public void CanTurnOnAllColumns() 92 | { 93 | //arrange 94 | var pins = StubPins(); 95 | var pinSet = StubPinSet(pins); 96 | var matrix = new DotMatrix(pinSet); 97 | 98 | //act 99 | matrix.SetAllColumns(PowerValue.On); 100 | 101 | //assert 102 | for (var x = 0; x < 8; x++) 103 | Assert.Equal(PowerValue.On, matrix.Column[x].Power); 104 | } 105 | 106 | [Fact] 107 | public void CanTurnOnAllPins() 108 | { 109 | //arrange 110 | var pins = StubPins(); 111 | var pinSet = StubPinSet(pins); 112 | var matrix = new DotMatrix(pinSet); 113 | 114 | //act 115 | matrix.SetAll(PowerValue.On); 116 | 117 | //assert 118 | for (var x = 0; x < 8; x++) 119 | { 120 | Assert.Equal(PowerValue.On, matrix.Row[x].Power); 121 | Assert.Equal(PowerValue.On, matrix.Column[x].Power); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Components/MotorTests.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Components; 2 | using SimpleGPIO.Power; 3 | using SimpleGPIO.Tests.GPIO; 4 | using Xunit; 5 | 6 | namespace SimpleGPIO.Tests.Components; 7 | 8 | public sealed class MotorTests 9 | { 10 | [Fact] 11 | public void StartWhenClockwiseSetsInputsCorrectly() 12 | { 13 | //arrange 14 | var enabled = new PinStub(1); 15 | var clockwise = new PinStub(2); 16 | var counterclockwise = new PinStub(3); 17 | var motor = new Motor(enabled, clockwise, counterclockwise) 18 | { 19 | Direction = Motor.Rotation.Clockwise 20 | }; 21 | 22 | //act 23 | motor.Start(); 24 | 25 | //assert 26 | Assert.Equal(PowerValue.On, enabled.Power); 27 | Assert.Equal(PowerValue.On, clockwise.Power); 28 | Assert.Equal(PowerValue.Off, counterclockwise.Power); 29 | } 30 | 31 | [Fact] 32 | public void StartWhenCounterclockwiseSetsInputsCorrectly() 33 | { 34 | //arrange 35 | var enabled = new PinStub(1); 36 | var clockwise = new PinStub(2); 37 | var counterclockwise = new PinStub(3); 38 | var motor = new Motor(enabled, clockwise, counterclockwise) 39 | { 40 | Direction = Motor.Rotation.Counterclockwise 41 | }; 42 | 43 | //act 44 | motor.Start(); 45 | 46 | //assert 47 | Assert.Equal(PowerValue.On, enabled.Power); 48 | Assert.Equal(PowerValue.Off, clockwise.Power); 49 | Assert.Equal(PowerValue.On, counterclockwise.Power); 50 | } 51 | 52 | [Fact] 53 | public void TurnClockwiseSetsDirection() 54 | { 55 | //arrange 56 | var enabled = new PinStub(1); 57 | var clockwise = new PinStub(2); 58 | var counterclockwise = new PinStub(3); 59 | var motor = new Motor(enabled, clockwise, counterclockwise) 60 | { 61 | Direction = Motor.Rotation.Counterclockwise 62 | }; 63 | 64 | //act 65 | motor.TurnClockwise(); 66 | 67 | //assert 68 | Assert.Equal(Motor.Rotation.Clockwise, motor.Direction); 69 | } 70 | 71 | [Fact] 72 | public void TurnCounterclockwiseSetsDirection() 73 | { 74 | //arrange 75 | var enabled = new PinStub(1); 76 | var clockwise = new PinStub(2); 77 | var counterclockwise = new PinStub(3); 78 | var motor = new Motor(enabled, clockwise, counterclockwise) 79 | { 80 | Direction = Motor.Rotation.Clockwise 81 | }; 82 | 83 | //act 84 | motor.TurnCounterclockwise(); 85 | 86 | //assert 87 | Assert.Equal(Motor.Rotation.Counterclockwise, motor.Direction); 88 | } 89 | 90 | [Fact] 91 | public async Task RunForStopsWhenDone() 92 | { 93 | //arrange 94 | var enabled = new PinStub(1); 95 | var clockwise = new PinStub(2); 96 | var counterclockwise = new PinStub(3); 97 | var motor = new Motor(enabled, clockwise, counterclockwise); 98 | 99 | //act 100 | await motor.RunFor(TimeSpan.Zero); 101 | 102 | //assert 103 | Assert.Equal(PowerValue.Off, clockwise.Power); 104 | Assert.Equal(PowerValue.Off, counterclockwise.Power); 105 | } 106 | 107 | [Fact] 108 | public async Task RunForCanCoastWhenDone() 109 | { 110 | //arrange 111 | var enabled = new PinStub(1); 112 | var clockwise = new PinStub(2); 113 | var counterclockwise = new PinStub(3); 114 | var motor = new Motor(enabled, clockwise, counterclockwise); 115 | 116 | //act 117 | await motor.RunFor(TimeSpan.Zero, true); 118 | 119 | //assert 120 | Assert.Equal(PowerValue.Off, enabled.Power); 121 | Assert.Equal(PowerValue.On, clockwise.Power); 122 | Assert.Equal(PowerValue.Off, counterclockwise.Power); 123 | } 124 | 125 | [Fact] 126 | public async Task TurnClockwiseForSetsDirection() 127 | { 128 | //arrange 129 | var enabled = new PinStub(1); 130 | var clockwise = new PinStub(2); 131 | var counterclockwise = new PinStub(3); 132 | var motor = new Motor(enabled, clockwise, counterclockwise); 133 | 134 | //act 135 | await motor.TurnClockwiseFor(TimeSpan.Zero); 136 | 137 | //assert 138 | Assert.Equal(Motor.Rotation.Clockwise, motor.Direction); 139 | } 140 | 141 | [Fact] 142 | public async Task TurnCounterclockwiseForSetsDirection() 143 | { 144 | //arrange 145 | var enabled = new PinStub(1); 146 | var clockwise = new PinStub(2); 147 | var counterclockwise = new PinStub(3); 148 | var motor = new Motor(enabled, clockwise, counterclockwise); 149 | 150 | //act 151 | await motor.TurnCounterclockwiseFor(TimeSpan.Zero); 152 | 153 | //assert 154 | Assert.Equal(Motor.Rotation.Counterclockwise, motor.Direction); 155 | } 156 | 157 | [Fact] 158 | public void StopTurnsOffAllPins() 159 | { 160 | //arrange 161 | var enabled = new PinStub(1); 162 | var clockwise = new PinStub(2); 163 | var counterclockwise = new PinStub(3); 164 | var motor = new Motor(enabled, clockwise, counterclockwise); 165 | motor.Start(); 166 | 167 | //act 168 | motor.Stop(); 169 | 170 | //assert 171 | Assert.Equal(PowerValue.Off, enabled.Power); 172 | Assert.Equal(PowerValue.Off, clockwise.Power); 173 | Assert.Equal(PowerValue.Off, counterclockwise.Power); 174 | } 175 | 176 | [Fact] 177 | public void CoastKeepsInputsOn() 178 | { 179 | //arrange 180 | var enabled = new PinStub(1); 181 | var clockwise = new PinStub(2); 182 | var counterclockwise = new PinStub(3); 183 | var motor = new Motor(enabled, clockwise, counterclockwise); 184 | motor.Start(); 185 | 186 | //act 187 | motor.Coast(); 188 | 189 | //assert 190 | Assert.Equal(PowerValue.Off, enabled.Power); 191 | Assert.Equal(PowerValue.On, clockwise.Power); 192 | Assert.Equal(PowerValue.Off, counterclockwise.Power); 193 | } 194 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Components/RGBLEDTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Drawing; 3 | using SimpleGPIO.Components; 4 | using SimpleGPIO.Power; 5 | using SimpleGPIO.Tests.GPIO; 6 | using Xunit; 7 | 8 | namespace SimpleGPIO.Tests.Components; 9 | 10 | public sealed class RGBLEDTests 11 | { 12 | [Theory] 13 | [ClassData(typeof(ColorData))] 14 | public void ColorsMatch(Color color, double r, double g, double b) 15 | { 16 | //arrange 17 | var red = new PinStub(1); 18 | var green = new PinStub(2); 19 | var blue = new PinStub(3); 20 | var led = new RGBLED(red, green, blue); 21 | 22 | //act 23 | led.SetColor(color); 24 | 25 | //assert 26 | Assert.Equal(r, red.Strength); 27 | Assert.Equal(g, green.Strength); 28 | Assert.Equal(b, blue.Strength); 29 | 30 | Assert.Equal(r > 0 ? PowerValue.On : PowerValue.Off, red.Power); 31 | Assert.Equal(g > 0 ? PowerValue.On : PowerValue.Off, green.Power); 32 | Assert.Equal(b > 0 ? PowerValue.On : PowerValue.Off, blue.Power); 33 | } 34 | 35 | [Fact] 36 | public void TurnOffTurnsOffAllPins() 37 | { 38 | //arrange 39 | var red = new PinStub(1); 40 | var green = new PinStub(2); 41 | var blue = new PinStub(3); 42 | var led = new RGBLED(red, green, blue); 43 | 44 | //act 45 | led.TurnOff(); 46 | 47 | //assert 48 | Assert.Equal(PowerValue.Off, red.Power); 49 | Assert.Equal(PowerValue.Off, green.Power); 50 | Assert.Equal(PowerValue.Off, blue.Power); 51 | } 52 | 53 | [Fact] 54 | public async Task CanFadeToColor() 55 | { 56 | //arrange 57 | var red = new PinStub(1); 58 | var green = new PinStub(2); 59 | var blue = new PinStub(3); 60 | var led = new RGBLED(red, green, blue); 61 | 62 | //act 63 | var ecoGreen = Color.FromArgb(83, 141, 67); 64 | await led.FadeTo(ecoGreen, TimeSpan.Zero); 65 | 66 | //assert 67 | Assert.Equal(32.5, red.Strength, 1); 68 | Assert.Equal(55.3, green.Strength, 1); 69 | Assert.Equal(26.3, blue.Strength, 1); 70 | } 71 | 72 | [Fact] 73 | public async Task CanFadeOutToBlack() 74 | { 75 | //arrange 76 | var red = new PinStub(1); 77 | var green = new PinStub(2); 78 | var blue = new PinStub(3); 79 | var led = new RGBLED(red, green, blue); 80 | 81 | //act 82 | await led.FadeOut(TimeSpan.Zero); 83 | 84 | //assert 85 | Assert.Equal(PowerValue.Off, red.Power); 86 | Assert.Equal(PowerValue.Off, green.Power); 87 | Assert.Equal(PowerValue.Off, blue.Power); 88 | } 89 | 90 | [Fact] 91 | public async Task PulseFadesOutCompletely() 92 | { 93 | //arrange 94 | var red = new PinStub(1); 95 | var green = new PinStub(2); 96 | var blue = new PinStub(3); 97 | var led = new RGBLED(red, green, blue); 98 | 99 | //act 100 | await led.Pulse(Color.White, TimeSpan.Zero); 101 | 102 | //assert 103 | Assert.Equal(PowerValue.Off, red.Power); 104 | Assert.Equal(PowerValue.Off, green.Power); 105 | Assert.Equal(PowerValue.Off, blue.Power); 106 | } 107 | 108 | private sealed class ColorData : IEnumerable 109 | { 110 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 111 | 112 | public IEnumerator GetEnumerator() 113 | { 114 | yield return [Color.Red, 100, 0, 0]; 115 | yield return [Color.Yellow, 100, 100, 0]; 116 | yield return [Color.Lime, 0, 100, 0]; 117 | yield return [Color.Cyan, 0, 100, 100]; 118 | yield return [Color.Blue, 0, 0, 100]; 119 | yield return [Color.Magenta, 100, 0, 100]; 120 | yield return [Color.White, 100, 100, 100]; 121 | yield return [Color.Black, 0, 0, 0]; 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Components/RotaryEncoderTests.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Components; 2 | using SimpleGPIO.Tests.GPIO; 3 | using Xunit; 4 | 5 | namespace SimpleGPIO.Tests.Components; 6 | 7 | public sealed class RotaryEncoderTests 8 | { 9 | [Fact] 10 | public void OnIncreasePerformsActionWhenSet() 11 | { 12 | //arrange 13 | var increasePin = new PinStub(1); 14 | var decreasePin = new PinStub(2); 15 | var dial = new RotaryEncoder(increasePin, decreasePin); 16 | 17 | var called = false; 18 | dial.OnIncrease(() => called = true); 19 | 20 | //act 21 | increasePin.Spike(); 22 | 23 | //assert 24 | Assert.True(called); 25 | } 26 | 27 | [Fact] 28 | public void OnDecreasePerformsActionWhenSet() 29 | { 30 | //arrange 31 | var increasePin = new PinStub(1); 32 | var decreasePin = new PinStub(2); 33 | var dial = new RotaryEncoder(increasePin, decreasePin); 34 | var called = false; 35 | dial.OnDecrease(() => called = true); 36 | 37 | //act 38 | decreasePin.Spike(); 39 | 40 | //assert 41 | Assert.True(called); 42 | } 43 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Components/SevenSegmentDisplayTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using SimpleGPIO.Components; 3 | using SimpleGPIO.Power; 4 | using SimpleGPIO.Tests.GPIO; 5 | using Xunit; 6 | 7 | namespace SimpleGPIO.Tests.Components; 8 | 9 | public sealed class SevenSegmentDisplayTests 10 | { 11 | [Theory, ClassData(typeof(CharacterData))] 12 | public void TestCharacters(char character, string expected) 13 | { 14 | //arrange 15 | var segments = new SevenSegmentDisplay.PinSet 16 | { 17 | Center = new PinStub(1), 18 | UpperLeft = new PinStub(2), 19 | Top = new PinStub(3), 20 | UpperRight = new PinStub(4), 21 | LowerLeft = new PinStub(5), 22 | Bottom = new PinStub(6), 23 | LowerRight = new PinStub(7), 24 | Decimal = new PinStub(8) 25 | }; 26 | var display = new SevenSegmentDisplay(segments); 27 | 28 | //act 29 | display.Show(character); 30 | 31 | //assert 32 | Assert.Equal(expected[1] == '*' ? PowerValue.On : PowerValue.Off, segments.Top.Power); 33 | Assert.Equal(expected[3] == '*' ? PowerValue.On : PowerValue.Off, segments.UpperLeft.Power); 34 | Assert.Equal(expected[5] == '*' ? PowerValue.On : PowerValue.Off, segments.UpperRight.Power); 35 | Assert.Equal(expected[7] == '*' ? PowerValue.On : PowerValue.Off, segments.Center.Power); 36 | Assert.Equal(expected[9] == '*' ? PowerValue.On : PowerValue.Off, segments.LowerLeft.Power); 37 | Assert.Equal(expected[11] == '*' ? PowerValue.On : PowerValue.Off, segments.LowerRight.Power); 38 | Assert.Equal(expected[13] == '*' ? PowerValue.On : PowerValue.Off, segments.Bottom.Power); 39 | 40 | if (expected.Length > 15) 41 | Assert.Equal(expected[15] == '*' ? PowerValue.On : PowerValue.Off, segments.Decimal.Power); 42 | } 43 | 44 | private sealed class CharacterData : IEnumerable 45 | { 46 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 47 | 48 | public IEnumerator GetEnumerator() 49 | { 50 | yield return new object[] 51 | { 52 | ' ', " " + 53 | " " + 54 | " " + 55 | " " + 56 | " " 57 | }; 58 | 59 | yield return new object[] 60 | { 61 | '"', " " + 62 | "* *" + 63 | " " + 64 | " " + 65 | " " 66 | }; 67 | 68 | yield return new object[] 69 | { 70 | '\'', " " + 71 | " *" + 72 | " " + 73 | " " + 74 | " " 75 | }; 76 | 77 | yield return new object[] 78 | { 79 | '(', " * " + 80 | "* " + 81 | " " + 82 | "* " + 83 | " * " 84 | }; 85 | 86 | yield return new object[] 87 | { 88 | ')', " * " + 89 | " *" + 90 | " " + 91 | " *" + 92 | " * " 93 | }; 94 | 95 | yield return new object[] 96 | { 97 | ',', " " + 98 | " " + 99 | " " + 100 | " *" + 101 | " " 102 | }; 103 | 104 | yield return new object[] 105 | { 106 | '-', " " + 107 | " " + 108 | " * " + 109 | " " + 110 | " " 111 | }; 112 | 113 | yield return new object[] 114 | { 115 | '.', " " + 116 | " " + 117 | " " + 118 | " " + 119 | " *" 120 | }; 121 | 122 | yield return new object[] 123 | { 124 | '/', " " + 125 | " *" + 126 | " * " + 127 | "* " + 128 | " " 129 | }; 130 | 131 | yield return new object[] 132 | { 133 | '0', " * " + 134 | "* *" + 135 | " " + 136 | "* *" + 137 | " * " 138 | }; 139 | 140 | yield return new object[] 141 | { 142 | '1', " " + 143 | " *" + 144 | " " + 145 | " *" + 146 | " " 147 | }; 148 | 149 | yield return new object[] 150 | { 151 | '2', " * " + 152 | " *" + 153 | " * " + 154 | "* " + 155 | " * " 156 | }; 157 | 158 | yield return new object[] 159 | { 160 | '3', " * " + 161 | " *" + 162 | " * " + 163 | " *" + 164 | " * " 165 | }; 166 | 167 | yield return new object[] 168 | { 169 | '4', " " + 170 | "* *" + 171 | " * " + 172 | " *" + 173 | " " 174 | }; 175 | 176 | yield return new object[] 177 | { 178 | '5', " * " + 179 | "* " + 180 | " * " + 181 | " *" + 182 | " * " 183 | }; 184 | 185 | yield return new object[] 186 | { 187 | '6', " * " + 188 | "* " + 189 | " * " + 190 | "* *" + 191 | " * " 192 | }; 193 | 194 | yield return new object[] 195 | { 196 | '7', " * " + 197 | " *" + 198 | " " + 199 | " *" + 200 | " " 201 | }; 202 | 203 | yield return new object[] 204 | { 205 | '8', " * " + 206 | "* *" + 207 | " * " + 208 | "* *" + 209 | " * " 210 | }; 211 | 212 | yield return new object[] 213 | { 214 | '9', " * " + 215 | "* *" + 216 | " * " + 217 | " *" + 218 | " * " 219 | }; 220 | 221 | yield return new object[] 222 | { 223 | '<', " " + 224 | " " + 225 | " * " + 226 | "* " + 227 | " * " 228 | }; 229 | 230 | yield return new object[] 231 | { 232 | '=', " " + 233 | " " + 234 | " * " + 235 | " " + 236 | " * " 237 | }; 238 | 239 | yield return new object[] 240 | { 241 | '>', " " + 242 | " " + 243 | " * " + 244 | " *" + 245 | " * " 246 | }; 247 | 248 | yield return new object[] 249 | { 250 | 'A', " * " + 251 | "* *" + 252 | " * " + 253 | "* *" + 254 | " " 255 | }; 256 | 257 | yield return new object[] 258 | { 259 | 'B', " * " + 260 | "* *" + 261 | " * " + 262 | "* *" + 263 | " * " 264 | }; 265 | 266 | yield return new object[] 267 | { 268 | 'C', " * " + 269 | "* " + 270 | " " + 271 | "* " + 272 | " * " 273 | }; 274 | 275 | yield return new object[] 276 | { 277 | 'D', " * " + 278 | "* *" + 279 | " " + 280 | "* *" + 281 | " * " 282 | }; 283 | 284 | yield return new object[] 285 | { 286 | 'E', " * " + 287 | "* " + 288 | " * " + 289 | "* " + 290 | " * " 291 | }; 292 | 293 | yield return new object[] 294 | { 295 | 'F', " * " + 296 | "* " + 297 | " * " + 298 | "* " + 299 | " " 300 | }; 301 | 302 | yield return new object[] 303 | { 304 | 'G', " * " + 305 | "* " + 306 | " " + 307 | "* *" + 308 | " * " 309 | }; 310 | 311 | yield return new object[] 312 | { 313 | 'H', " " + 314 | "* *" + 315 | " * " + 316 | "* *" + 317 | " " 318 | }; 319 | 320 | yield return new object[] 321 | { 322 | 'I', " " + 323 | "* " + 324 | " " + 325 | "* " + 326 | " " 327 | }; 328 | 329 | yield return new object[] 330 | { 331 | 'J', " " + 332 | " *" + 333 | " " + 334 | "* *" + 335 | " * " 336 | }; 337 | 338 | yield return new object[] 339 | { 340 | 'K', " " + 341 | "* *" + 342 | " * " + 343 | "* *" + 344 | " " 345 | }; 346 | 347 | yield return new object[] 348 | { 349 | 'L', " " + 350 | "* " + 351 | " " + 352 | "* " + 353 | " * " 354 | }; 355 | 356 | yield return new object[] 357 | { 358 | 'M', " * " + 359 | "* *" + 360 | " " + 361 | "* *" + 362 | " " 363 | }; 364 | 365 | yield return new object[] 366 | { 367 | 'N', " " + 368 | "* *" + 369 | " * " + 370 | "* *" + 371 | " " 372 | }; 373 | 374 | yield return new object[] 375 | { 376 | 'O', " * " + 377 | "* *" + 378 | " " + 379 | "* *" + 380 | " * " 381 | }; 382 | 383 | yield return new object[] 384 | { 385 | 'P', " * " + 386 | "* *" + 387 | " * " + 388 | "* " + 389 | " " 390 | }; 391 | 392 | yield return new object[] 393 | { 394 | 'Q', " * " + 395 | "* *" + 396 | " " + 397 | "* *" + 398 | " * *" 399 | }; 400 | 401 | yield return new object[] 402 | { 403 | 'R', " * " + 404 | "* *" + 405 | " * " + 406 | "* *" + 407 | " " 408 | }; 409 | 410 | yield return new object[] 411 | { 412 | 'S', " * " + 413 | "* " + 414 | " * " + 415 | " *" + 416 | " * " 417 | }; 418 | 419 | yield return new object[] 420 | { 421 | 'T', " * " + 422 | "* " + 423 | " " + 424 | "* " + 425 | " " 426 | }; 427 | 428 | yield return new object[] 429 | { 430 | 'U', " " + 431 | "* *" + 432 | " " + 433 | "* *" + 434 | " * " 435 | }; 436 | 437 | yield return new object[] 438 | { 439 | 'V', " " + 440 | "* *" + 441 | " * " + 442 | "* " + 443 | " " 444 | }; 445 | 446 | yield return new object[] 447 | { 448 | 'W', " " + 449 | "* *" + 450 | " " + 451 | "* *" + 452 | " * " 453 | }; 454 | 455 | yield return new object[] 456 | { 457 | 'X', " " + 458 | "* *" + 459 | " * " + 460 | "* *" + 461 | " " 462 | }; 463 | 464 | yield return new object[] 465 | { 466 | 'Y', " " + 467 | "* *" + 468 | " * " + 469 | " *" + 470 | " " 471 | }; 472 | 473 | yield return new object[] 474 | { 475 | 'Z', " * " + 476 | " *" + 477 | " * " + 478 | "* " + 479 | " * " 480 | }; 481 | 482 | yield return new object[] 483 | { 484 | '[', " * " + 485 | "* " + 486 | " " + 487 | "* " + 488 | " * " 489 | }; 490 | 491 | yield return new object[] 492 | { 493 | '\\', " " + 494 | "* " + 495 | " * " + 496 | " *" + 497 | " " 498 | }; 499 | 500 | yield return new object[] 501 | { 502 | ']', " * " + 503 | " *" + 504 | " " + 505 | " *" + 506 | " * " 507 | }; 508 | 509 | yield return new object[] 510 | { 511 | '^', " * " + 512 | "* *" + 513 | " " + 514 | " " + 515 | " " 516 | }; 517 | 518 | yield return new object[] 519 | { 520 | '_', " " + 521 | " " + 522 | " " + 523 | " " + 524 | " * " 525 | }; 526 | 527 | yield return new object[] 528 | { 529 | '`', " " + 530 | "* " + 531 | " " + 532 | " " + 533 | " " 534 | }; 535 | 536 | yield return new object[] 537 | { 538 | 'a', " * " + 539 | " *" + 540 | " * " + 541 | "* *" + 542 | " * " 543 | }; 544 | 545 | yield return new object[] 546 | { 547 | 'b', " " + 548 | "* " + 549 | " * " + 550 | "* *" + 551 | " * " 552 | }; 553 | 554 | yield return new object[] 555 | { 556 | 'c', " " + 557 | " " + 558 | " * " + 559 | "* " + 560 | " * " 561 | }; 562 | 563 | yield return new object[] 564 | { 565 | 'd', " " + 566 | " *" + 567 | " * " + 568 | "* *" + 569 | " * " 570 | }; 571 | 572 | yield return new object[] 573 | { 574 | 'e', " * " + 575 | "* *" + 576 | " * " + 577 | "* " + 578 | " * " 579 | }; 580 | 581 | yield return new object[] 582 | { 583 | 'f', " * " + 584 | "* " + 585 | " * " + 586 | "* " + 587 | " " 588 | }; 589 | 590 | yield return new object[] 591 | { 592 | 'g', " * " + 593 | "* *" + 594 | " * " + 595 | " *" + 596 | " * " 597 | }; 598 | 599 | yield return new object[] 600 | { 601 | 'h', " " + 602 | "* " + 603 | " * " + 604 | "* *" + 605 | " " 606 | }; 607 | 608 | yield return new object[] 609 | { 610 | 'i', " " + 611 | " " + 612 | " " + 613 | "* " + 614 | " " 615 | }; 616 | 617 | yield return new object[] 618 | { 619 | 'j', " " + 620 | " " + 621 | " " + 622 | " *" + 623 | " * " 624 | }; 625 | 626 | yield return new object[] 627 | { 628 | 'k', " " + 629 | "* " + 630 | " * " + 631 | "* *" + 632 | " " 633 | }; 634 | 635 | yield return new object[] 636 | { 637 | 'l', " " + 638 | "* " + 639 | " " + 640 | "* " + 641 | " " 642 | }; 643 | 644 | yield return new object[] 645 | { 646 | 'm', " " + 647 | " " + 648 | " * " + 649 | "* *" + 650 | " " 651 | }; 652 | 653 | yield return new object[] 654 | { 655 | 'n', " " + 656 | " " + 657 | " * " + 658 | "* *" + 659 | " " 660 | }; 661 | 662 | yield return new object[] 663 | { 664 | 'o', " " + 665 | " " + 666 | " * " + 667 | "* *" + 668 | " * " 669 | }; 670 | 671 | yield return new object[] 672 | { 673 | 'p', " * " + 674 | "* *" + 675 | " * " + 676 | "* " + 677 | " " 678 | }; 679 | 680 | yield return new object[] 681 | { 682 | 'q', " * " + 683 | "* *" + 684 | " * " + 685 | " *" + 686 | " " 687 | }; 688 | 689 | yield return new object[] 690 | { 691 | 'r', " " + 692 | " " + 693 | " * " + 694 | "* " + 695 | " " 696 | }; 697 | 698 | yield return new object[] 699 | { 700 | 's', " * " + 701 | "* " + 702 | " * " + 703 | " *" + 704 | " * " 705 | }; 706 | 707 | yield return new object[] 708 | { 709 | 't', " " + 710 | "* " + 711 | " * " + 712 | "* " + 713 | " * " 714 | }; 715 | 716 | yield return new object[] 717 | { 718 | 'u', " " + 719 | " " + 720 | " " + 721 | "* *" + 722 | " * " 723 | }; 724 | 725 | yield return new object[] 726 | { 727 | 'v', " " + 728 | " " + 729 | " " + 730 | "* *" + 731 | " * " 732 | }; 733 | 734 | yield return new object[] 735 | { 736 | 'w', " " + 737 | " " + 738 | " " + 739 | "* *" + 740 | " * " 741 | }; 742 | 743 | yield return new object[] 744 | { 745 | 'x', " " + 746 | "* *" + 747 | " * " + 748 | "* *" + 749 | " " 750 | }; 751 | 752 | yield return new object[] 753 | { 754 | 'y', " " + 755 | "* *" + 756 | " * " + 757 | " *" + 758 | " " 759 | }; 760 | 761 | yield return new object[] 762 | { 763 | 'z', " * " + 764 | " *" + 765 | " * " + 766 | "* " + 767 | " * " 768 | }; 769 | 770 | yield return new object[] 771 | { 772 | '{', " * " + 773 | "* " + 774 | " " + 775 | "* " + 776 | " * " 777 | }; 778 | 779 | yield return new object[] 780 | { 781 | '|', " " + 782 | "* " + 783 | " " + 784 | "* " + 785 | " " 786 | }; 787 | 788 | yield return new object[] 789 | { 790 | '}', " * " + 791 | " *" + 792 | " " + 793 | " *" + 794 | " * " 795 | }; 796 | 797 | yield return new object[] 798 | { 799 | '~', " * " + 800 | " " + 801 | " " + 802 | " " + 803 | " " 804 | }; 805 | } 806 | } 807 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Components/ShiftRegisterTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute; 2 | using SimpleGPIO.Components; 3 | using SimpleGPIO.GPIO; 4 | using SimpleGPIO.Power; 5 | using SimpleGPIO.Tests.GPIO; 6 | using Xunit; 7 | 8 | namespace SimpleGPIO.Tests.Components; 9 | 10 | public sealed class ShiftRegisterTests 11 | { 12 | [Fact] 13 | public void SetPowerValuesSetsCorrectDataBits() 14 | { 15 | //arrange 16 | var enabled = new PinStub(1); 17 | var data = Substitute.For(); 18 | var shift = new PinStub(2); 19 | var output = new PinStub(3); 20 | var clear = new PinStub(4); 21 | var register = new ShiftRegister(enabled, data, shift, output, clear); 22 | 23 | var values = new ShiftRegister.PowerSet 24 | { 25 | A = PowerValue.Off, 26 | B = PowerValue.On, 27 | C = PowerValue.Off, 28 | D = PowerValue.Off, 29 | E = PowerValue.On, 30 | F = PowerValue.Off, 31 | G = PowerValue.Off, 32 | H = PowerValue.On 33 | }; 34 | 35 | //act 36 | register.SetPowerValues(values); 37 | 38 | //assert 39 | var calls = data.ReceivedCalls().ToArray(); 40 | Assert.Equal(PowerValue.On, calls[7].GetArguments()[0]); 41 | Assert.Equal(PowerValue.Off, calls[6].GetArguments()[0]); 42 | Assert.Equal(PowerValue.Off, calls[5].GetArguments()[0]); 43 | Assert.Equal(PowerValue.On, calls[4].GetArguments()[0]); 44 | Assert.Equal(PowerValue.Off, calls[3].GetArguments()[0]); 45 | Assert.Equal(PowerValue.Off, calls[2].GetArguments()[0]); 46 | Assert.Equal(PowerValue.On, calls[1].GetArguments()[0]); 47 | Assert.Equal(PowerValue.Off, calls[0].GetArguments()[0]); 48 | } 49 | 50 | [Fact] 51 | public void SetValueSetsCorrectDataBits() 52 | { 53 | //arrange 54 | var enabled = new PinStub(1); 55 | var data = Substitute.For(); 56 | var shift = new PinStub(2); 57 | var output = new PinStub(3); 58 | var clear = new PinStub(4); 59 | var register = new ShiftRegister(enabled, data, shift, output, clear); 60 | 61 | //act 62 | register.SetValue(146); 63 | 64 | //assert 65 | var calls = data.ReceivedCalls().ToArray(); 66 | Assert.Equal(PowerValue.On, calls[0].GetArguments()[0]); 67 | Assert.Equal(PowerValue.Off, calls[1].GetArguments()[0]); 68 | Assert.Equal(PowerValue.Off, calls[2].GetArguments()[0]); 69 | Assert.Equal(PowerValue.On, calls[3].GetArguments()[0]); 70 | Assert.Equal(PowerValue.Off, calls[4].GetArguments()[0]); 71 | Assert.Equal(PowerValue.Off, calls[5].GetArguments()[0]); 72 | Assert.Equal(PowerValue.On, calls[6].GetArguments()[0]); 73 | Assert.Equal(PowerValue.Off, calls[7].GetArguments()[0]); 74 | } 75 | 76 | [Fact] 77 | public void ClearSpikesOutputWhileClearIsOn() 78 | { 79 | //arrange 80 | var enabled = new PinStub(1); 81 | var data = new PinStub(2); 82 | var shift = new PinStub(3); 83 | var output = Substitute.For(); 84 | var clear = Substitute.For(); 85 | var register = new ShiftRegister(enabled, data, shift, output, clear); 86 | 87 | //act 88 | register.Clear(); 89 | 90 | //assert 91 | clear.Received().TurnOn(); 92 | output.Received().Spike(); 93 | clear.Received().TurnOff(); 94 | } 95 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/GPIO/PinStub.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | using SimpleGPIO.IO; 3 | using SimpleGPIO.Power; 4 | 5 | namespace SimpleGPIO.Tests.GPIO; 6 | 7 | public sealed class PinStub : PinInterface 8 | { 9 | public PinStub(byte pin) => Pin = pin; 10 | 11 | public byte Pin { get; } 12 | 13 | public override bool Enabled { get; set; } 14 | public override Direction Direction { get; set; } 15 | 16 | public override Voltage Voltage { get; set; } 17 | 18 | protected override void RefreshPWM() 19 | { 20 | } 21 | 22 | public override void OnPowerOn(Action action) => action(); 23 | public override void OnPowerOff(Action action) => action(); 24 | public override void OnPowerChange(Action action) => action(); 25 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/GPIO/SystemPinInterfaceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Device.Gpio; 2 | using NSubstitute; 3 | using SimpleGPIO.Device; 4 | using SimpleGPIO.GPIO; 5 | using SimpleGPIO.IO; 6 | using SimpleGPIO.Power; 7 | using Xunit; 8 | 9 | namespace SimpleGPIO.Tests.GPIO; 10 | 11 | public sealed class SystemPinInterfaceTests 12 | { 13 | [Fact] 14 | public void EnabledIfPinOpen() 15 | { 16 | //arrange 17 | var gpio = Substitute.For(); 18 | gpio.IsPinOpen(123).Returns(true); 19 | var pwm = Substitute.For(); 20 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 21 | 22 | //act 23 | var enabled = pinInterface.Enabled; 24 | 25 | //assert 26 | Assert.True(enabled); 27 | } 28 | 29 | [Fact] 30 | public void NotEnabledIfPinNotOpen() 31 | { 32 | //arrange 33 | var gpio = Substitute.For(); 34 | gpio.IsPinOpen(123).Returns(false); 35 | var pwm = Substitute.For(); 36 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 37 | 38 | //act 39 | var enabled = pinInterface.Enabled; 40 | 41 | //assert 42 | Assert.False(enabled); 43 | } 44 | 45 | [Theory] 46 | [InlineData(PinMode.Input, IOMode.Read)] 47 | [InlineData(PinMode.Output, IOMode.Write)] 48 | public void CanGetIOMode(PinMode value, IOMode expected) 49 | { 50 | //arrange 51 | var gpio = Substitute.For(); 52 | gpio.GetPinMode(123).Returns(value); 53 | var pwm = Substitute.For(); 54 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 55 | 56 | //act 57 | var io = pinInterface.IOMode; 58 | 59 | //assert 60 | Assert.Equal(expected, io); 61 | } 62 | 63 | [Fact] 64 | public void GettingIOModeEnablesPinIfNotEnabled() 65 | { 66 | //arrange 67 | var gpio = Substitute.For(); 68 | gpio.GetPinMode(123).Returns(PinMode.Input); 69 | var pwm = Substitute.For(); 70 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 71 | 72 | //act 73 | var io = pinInterface.IOMode; 74 | 75 | //assert 76 | Assert.Equal(IOMode.Read, io); 77 | gpio.Received().OpenPin(123); 78 | } 79 | 80 | [Theory] 81 | [InlineData(IOMode.Read, PinMode.Input)] 82 | [InlineData(IOMode.Write, PinMode.Output)] 83 | public void CanSetIOMode(IOMode io, PinMode expected) 84 | { 85 | //arrange 86 | var gpio = Substitute.For(); 87 | var pwm = Substitute.For(); 88 | 89 | //act 90 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 91 | { 92 | Enabled = true, 93 | IOMode = io 94 | }; 95 | 96 | //assert 97 | Assert.NotNull(pinInterface); 98 | gpio.Received().SetPinMode(123, expected); 99 | } 100 | 101 | [Fact] 102 | public void SettingIOModeEnablesPinIfNotEnabled() 103 | { 104 | //arrange 105 | var gpio = Substitute.For(); 106 | var pwm = Substitute.For(); 107 | 108 | //act 109 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 110 | { 111 | IOMode = IOMode.Write 112 | }; 113 | 114 | //assert 115 | Assert.NotNull(pinInterface); 116 | gpio.Received().OpenPin(123); 117 | } 118 | 119 | [Theory] 120 | [InlineData(typeof(Direct), 0, PowerValue.Off)] 121 | [InlineData(typeof(Direct), 1, PowerValue.On)] 122 | [InlineData(typeof(Differential), 1, PowerValue.Off)] 123 | [InlineData(typeof(Differential), 0, PowerValue.On)] 124 | public void CanGetPower(Type powerModeType, byte value, PowerValue expected) 125 | { 126 | //arrange 127 | var gpio = Substitute.For(); 128 | gpio.Read(123).Returns(value); 129 | var pwm = Substitute.For(); 130 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 131 | { 132 | Direction = Direction.Out, 133 | PowerMode = (IPowerMode)Activator.CreateInstance(powerModeType)! 134 | }; 135 | 136 | //act 137 | var power = pinInterface.Power; 138 | 139 | //assert 140 | Assert.Equal(expected, power); 141 | } 142 | 143 | [Fact] 144 | public void GettingPowerEnablesPinIfNotEnabled() 145 | { 146 | //arrange 147 | var gpio = Substitute.For(); 148 | gpio.Read(123).Returns(PinValue.High); 149 | var pwm = Substitute.For(); 150 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 151 | { 152 | Direction = Direction.In 153 | }; 154 | 155 | //act 156 | var power = pinInterface.Power; 157 | 158 | //assert 159 | Assert.IsType(power); 160 | gpio.Received().OpenPin(123); 161 | } 162 | 163 | [Theory] 164 | [InlineData(typeof(Direct), PowerValue.Off, 0)] 165 | [InlineData(typeof(Direct), PowerValue.On, 1)] 166 | [InlineData(typeof(Differential), PowerValue.Off, 1)] 167 | [InlineData(typeof(Differential), PowerValue.On, 0)] 168 | public void CanSetPower(Type powerModeType, PowerValue power, byte expected) 169 | { 170 | //arrange 171 | var gpio = Substitute.For(); 172 | gpio.GetPinMode(123).Returns(PinMode.Output); 173 | var pwm = Substitute.For(); 174 | 175 | //act 176 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 177 | { 178 | Enabled = true, 179 | PowerMode = (IPowerMode)Activator.CreateInstance(powerModeType)!, 180 | Power = power 181 | }; 182 | 183 | //assert 184 | Assert.NotNull(pinInterface); 185 | gpio.Received().Write(123, expected); 186 | } 187 | 188 | [Fact] 189 | public void SettingPowerSetsIOModeIfNotWrite() 190 | { 191 | //arrange 192 | var gpio = Substitute.For(); 193 | gpio.GetPinMode(123).Returns(PinMode.Input); 194 | var pwm = Substitute.For(); 195 | 196 | //act 197 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 198 | { 199 | Power = PowerValue.On 200 | }; 201 | 202 | //assert 203 | Assert.NotNull(pinInterface); 204 | gpio.Received().SetPinMode(123, PinMode.Output); 205 | } 206 | 207 | [Fact] 208 | public void SettingPowerModeTurnsPinOff() 209 | { 210 | //arrange 211 | var gpio = Substitute.For(); 212 | gpio.GetPinMode(123).Returns(PinMode.Input); 213 | var pwm = Substitute.For(); 214 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 215 | { 216 | PowerMode = PowerMode.Direct 217 | }; 218 | 219 | //act 220 | pinInterface.PowerMode = PowerMode.Differential; 221 | 222 | //assert 223 | gpio.Received().Write(123, PinValue.High); 224 | } 225 | 226 | [Fact] 227 | public void EnableOpensPin() 228 | { 229 | //arrange 230 | var gpio = Substitute.For(); 231 | var pwm = Substitute.For(); 232 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 233 | { 234 | Enabled = false 235 | }; 236 | 237 | //act 238 | pinInterface.Enable(); 239 | 240 | //assert 241 | gpio.Received().OpenPin(123); 242 | } 243 | 244 | [Fact] 245 | public void DisableSetsEnabledFalse() 246 | { 247 | //arrange 248 | var gpio = Substitute.For(); 249 | var pwm = Substitute.For(); 250 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 251 | { 252 | Enabled = true 253 | }; 254 | 255 | //act 256 | pinInterface.Disable(); 257 | 258 | //assert 259 | Assert.False(pinInterface.Enabled); 260 | } 261 | 262 | [Theory] 263 | [InlineData(0, 0)] 264 | [InlineData(100, 1)] 265 | [InlineData(50, 0.5)] 266 | [InlineData(101, 1)] 267 | [InlineData(-1, 0)] 268 | public void StrengthSetsDutyCycle(double strength, double expected) 269 | { 270 | //arrange 271 | var gpio = Substitute.For(); 272 | var pwm = Substitute.For(); 273 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 274 | 275 | //act 276 | pinInterface.Strength = strength; 277 | 278 | //assert 279 | pwm.Received().DutyCycle = expected; 280 | } 281 | 282 | [Fact] 283 | public void SettingStrengthTurnsOffIfOnAndZeroStrength() 284 | { 285 | //arrange 286 | var gpio = Substitute.For(); 287 | gpio.Read(123).Returns(PinValue.High); 288 | var pwm = Substitute.For(); 289 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 290 | 291 | //act 292 | pinInterface.Strength = 0; 293 | 294 | //assert 295 | gpio.Received().Write(123, PinValue.Low); 296 | } 297 | 298 | [Fact] 299 | public void SettingStrengthDoesNotTurnOffIfOnAndNonZeroStrength() 300 | { 301 | //arrange 302 | var gpio = Substitute.For(); 303 | gpio.Read(123).Returns(PinValue.High); 304 | var pwm = Substitute.For(); 305 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 306 | 307 | //act 308 | pinInterface.Strength = 1; 309 | 310 | //assert 311 | gpio.DidNotReceive().Write(123, PinValue.Low); 312 | } 313 | 314 | [Fact] 315 | public void SettingStrengthDoesNotTurnOffIfAlreadyOff() 316 | { 317 | //arrange 318 | var gpio = Substitute.For(); 319 | gpio.Read(123).Returns(PinValue.Low); 320 | var pwm = Substitute.For(); 321 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 322 | 323 | //act 324 | pinInterface.Strength = 0; 325 | 326 | //assert 327 | gpio.DidNotReceive().Write(123, PinValue.Low); 328 | } 329 | 330 | [Fact] 331 | public void SettingStrengthTurnsOnIfOffAndNonZeroStrength() 332 | { 333 | //arrange 334 | var gpio = Substitute.For(); 335 | gpio.Read(123).Returns(PinValue.Low); 336 | var pwm = Substitute.For(); 337 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 338 | 339 | //act 340 | pinInterface.Strength = 1; 341 | 342 | //assert 343 | gpio.Received().Write(123, PinValue.High); 344 | } 345 | 346 | [Fact] 347 | public void SettingStrengthDoesNotTurnOnIfOffAndZeroStrength() 348 | { 349 | //arrange 350 | var gpio = Substitute.For(); 351 | gpio.Read(123).Returns(PinValue.High); 352 | var pwm = Substitute.For(); 353 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 354 | 355 | //act 356 | pinInterface.Strength = 0; 357 | 358 | //assert 359 | gpio.DidNotReceive().Write(123, PinValue.High); 360 | } 361 | 362 | [Fact] 363 | public void SettingStrengthDoesNotTurnOnIfAlreadyOn() 364 | { 365 | //arrange 366 | var gpio = Substitute.For(); 367 | gpio.Read(123).Returns(PinValue.High); 368 | var pwm = Substitute.For(); 369 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 370 | 371 | //act 372 | pinInterface.Strength = 1; 373 | 374 | //assert 375 | gpio.DidNotReceive().Write(123, PinValue.High); 376 | } 377 | 378 | [Fact] 379 | public void TurnOnSetsPowerOn() 380 | { 381 | //arrange 382 | var gpio = Substitute.For(); 383 | gpio.GetPinMode(123).Returns(PinMode.Output); 384 | var pwm = Substitute.For(); 385 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 386 | { 387 | Power = PowerValue.Off 388 | }; 389 | 390 | //act 391 | pinInterface.TurnOn(); 392 | 393 | //assert 394 | gpio.Received().Write(123, PinValue.High); 395 | } 396 | 397 | [Fact] 398 | public void TurnOffSetsPowerOff() 399 | { 400 | //arrange 401 | var gpio = Substitute.For(); 402 | gpio.GetPinMode(123).Returns(PinMode.Output); 403 | var pwm = Substitute.For(); 404 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 405 | { 406 | Power = PowerValue.On 407 | }; 408 | 409 | //act 410 | pinInterface.TurnOff(); 411 | 412 | //assert 413 | gpio.Received().Write(123, PinValue.Low); 414 | } 415 | 416 | [Fact] 417 | public void TurnOnDoesNotStartPWMByDefault() 418 | { 419 | //arrange 420 | var gpio = Substitute.For(); 421 | gpio.GetPinMode(123).Returns(PinMode.Output); 422 | var pwm = Substitute.For(); 423 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 424 | { 425 | Power = PowerValue.Off 426 | }; 427 | 428 | //act 429 | pinInterface.TurnOn(); 430 | 431 | //assert 432 | pwm.DidNotReceive().Start(); 433 | } 434 | 435 | [Fact] 436 | public void TurnOnStartsPWMWhenStrengthSet() 437 | { 438 | //arrange 439 | var gpio = Substitute.For(); 440 | gpio.GetPinMode(123).Returns(PinMode.Output); 441 | gpio.Read(123).Returns(PinValue.High); 442 | var pwm = Substitute.For(); 443 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 444 | { 445 | Strength = 50 446 | }; 447 | 448 | //act 449 | pinInterface.TurnOn(); 450 | 451 | //assert 452 | pwm.Received().Start(); 453 | } 454 | 455 | [Fact] 456 | public void TurnOnStopsPWMWhenFullStrength() 457 | { 458 | //arrange 459 | var gpio = Substitute.For(); 460 | gpio.GetPinMode(123).Returns(PinMode.Output); 461 | gpio.Read(123).Returns(PinValue.High); 462 | var pwm = Substitute.For(); 463 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 464 | { 465 | Strength = 100 466 | }; 467 | 468 | //act 469 | pinInterface.TurnOn(); 470 | 471 | //assert 472 | pwm.DidNotReceive().Start(); 473 | pwm.Received().Stop(); 474 | } 475 | 476 | [Fact] 477 | public void TurnOnStopsPWMWhenZeroStrength() 478 | { 479 | //arrange 480 | var gpio = Substitute.For(); 481 | gpio.GetPinMode(123).Returns(PinMode.Output); 482 | gpio.Read(123).Returns(PinValue.High); 483 | var pwm = Substitute.For(); 484 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 485 | { 486 | Strength = 0 487 | }; 488 | 489 | //act 490 | pinInterface.TurnOn(); 491 | 492 | //assert 493 | pwm.DidNotReceive().Start(); 494 | pwm.Received().Stop(); 495 | } 496 | 497 | [Fact] 498 | public void TurnOffStopsPWM() 499 | { 500 | //arrange 501 | var gpio = Substitute.For(); 502 | gpio.GetPinMode(123).Returns(PinMode.Output); 503 | var pwm = Substitute.For(); 504 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 505 | { 506 | Power = PowerValue.On 507 | }; 508 | 509 | //act 510 | pinInterface.TurnOff(); 511 | 512 | //assert 513 | pwm.Received().Stop(); 514 | } 515 | 516 | [Fact] 517 | public void SpikeTurnsOnThenOff() 518 | { 519 | //arrange 520 | var gpio = Substitute.For(); 521 | gpio.GetPinMode(123).Returns(PinMode.Output); 522 | var pwm = Substitute.For(); 523 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 524 | 525 | //act 526 | pinInterface.Spike(); 527 | 528 | //assert 529 | var calls = gpio.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "Write").ToArray(); 530 | Assert.Equal(PinValue.High, calls[0].GetArguments()[1]); 531 | Assert.Equal(PinValue.Low, calls[1].GetArguments()[1]); 532 | } 533 | 534 | [Fact] 535 | public async Task TurnOnForTurnsOffAfter() 536 | { 537 | //arrange 538 | var gpio = Substitute.For(); 539 | gpio.GetPinMode(123).Returns(PinMode.Output); 540 | var pwm = Substitute.For(); 541 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 542 | 543 | //act 544 | await pinInterface.TurnOnFor(TimeSpan.Zero); 545 | 546 | //assert 547 | var calls = gpio.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "Write").ToArray(); 548 | Assert.Equal(PinValue.High, calls[0].GetArguments()[1]); 549 | Assert.Equal(PinValue.Low, calls[1].GetArguments()[1]); 550 | } 551 | 552 | [Fact] 553 | public async Task TurnOffForTurnsOnAfter() 554 | { 555 | //arrange 556 | var gpio = Substitute.For(); 557 | gpio.GetPinMode(123).Returns(PinMode.Output); 558 | var pwm = Substitute.For(); 559 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 560 | 561 | //act 562 | await pinInterface.TurnOffFor(TimeSpan.Zero); 563 | 564 | //assert 565 | var calls = gpio.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "Write").ToArray(); 566 | Assert.Equal(PinValue.Low, calls[0].GetArguments()[1]); 567 | Assert.Equal(PinValue.High, calls[1].GetArguments()[1]); 568 | } 569 | 570 | [Fact] 571 | public void ToggleTurnsOnIfOff() 572 | { 573 | //arrange 574 | var gpio = Substitute.For(); 575 | gpio.GetPinMode(123).Returns(PinMode.Output); 576 | gpio.Read(123).Returns(PinValue.Low); 577 | var pwm = Substitute.For(); 578 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 579 | { 580 | Power = PowerValue.Off 581 | }; 582 | 583 | //act 584 | pinInterface.Toggle(); 585 | 586 | //assert 587 | gpio.Received().Write(123, PinValue.High); 588 | } 589 | 590 | [Fact] 591 | public void ToggleTurnsOffIfOn() 592 | { 593 | //arrange 594 | var gpio = Substitute.For(); 595 | gpio.GetPinMode(123).Returns(PinMode.Output); 596 | gpio.Read(123).Returns(PinValue.High); 597 | var pwm = Substitute.For(); 598 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 599 | { 600 | Power = PowerValue.On 601 | }; 602 | 603 | //act 604 | pinInterface.Toggle(); 605 | 606 | //assert 607 | gpio.Received().Write(123, PinValue.Low); 608 | } 609 | 610 | [Fact] 611 | public async Task CanToggleForADuration() 612 | { 613 | //arrange 614 | var gpio = Substitute.For(); 615 | gpio.GetPinMode(123).Returns(PinMode.Output); 616 | gpio.Read(123).Returns(PinValue.Low); 617 | var pwm = Substitute.For(); 618 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 619 | 620 | //act 621 | await pinInterface.Toggle(1000, TimeSpan.FromMilliseconds(1)); 622 | 623 | //assert 624 | gpio.Received(2).Write(123, Arg.Any()); 625 | } 626 | 627 | [Fact] 628 | public async Task CanToggleForSetIterations() 629 | { 630 | //arrange 631 | var gpio = Substitute.For(); 632 | gpio.GetPinMode(123).Returns(PinMode.Output); 633 | gpio.Read(123).Returns(PinValue.Low); 634 | var pwm = Substitute.For(); 635 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 636 | 637 | //act 638 | await pinInterface.Toggle(TimeSpan.TicksPerMillisecond, 10); 639 | 640 | //assert 641 | gpio.Received(20).Write(123, Arg.Any()); 642 | } 643 | 644 | [Fact] 645 | public async Task FadeInEndsAtFullStrength() 646 | { 647 | //arrange 648 | var gpio = Substitute.For(); 649 | var pwm = Substitute.For(); 650 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 651 | 652 | //act 653 | await pinInterface.FadeIn(TimeSpan.Zero); 654 | 655 | //assert 656 | Assert.Equal(100, pinInterface.Strength); 657 | } 658 | 659 | [Fact] 660 | public async Task FadeOutEndsAtZeroStrength() 661 | { 662 | //arrange 663 | var gpio = Substitute.For(); 664 | var pwm = Substitute.For(); 665 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 666 | 667 | //act 668 | await pinInterface.FadeOut(TimeSpan.Zero); 669 | 670 | //assert 671 | Assert.Equal(0, pinInterface.Strength); 672 | } 673 | 674 | [Fact] 675 | public void FadeToScalesStrength() 676 | { 677 | //arrange 678 | var gpio = Substitute.For(); 679 | var pwm = Substitute.For(); 680 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 681 | { 682 | Strength = 75 683 | }; 684 | 685 | //act 686 | _ = pinInterface.FadeTo(25, TimeSpan.FromSeconds(1)); 687 | SpinWait.SpinUntil(() => pinInterface.Strength < 75); 688 | 689 | //assert 690 | Assert.True(pinInterface.Strength > 25); 691 | } 692 | 693 | [Fact] 694 | public async Task PulseSetsInitialStrength() 695 | { 696 | //arrange 697 | var gpio = Substitute.For(); 698 | var pwm = Substitute.For(); 699 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 700 | 701 | //act 702 | await pinInterface.Pulse(50, TimeSpan.Zero); 703 | 704 | //assert 705 | pwm.Received().DutyCycle = 0.5; 706 | Assert.Equal(0, pinInterface.Strength); 707 | } 708 | 709 | [Fact] 710 | public async Task DefaultPulseSetsToFullStrength() 711 | { 712 | //arrange 713 | var gpio = Substitute.For(); 714 | var pwm = Substitute.For(); 715 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 716 | 717 | //act 718 | await pinInterface.Pulse(TimeSpan.Zero); 719 | 720 | //assert 721 | pwm.Received().DutyCycle = 1; 722 | Assert.Equal(0, pinInterface.Strength); 723 | } 724 | 725 | [Fact] 726 | public void OnPowerOnEnablesIfNotEnabled() 727 | { 728 | //arrange 729 | var gpio = Substitute.For(); 730 | gpio.GetPinMode(123).Returns(PinMode.Input); 731 | var pwm = Substitute.For(); 732 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 733 | 734 | //act 735 | pinInterface.OnPowerOn(() => { }); 736 | 737 | //assert 738 | gpio.Received().OpenPin(123); 739 | } 740 | 741 | [Fact] 742 | public void OnPowerOnPerformsAction() 743 | { 744 | //arrange 745 | var gpio = Substitute.For(); 746 | gpio.GetPinMode(123).Returns(PinMode.Input); 747 | gpio.When(g => 748 | g.RegisterCallbackForPinValueChangedEvent(123, PinEventTypes.Rising, Arg.Any())) 749 | .Do(c => c.Arg().Invoke(null!, null!)); 750 | var pwm = Substitute.For(); 751 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 752 | { 753 | Power = PowerValue.On 754 | }; 755 | var called = false; 756 | 757 | //act 758 | pinInterface.OnPowerOn(() => called = true); 759 | 760 | //assert 761 | Assert.True(called); 762 | } 763 | 764 | [Fact] 765 | public void OnPowerOffEnablesIfNotEnabled() 766 | { 767 | //arrange 768 | var gpio = Substitute.For(); 769 | gpio.GetPinMode(123).Returns(PinMode.Input); 770 | var pwm = Substitute.For(); 771 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 772 | 773 | //act 774 | pinInterface.OnPowerOff(() => { }); 775 | 776 | //assert 777 | gpio.Received().OpenPin(123); 778 | } 779 | 780 | [Fact] 781 | public void OnPowerOffPerformsAction() 782 | { 783 | //arrange 784 | var gpio = Substitute.For(); 785 | gpio.GetPinMode(123).Returns(PinMode.Input); 786 | gpio.When(g => 787 | g.RegisterCallbackForPinValueChangedEvent(123, PinEventTypes.Falling, Arg.Any())) 788 | .Do(c => c.Arg().Invoke(null!, null!)); 789 | 790 | var pwm = Substitute.For(); 791 | var pinInterface = new SystemPinInterface(123, gpio, pwm) 792 | { 793 | Power = PowerValue.Off 794 | }; 795 | var called = false; 796 | 797 | //act 798 | pinInterface.OnPowerOff(() => called = true); 799 | 800 | //assert 801 | Assert.True(called); 802 | } 803 | 804 | [Fact] 805 | public void OnPowerChangeEnablesIfNotEnabled() 806 | { 807 | //arrange 808 | var gpio = Substitute.For(); 809 | gpio.GetPinMode(123).Returns(PinMode.Input); 810 | var pwm = Substitute.For(); 811 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 812 | 813 | //act 814 | pinInterface.OnPowerChange(() => { }); 815 | 816 | //assert 817 | gpio.Received().OpenPin(123); 818 | } 819 | 820 | [Fact] 821 | public void OnPowerChangePerformsAction() 822 | { 823 | //arrange 824 | var gpio = Substitute.For(); 825 | gpio.GetPinMode(123).Returns(PinMode.Input); 826 | gpio.When(g => 827 | g.RegisterCallbackForPinValueChangedEvent(123, Arg.Any(), 828 | Arg.Any())) 829 | .Do(c => c.Arg().Invoke(null!, null!)); 830 | var pwm = Substitute.For(); 831 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 832 | var called = false; 833 | 834 | //act 835 | pinInterface.OnPowerChange(() => called = true); 836 | 837 | //assert 838 | Assert.True(called); 839 | } 840 | 841 | [Fact] 842 | public void DisablesOnDispose() 843 | { 844 | //arrange 845 | var gpio = Substitute.For(); 846 | gpio.GetPinMode(123).Returns(PinMode.Output); 847 | var pwm = Substitute.For(); 848 | var pinInterface = new SystemPinInterface(123, gpio, pwm); 849 | 850 | //act 851 | pinInterface.Dispose(); 852 | 853 | //assert 854 | pwm.Received().Dispose(); 855 | gpio.Received().ClosePin(123); 856 | } 857 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/IO/IOHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.IO; 2 | using Xunit; 3 | 4 | namespace SimpleGPIO.Tests.IO; 5 | 6 | public sealed class IOHelpersTests 7 | { 8 | [Theory] 9 | [InlineData(Direction.In, IOMode.Read)] 10 | [InlineData(Direction.Out, IOMode.Write)] 11 | public void CanGetIOModeFromDirection(Direction direction, IOMode expected) 12 | => Assert.Equal(expected, direction.ToIOMode()); 13 | 14 | [Theory] 15 | [InlineData(IOMode.Read, Direction.In)] 16 | [InlineData(IOMode.Write, Direction.Out)] 17 | public void CanGetDirectionFromIOMode(IOMode io, Direction expected) 18 | => Assert.Equal(expected, io.ToDirection()); 19 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/MathTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace SimpleGPIO.Tests; 4 | 5 | public class MathTests 6 | { 7 | [Theory] 8 | [InlineData(1, 2, 4, 2)] 9 | [InlineData(2, 2, 4, 2)] 10 | [InlineData(3, 2, 4, 3)] 11 | [InlineData(4, 2, 4, 4)] 12 | [InlineData(5, 2, 4, 4)] 13 | public void CanClampNumber(int value, int min, int max, int expected) 14 | { 15 | //act 16 | var actual = value.Clamp(min, max); 17 | 18 | //assert 19 | Assert.Equal(expected, actual); 20 | } 21 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Power/PowerHelpersTests.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Power; 2 | using Xunit; 3 | 4 | namespace SimpleGPIO.Tests.Power; 5 | 6 | public sealed class PowerHelpersTests 7 | { 8 | [Theory] 9 | [InlineData(typeof(Direct), Voltage.Low, PowerValue.Off)] 10 | [InlineData(typeof(Direct), Voltage.High, PowerValue.On)] 11 | [InlineData(typeof(Differential), Voltage.Low, PowerValue.On)] 12 | [InlineData(typeof(Differential), Voltage.High, PowerValue.Off)] 13 | public void CanConvertPowerToVoltage(Type powerModeType, Voltage voltage, PowerValue expected) 14 | { 15 | //arrange 16 | var powerMode = (IPowerMode)Activator.CreateInstance(powerModeType)!; 17 | 18 | //act 19 | var power = voltage.ToPowerValue(powerMode); 20 | 21 | //assert 22 | Assert.Equal(expected, power); 23 | } 24 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/Power/PowerModeTests.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Power; 2 | using Xunit; 3 | 4 | namespace SimpleGPIO.Tests.Power; 5 | 6 | public sealed class PowerModeTests 7 | { 8 | [Fact] 9 | public void CanCreateDirect() 10 | { 11 | //act 12 | var powerMode = PowerMode.Direct; 13 | 14 | //assert 15 | Assert.IsAssignableFrom(powerMode); 16 | } 17 | 18 | [Fact] 19 | public void CanCreateDifferential() 20 | { 21 | //act 22 | var powerMode = PowerMode.Differential; 23 | 24 | //assert 25 | Assert.IsAssignableFrom(powerMode); 26 | } 27 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/SPI/InputStub.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | 3 | namespace SimpleGPIO.Tests.SPI; 4 | 5 | public sealed class InputStub : SimpleGPIO.SPI.Input 6 | { 7 | public InputStub(IPinInterface data, IPinInterface clock) : base(data, clock) 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/SPI/InputTests.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using NSubstitute; 3 | using SimpleGPIO.GPIO; 4 | using SimpleGPIO.Power; 5 | using SimpleGPIO.Tests.GPIO; 6 | using Xunit; 7 | 8 | namespace SimpleGPIO.Tests.SPI; 9 | 10 | public sealed class InputTests 11 | { 12 | [Fact] 13 | public void CanSendPowerValue() 14 | { 15 | //arrange 16 | var data = Substitute.For(); 17 | var clock = new PinStub(2); 18 | var input = new InputStub(data, clock); 19 | 20 | //act 21 | input.Send(PowerValue.On); 22 | 23 | //assert 24 | data.Received().Power = PowerValue.On; 25 | } 26 | 27 | [Fact] 28 | public void SendByteSendsCorrectPowerValues() 29 | { 30 | //arrange 31 | var data = Substitute.For(); 32 | var clock = new PinStub(2); 33 | var input = new InputStub(data, clock); 34 | 35 | //act 36 | input.Send(146); 37 | 38 | //assert 39 | var calls = data.ReceivedCalls().ToArray(); 40 | Assert.Equal(PowerValue.On, calls[0].GetArguments()[0]); 41 | Assert.Equal(PowerValue.Off, calls[1].GetArguments()[0]); 42 | Assert.Equal(PowerValue.Off, calls[2].GetArguments()[0]); 43 | Assert.Equal(PowerValue.On, calls[3].GetArguments()[0]); 44 | Assert.Equal(PowerValue.Off, calls[4].GetArguments()[0]); 45 | Assert.Equal(PowerValue.Off, calls[5].GetArguments()[0]); 46 | Assert.Equal(PowerValue.On, calls[6].GetArguments()[0]); 47 | Assert.Equal(PowerValue.Off, calls[7].GetArguments()[0]); 48 | } 49 | 50 | [Fact] 51 | public void SendPowerArraySendsCorrectPowerValues() 52 | { 53 | //arrange 54 | var data = Substitute.For(); 55 | var clock = new PinStub(2); 56 | var input = new InputStub(data, clock); 57 | var values = new[] { PowerValue.On, PowerValue.Off, PowerValue.Off, PowerValue.On }; 58 | 59 | //act 60 | input.Send(values); 61 | 62 | //assert 63 | var calls = data.ReceivedCalls().ToArray(); 64 | Assert.Equal(PowerValue.On, calls[0].GetArguments()[0]); 65 | Assert.Equal(PowerValue.Off, calls[1].GetArguments()[0]); 66 | Assert.Equal(PowerValue.Off, calls[2].GetArguments()[0]); 67 | Assert.Equal(PowerValue.On, calls[3].GetArguments()[0]); 68 | } 69 | 70 | [Fact] 71 | public void SendByteArraySendsCorrectPowerValues() 72 | { 73 | //arrange 74 | 75 | var data = Substitute.For(); 76 | var clock = new PinStub(2); 77 | var input = new InputStub(data, clock); 78 | var bytesIn = Encoding.ASCII.GetBytes("Abc123"); 79 | 80 | //act 81 | input.Send(bytesIn); 82 | 83 | //assert 84 | var bit = data.ReceivedCalls() 85 | .Select(c => c.GetArguments()[0]).Cast() 86 | .Select(p => p == PowerValue.On ? (byte)1 : (byte)0) 87 | .ToArray(); 88 | 89 | var bytesOut = new byte[6]; 90 | for (var byteNum = 0; byteNum < bytesOut.Length; byteNum++) 91 | { 92 | byte byteSum = 0; 93 | for (var bitNum = 0; bitNum < 8; bitNum++) 94 | { 95 | byteSum += (byte)(bit[8 * byteNum + bitNum] << (7 - bitNum)); 96 | } 97 | 98 | bytesOut[byteNum] = byteSum; 99 | } 100 | 101 | var output = Encoding.ASCII.GetString(bytesOut); 102 | Assert.Equal("Abc123", output); 103 | } 104 | } -------------------------------------------------------------------------------- /SimpleGPIO.Tests/SimpleGPIO.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | enable 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SimpleGPIO.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleGPIO", "SimpleGPIO\SimpleGPIO.csproj", "{90EA81A7-E4D2-4D0D-BCF9-3BDA729909D0}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleGPIO.Tests", "SimpleGPIO.Tests\SimpleGPIO.Tests.csproj", "{DC921947-93D5-4120-952D-FF5B6D1FE09F}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{72AD87B6-356E-4501-9E63-E7FDB6C78F63}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Components", "Components", "{E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "Examples\CLI\CLI.csproj", "{C9C4B277-1A7B-454C-AA32-E406AEBA61FC}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Input", "Examples\Input\Input.csproj", "{962C5E8A-6A3A-4771-BA82-022F1BCA7143}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PWM", "Examples\PWM\PWM.csproj", "{BD8FEB6C-0063-447F-91AD-8BAB636BF1E7}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "Examples\Web\Web.csproj", "{FBDE5D8F-BC2D-4441-BF43-CB02224E62E4}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShiftRegister", "Examples\Components\ShiftRegister\ShiftRegister.csproj", "{D677060A-4476-420E-AB6F-1E0C7E317F60}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotMatrix", "Examples\Components\DotMatrix\DotMatrix.csproj", "{1032759C-F88C-40BE-8F4A-57C51DF248A2}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Motor", "Examples\Components\Motor\Motor.csproj", "{DFAF29B8-DDAA-4FFB-884A-42ACA40561F2}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RGBLED", "Examples\Components\RGBLED\RGBLED.csproj", "{530772F4-9B82-4F48-A025-B0F948D59820}" 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RotaryEncoder", "Examples\Components\RotaryEncoder\RotaryEncoder.csproj", "{E1589800-F0C8-4166-A6CE-9364E4E06250}" 31 | EndProject 32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SevenSegmentDisplay", "Examples\Components\SevenSegmentDisplay\SevenSegmentDisplay.csproj", "{5BFC16E6-77DE-4AAD-9645-641EFABCA61E}" 33 | EndProject 34 | Global 35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 36 | Debug|Any CPU = Debug|Any CPU 37 | Release|Any CPU = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {90EA81A7-E4D2-4D0D-BCF9-3BDA729909D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {90EA81A7-E4D2-4D0D-BCF9-3BDA729909D0}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {90EA81A7-E4D2-4D0D-BCF9-3BDA729909D0}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {90EA81A7-E4D2-4D0D-BCF9-3BDA729909D0}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {DC921947-93D5-4120-952D-FF5B6D1FE09F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {DC921947-93D5-4120-952D-FF5B6D1FE09F}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {DC921947-93D5-4120-952D-FF5B6D1FE09F}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {DC921947-93D5-4120-952D-FF5B6D1FE09F}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {C9C4B277-1A7B-454C-AA32-E406AEBA61FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {C9C4B277-1A7B-454C-AA32-E406AEBA61FC}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {C9C4B277-1A7B-454C-AA32-E406AEBA61FC}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {C9C4B277-1A7B-454C-AA32-E406AEBA61FC}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {962C5E8A-6A3A-4771-BA82-022F1BCA7143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {962C5E8A-6A3A-4771-BA82-022F1BCA7143}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {962C5E8A-6A3A-4771-BA82-022F1BCA7143}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {962C5E8A-6A3A-4771-BA82-022F1BCA7143}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {BD8FEB6C-0063-447F-91AD-8BAB636BF1E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {BD8FEB6C-0063-447F-91AD-8BAB636BF1E7}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {BD8FEB6C-0063-447F-91AD-8BAB636BF1E7}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {BD8FEB6C-0063-447F-91AD-8BAB636BF1E7}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {FBDE5D8F-BC2D-4441-BF43-CB02224E62E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {FBDE5D8F-BC2D-4441-BF43-CB02224E62E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {FBDE5D8F-BC2D-4441-BF43-CB02224E62E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {FBDE5D8F-BC2D-4441-BF43-CB02224E62E4}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {D677060A-4476-420E-AB6F-1E0C7E317F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {D677060A-4476-420E-AB6F-1E0C7E317F60}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {D677060A-4476-420E-AB6F-1E0C7E317F60}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {D677060A-4476-420E-AB6F-1E0C7E317F60}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {1032759C-F88C-40BE-8F4A-57C51DF248A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {1032759C-F88C-40BE-8F4A-57C51DF248A2}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {1032759C-F88C-40BE-8F4A-57C51DF248A2}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {1032759C-F88C-40BE-8F4A-57C51DF248A2}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {DFAF29B8-DDAA-4FFB-884A-42ACA40561F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {DFAF29B8-DDAA-4FFB-884A-42ACA40561F2}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {DFAF29B8-DDAA-4FFB-884A-42ACA40561F2}.Debug|Any CPU.Build.0 = Debug|Any CPU 75 | {DFAF29B8-DDAA-4FFB-884A-42ACA40561F2}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {530772F4-9B82-4F48-A025-B0F948D59820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {530772F4-9B82-4F48-A025-B0F948D59820}.Release|Any CPU.ActiveCfg = Release|Any CPU 78 | {530772F4-9B82-4F48-A025-B0F948D59820}.Debug|Any CPU.Build.0 = Debug|Any CPU 79 | {530772F4-9B82-4F48-A025-B0F948D59820}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {E1589800-F0C8-4166-A6CE-9364E4E06250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {E1589800-F0C8-4166-A6CE-9364E4E06250}.Release|Any CPU.ActiveCfg = Release|Any CPU 82 | {E1589800-F0C8-4166-A6CE-9364E4E06250}.Debug|Any CPU.Build.0 = Debug|Any CPU 83 | {E1589800-F0C8-4166-A6CE-9364E4E06250}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {5BFC16E6-77DE-4AAD-9645-641EFABCA61E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 85 | {5BFC16E6-77DE-4AAD-9645-641EFABCA61E}.Release|Any CPU.ActiveCfg = Release|Any CPU 86 | {5BFC16E6-77DE-4AAD-9645-641EFABCA61E}.Debug|Any CPU.Build.0 = Debug|Any CPU 87 | {5BFC16E6-77DE-4AAD-9645-641EFABCA61E}.Release|Any CPU.Build.0 = Release|Any CPU 88 | EndGlobalSection 89 | GlobalSection(SolutionProperties) = preSolution 90 | HideSolutionNode = FALSE 91 | EndGlobalSection 92 | GlobalSection(ExtensibilityGlobals) = postSolution 93 | SolutionGuid = {3D4626B6-8072-4798-BB8C-EAA16808A897} 94 | EndGlobalSection 95 | GlobalSection(NestedProjects) = preSolution 96 | {E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F} = {72AD87B6-356E-4501-9E63-E7FDB6C78F63} 97 | {C9C4B277-1A7B-454C-AA32-E406AEBA61FC} = {72AD87B6-356E-4501-9E63-E7FDB6C78F63} 98 | {962C5E8A-6A3A-4771-BA82-022F1BCA7143} = {72AD87B6-356E-4501-9E63-E7FDB6C78F63} 99 | {BD8FEB6C-0063-447F-91AD-8BAB636BF1E7} = {72AD87B6-356E-4501-9E63-E7FDB6C78F63} 100 | {FBDE5D8F-BC2D-4441-BF43-CB02224E62E4} = {72AD87B6-356E-4501-9E63-E7FDB6C78F63} 101 | {D677060A-4476-420E-AB6F-1E0C7E317F60} = {E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F} 102 | {1032759C-F88C-40BE-8F4A-57C51DF248A2} = {E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F} 103 | {DFAF29B8-DDAA-4FFB-884A-42ACA40561F2} = {E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F} 104 | {530772F4-9B82-4F48-A025-B0F948D59820} = {E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F} 105 | {E1589800-F0C8-4166-A6CE-9364E4E06250} = {E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F} 106 | {5BFC16E6-77DE-4AAD-9645-641EFABCA61E} = {E3DCEE1D-9553-4F1A-BEE5-5E553A0BF90F} 107 | EndGlobalSection 108 | EndGlobal 109 | -------------------------------------------------------------------------------- /SimpleGPIO/Boards/BroadcomBoard.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | 3 | namespace SimpleGPIO.Boards; 4 | 5 | /// A device with a BCM2xxx series chip 6 | public abstract class BroadcomBoard : IDisposable 7 | { 8 | private readonly Func _pinInterfaceFactory; 9 | 10 | protected BroadcomBoard(Func pinInterfaceFactory) 11 | { 12 | _pinInterfaceFactory = pinInterfaceFactory; 13 | } 14 | 15 | private readonly IPinInterface?[] _gpio = new IPinInterface[28]; 16 | 17 | private IPinInterface GetGPIO(byte bcmIdentifier) 18 | => _gpio[bcmIdentifier] ??= _pinInterfaceFactory(bcmIdentifier); 19 | 20 | /// The pin labeled GPIO0 21 | public IPinInterface GPIO0 => GetGPIO(0); 22 | 23 | /// The pin labeled GPIO1 24 | public IPinInterface GPIO1 => GetGPIO(1); 25 | 26 | /// The pin labeled GPIO2 27 | public IPinInterface GPIO2 => GetGPIO(2); 28 | 29 | /// The pin labeled GPIO3 30 | public IPinInterface GPIO3 => GetGPIO(3); 31 | 32 | /// The pin labeled GPIO4 33 | public IPinInterface GPIO4 => GetGPIO(4); 34 | 35 | /// The pin labeled GPIO5 36 | public IPinInterface GPIO5 => GetGPIO(5); 37 | 38 | /// The pin labeled GPIO6 39 | public IPinInterface GPIO6 => GetGPIO(6); 40 | 41 | /// The pin labeled GPIO7 42 | public IPinInterface GPIO7 => GetGPIO(7); 43 | 44 | /// The pin labeled GPIO8 45 | public IPinInterface GPIO8 => GetGPIO(8); 46 | 47 | /// The pin labeled GPIO9 48 | public IPinInterface GPIO9 => GetGPIO(9); 49 | 50 | /// The pin labeled GPIO10 51 | public IPinInterface GPIO10 => GetGPIO(10); 52 | 53 | /// The pin labeled GPIO11 54 | public IPinInterface GPIO11 => GetGPIO(11); 55 | 56 | /// The pin labeled GPIO12 57 | public IPinInterface GPIO12 => GetGPIO(12); 58 | 59 | /// The pin labeled GPIO13 60 | public IPinInterface GPIO13 => GetGPIO(13); 61 | 62 | /// The pin labeled GPIO14 63 | public IPinInterface GPIO14 => GetGPIO(14); 64 | 65 | /// The pin labeled GPIO15 66 | public IPinInterface GPIO15 => GetGPIO(15); 67 | 68 | /// The pin labeled GPIO16 69 | public IPinInterface GPIO16 => GetGPIO(16); 70 | 71 | /// The pin labeled GPIO17 72 | public IPinInterface GPIO17 => GetGPIO(17); 73 | 74 | /// The pin labeled GPIO18 75 | public IPinInterface GPIO18 => GetGPIO(18); 76 | 77 | /// The pin labeled GPIO19 78 | public IPinInterface GPIO19 => GetGPIO(19); 79 | 80 | /// The pin labeled GPIO21 81 | public IPinInterface GPIO20 => GetGPIO(20); 82 | 83 | /// The pin labeled GPIO21 84 | public IPinInterface GPIO21 => GetGPIO(21); 85 | 86 | /// The pin labeled GPIO22 87 | public IPinInterface GPIO22 => GetGPIO(22); 88 | 89 | /// The pin labeled GPIO23 90 | public IPinInterface GPIO23 => GetGPIO(23); 91 | 92 | /// The pin labeled GPIO24 93 | public IPinInterface GPIO24 => GetGPIO(24); 94 | 95 | /// The pin labeled GPIO25 96 | public IPinInterface GPIO25 => GetGPIO(25); 97 | 98 | /// The pin labeled GPIO26 99 | public IPinInterface GPIO26 => GetGPIO(26); 100 | 101 | /// The pin labeled GPIO27 102 | public IPinInterface GPIO27 => GetGPIO(27); 103 | 104 | public void Dispose() 105 | { 106 | Dispose(true); 107 | GC.SuppressFinalize(this); 108 | } 109 | 110 | protected virtual void Dispose(bool disposing) 111 | { 112 | if (!disposing) 113 | { 114 | return; 115 | } 116 | 117 | for (var id = 0; id < 28; id++) 118 | { 119 | _gpio[id]?.Dispose(); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /SimpleGPIO/Boards/RaspberryPi.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.Device; 2 | using SimpleGPIO.GPIO; 3 | 4 | namespace SimpleGPIO.Boards; 5 | 6 | /// A Raspberry Pi 2/3/4 7 | public sealed class RaspberryPi : BroadcomBoard 8 | { 9 | /// Creates a new Raspberry Pi 2/3/4 10 | public RaspberryPi() : this(PinInterfaceFactory.NewPinInterface) 11 | { 12 | } 13 | 14 | /// Internal constructor for unit testing 15 | /// The factory method to create pin interfaces 16 | public RaspberryPi(Func pinInterfaceFactory) : base(pinInterfaceFactory) 17 | { 18 | } 19 | 20 | /// The pin 2 rows down on the left, corresponding to GPIO2 21 | public IPinInterface Pin3 => GPIO2; 22 | 23 | /// The pin 3 rows down on the left, corresponding to GPIO3 24 | public IPinInterface Pin5 => GPIO3; 25 | 26 | /// The pin 4 rows down on the left, corresponding to GPIO4 27 | public IPinInterface Pin7 => GPIO4; 28 | 29 | /// The pin 4 rows down on the right, corresponding to GPIO14 30 | public IPinInterface Pin8 => GPIO14; 31 | 32 | /// The pin 5 rows down on the right, corresponding to GPIO15 33 | public IPinInterface Pin10 => GPIO15; 34 | 35 | /// The pin 6 rows down on the left, corresponding to GPIO17 36 | public IPinInterface Pin11 => GPIO17; 37 | 38 | /// The pin 6 rows down on the right, corresponding to GPIO18 39 | public IPinInterface Pin12 => GPIO18; 40 | 41 | /// The pin 7 rows down on the left, corresponding to GPIO27 42 | public IPinInterface Pin13 => GPIO27; 43 | 44 | /// The pin 8 rows down on the left, corresponding to GPIO22 45 | public IPinInterface Pin15 => GPIO22; 46 | 47 | /// The pin 8 rows down on the right, corresponding to GPIO23 48 | public IPinInterface Pin16 => GPIO23; 49 | 50 | /// The pin 9 rows down on the right, corresponding to GPIO24 51 | public IPinInterface Pin18 => GPIO24; 52 | 53 | /// The pin 10 rows down on the left, corresponding to GPIO10 54 | public IPinInterface Pin19 => GPIO10; 55 | 56 | /// The pin 11 rows down (10 rows up from the bottom) on the left, corresponding to GPIO9 57 | public IPinInterface Pin21 => GPIO9; 58 | 59 | /// The pin 11 rows down (10 rows up from the bottom) on the right, corresponding to GPIO25 60 | public IPinInterface Pin22 => GPIO25; 61 | 62 | /// The pin 12 rows down (9 rows up from the bottom) on the left, corresponding to GPIO11 63 | public IPinInterface Pin23 => GPIO11; 64 | 65 | /// The pin 12 rows down (9 rows up from the bottom) on the right, corresponding to GPIO8 66 | public IPinInterface Pin24 => GPIO8; 67 | 68 | /// The pin 13 rows down (8 rows up from the bottom) on the right, corresponding to GPIO7 69 | public IPinInterface Pin26 => GPIO7; 70 | 71 | /// The pin 14 rows down (7 rows up from the bottom) on the left, corresponding to GPIO0 72 | public IPinInterface Pin27 => GPIO0; 73 | 74 | /// The pin 14 rows down (7 rows up from the bottom) on the right, corresponding to GPIO1 75 | public IPinInterface Pin28 => GPIO1; 76 | 77 | /// The pin 15 rows down (6 rows up from the bottom) on the left, corresponding to GPIO5 78 | public IPinInterface Pin29 => GPIO5; 79 | 80 | /// The pin 16 rows down (5 rows up from the bottom) on the left, corresponding to GPIO6 81 | public IPinInterface Pin31 => GPIO6; 82 | 83 | /// The pin 16 rows down (5 rows up from the bottom) on the right, corresponding to GPIO12 84 | public IPinInterface Pin32 => GPIO12; 85 | 86 | /// The pin 17 rows down (4 rows up from the bottom) on the left, corresponding to GPIO13 87 | public IPinInterface Pin33 => GPIO13; 88 | 89 | /// The pin 18 rows down (3 rows up from the bottom) on the left, corresponding to GPIO19 90 | public IPinInterface Pin35 => GPIO19; 91 | 92 | /// The pin 18 rows down (3 rows up from the bottom) on the right, corresponding to GPIO16 93 | public IPinInterface Pin36 => GPIO16; 94 | 95 | /// The pin 19 rows down (2nd bottom row) on the left, corresponding to GPIO26 96 | public IPinInterface Pin37 => GPIO26; 97 | 98 | /// The pin 19 rows down (2nd bottom row) on the right, corresponding to GPIO20 99 | public IPinInterface Pin38 => GPIO20; 100 | 101 | /// The pin 20 rows down (bottom row) on the right, corresponding to GPIO21 102 | public IPinInterface Pin40 => GPIO21; 103 | } -------------------------------------------------------------------------------- /SimpleGPIO/Components/DotMatrix.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | using SimpleGPIO.Power; 3 | 4 | namespace SimpleGPIO.Components; 5 | 6 | /// An 8x8 dot matrix LED array 7 | public sealed class DotMatrix 8 | { 9 | /// The set of pins representing the rows of the matrix 10 | public IPinInterface[] Row { get; } 11 | 12 | /// The set of pins representing the rows of the matrix 13 | public IPinInterface[] Column { get; } 14 | 15 | /// Creates a new 8x8 dot matrix LED array 16 | /// The set of pins controlling the component 17 | public DotMatrix(PinSet input) 18 | { 19 | Row = new[] { input.Pin9, input.Pin14, input.Pin8, input.Pin12, input.Pin1, input.Pin7, input.Pin2, input.Pin5 }; 20 | Column = new[] { input.Pin13, input.Pin3, input.Pin4, input.Pin10, input.Pin6, input.Pin11, input.Pin15, input.Pin16 }; 21 | 22 | foreach (var column in Column) 23 | { 24 | column.PowerMode = PowerMode.Differential; 25 | } 26 | } 27 | 28 | /// Sets the values for each row in the display 29 | /// The power values for each row 30 | public void SetRows(PowerSet rows) 31 | { 32 | Row[0].Power = rows.Item1; 33 | Row[1].Power = rows.Item2; 34 | Row[2].Power = rows.Item3; 35 | Row[3].Power = rows.Item4; 36 | Row[4].Power = rows.Item5; 37 | Row[5].Power = rows.Item6; 38 | Row[6].Power = rows.Item7; 39 | Row[7].Power = rows.Item8; 40 | } 41 | 42 | /// Sets the values for each column in the display 43 | /// The power values for each column 44 | public void SetColumns(PowerSet columns) 45 | { 46 | Column[0].Power = columns.Item1; 47 | Column[1].Power = columns.Item2; 48 | Column[2].Power = columns.Item3; 49 | Column[3].Power = columns.Item4; 50 | Column[4].Power = columns.Item5; 51 | Column[5].Power = columns.Item6; 52 | Column[6].Power = columns.Item7; 53 | Column[7].Power = columns.Item8; 54 | } 55 | 56 | /// Sets each row to the same value 57 | /// The power value to set each row to 58 | public void SetAllRows(PowerValue power) 59 | => SetRows(new PowerSet 60 | { 61 | Item1 = power, 62 | Item2 = power, 63 | Item3 = power, 64 | Item4 = power, 65 | Item5 = power, 66 | Item6 = power, 67 | Item7 = power, 68 | Item8 = power 69 | }); 70 | 71 | /// Sets each column to the same value 72 | /// The power value to set each column to 73 | public void SetAllColumns(PowerValue power) 74 | => SetColumns(new PowerSet 75 | { 76 | Item1 = power, 77 | Item2 = power, 78 | Item3 = power, 79 | Item4 = power, 80 | Item5 = power, 81 | Item6 = power, 82 | Item7 = power, 83 | Item8 = power 84 | }); 85 | 86 | /// Sets all pixels to the same value 87 | /// The power value to set each pixel to 88 | public void SetAll(PowerValue power) 89 | { 90 | SetAllRows(power); 91 | SetAllColumns(power); 92 | } 93 | 94 | /// The set of pins input into the component 95 | public sealed class PinSet 96 | { 97 | public IPinInterface Pin1 { get; init; } = null!; 98 | public IPinInterface Pin2 { get; init; } = null!; 99 | public IPinInterface Pin3 { get; init; } = null!; 100 | public IPinInterface Pin4 { get; init; } = null!; 101 | public IPinInterface Pin5 { get; init; } = null!; 102 | public IPinInterface Pin6 { get; init; } = null!; 103 | public IPinInterface Pin7 { get; init; } = null!; 104 | public IPinInterface Pin8 { get; init; } = null!; 105 | public IPinInterface Pin9 { get; init; } = null!; 106 | public IPinInterface Pin10 { get; init; } = null!; 107 | public IPinInterface Pin11 { get; init; } = null!; 108 | public IPinInterface Pin12 { get; init; } = null!; 109 | public IPinInterface Pin13 { get; init; } = null!; 110 | public IPinInterface Pin14 { get; init; } = null!; 111 | public IPinInterface Pin15 { get; init; } = null!; 112 | public IPinInterface Pin16 { get; init; } = null!; 113 | } 114 | 115 | /// The set of power values for a row or column 116 | public sealed class PowerSet 117 | { 118 | public PowerValue Item1 { get; init; } 119 | public PowerValue Item2 { get; init; } 120 | public PowerValue Item3 { get; init; } 121 | public PowerValue Item4 { get; init; } 122 | public PowerValue Item5 { get; init; } 123 | public PowerValue Item6 { get; init; } 124 | public PowerValue Item7 { get; init; } 125 | public PowerValue Item8 { get; init; } 126 | } 127 | } -------------------------------------------------------------------------------- /SimpleGPIO/Components/Motor.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using SimpleGPIO.GPIO; 3 | 4 | namespace SimpleGPIO.Components; 5 | 6 | /// A DC motor controlled by an L293D 7 | public sealed class Motor 8 | { 9 | /// The list of possible rotation directions 10 | public enum Rotation 11 | { 12 | Clockwise, 13 | Counterclockwise 14 | } 15 | 16 | private readonly IPinInterface? _enabled; 17 | private readonly IPinInterface _clockwise; 18 | private readonly IPinInterface? _counterclockwise; 19 | 20 | /// The motor's direction of rotation 21 | public Rotation Direction { get; set; } = Rotation.Clockwise; 22 | 23 | /// Creates a new DC motor 24 | /// The pin controlling if the motor is enabled 25 | /// The pin controlling clockwise rotation 26 | /// The pin controlling counter-clockwise rotation 27 | public Motor(IPinInterface? enabledPin, IPinInterface clockwisePin, IPinInterface? counterclockwisePin = null) 28 | { 29 | _enabled = enabledPin; 30 | _clockwise = clockwisePin; 31 | _counterclockwise = counterclockwisePin; 32 | } 33 | 34 | /// Starts the motor running in the set direction 35 | public void Start() 36 | { 37 | switch (Direction) 38 | { 39 | case Rotation.Clockwise: 40 | _clockwise.TurnOn(); 41 | _counterclockwise?.TurnOff(); 42 | break; 43 | case Rotation.Counterclockwise: 44 | _clockwise.TurnOff(); 45 | _counterclockwise?.TurnOn(); 46 | break; 47 | default: 48 | throw new InvalidEnumArgumentException(nameof(Direction)); 49 | } 50 | _enabled?.TurnOn(); 51 | } 52 | 53 | /// Starts the motor running clockwise 54 | public void TurnClockwise() 55 | { 56 | Direction = Rotation.Clockwise; 57 | Start(); 58 | } 59 | 60 | /// Starts the motor running counter-clockwise 61 | public void TurnCounterclockwise() 62 | { 63 | Direction = Rotation.Counterclockwise; 64 | Start(); 65 | } 66 | 67 | /// Runs the motor in the set direction for a given duration 68 | /// The duration to run the motor for 69 | /// Coast once the duration is up, otherwise stop immediately 70 | public async Task RunFor(TimeSpan length, bool coast = false) 71 | { 72 | Start(); 73 | await Task.Delay(length); 74 | 75 | if (coast) 76 | Coast(); 77 | else 78 | Stop(); 79 | } 80 | 81 | /// Runs the motor clockwise for a given duration 82 | /// The duration to run the motor for 83 | /// Coast once the duration is up, otherwise stop immediately 84 | public async Task TurnClockwiseFor(TimeSpan length, bool coast = false) 85 | { 86 | Direction = Rotation.Clockwise; 87 | await RunFor(length, coast); 88 | } 89 | 90 | /// Runs the motor counter-clockwise for a given duration 91 | /// The duration to run the motor for 92 | /// Coast once the duration is up, otherwise stop immediately 93 | public async Task TurnCounterclockwiseFor(TimeSpan length, bool coast = false) 94 | { 95 | Direction = Rotation.Counterclockwise; 96 | await RunFor(length, coast); 97 | } 98 | 99 | /// Stops the motor immediately 100 | public void Stop() 101 | { 102 | _clockwise.TurnOff(); 103 | _counterclockwise?.TurnOff(); 104 | _enabled?.TurnOff(); 105 | } 106 | 107 | /// Lets the motor coast to a stop 108 | public void Coast() => _enabled?.TurnOff(); 109 | } -------------------------------------------------------------------------------- /SimpleGPIO/Components/RGBLED.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using SimpleGPIO.GPIO; 3 | 4 | namespace SimpleGPIO.Components; 5 | 6 | /// An LED with separate diodes for red, green, and blue 7 | public sealed class RGBLED 8 | { 9 | private readonly IPinInterface _red; 10 | private readonly IPinInterface _green; 11 | private readonly IPinInterface _blue; 12 | 13 | /// Creates a new RGB LED 14 | /// The pin controlling the red diode 15 | /// The pin controlling the green diode 16 | /// The pin controlling the blue diode 17 | public RGBLED(IPinInterface redPin, IPinInterface greenPin, IPinInterface bluePin) 18 | { 19 | _red = redPin; 20 | _green = greenPin; 21 | _blue = bluePin; 22 | } 23 | 24 | /// Sets the LED to the specified color 25 | /// The color to make the LED 26 | public void SetColor(Color color) 27 | { 28 | SetComponent(_red, color.R); 29 | SetComponent(_green, color.G); 30 | SetComponent(_blue, color.B); 31 | } 32 | 33 | private static void SetComponent(IPinInterface pin, byte value) 34 | => pin.Strength = value / 2.55; 35 | 36 | /// Turns the LED off 37 | public void TurnOff() => SetColor(Color.Black); 38 | 39 | /// Fades the LED to the specified color over a given duration 40 | /// The color to fade to 41 | /// The duration to fade over 42 | public async Task FadeTo(Color color, TimeSpan duration) 43 | => await Task.WhenAll( 44 | _red.FadeTo(color.R / 2.55, duration), 45 | _green.FadeTo(color.G / 2.55, duration), 46 | _blue.FadeTo(color.B / 2.55, duration) 47 | ); 48 | 49 | /// Fades the LED out to black/off 50 | /// The duration to fade over 51 | public async Task FadeOut(TimeSpan duration) 52 | => await FadeTo(Color.Black, duration); 53 | 54 | /// Sets the LED to the specified color, and then fades out over a given duration 55 | /// The color to fade to 56 | /// The duration to fade over 57 | public async Task Pulse(Color color, TimeSpan duration) 58 | { 59 | SetColor(color); 60 | await FadeOut(duration); 61 | } 62 | } -------------------------------------------------------------------------------- /SimpleGPIO/Components/RotaryEncoder.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | using SimpleGPIO.Power; 3 | 4 | namespace SimpleGPIO.Components; 5 | 6 | /// A rotary encoder input device 7 | public sealed class RotaryEncoder 8 | { 9 | private readonly IPinInterface _increase; 10 | private readonly IPinInterface _decrease; 11 | 12 | /// Creates a new rotary encoder 13 | /// The pin connected to the "CLK" contact 14 | /// The pin connected to the "DT" contact 15 | public RotaryEncoder(IPinInterface clockPin, IPinInterface dataPin) 16 | { 17 | _increase = clockPin; 18 | _decrease = dataPin; 19 | } 20 | 21 | /// Configures the action to run when the encoder is rotated clockwise 22 | /// The action to perform 23 | public void OnIncrease(Action action) 24 | { 25 | _increase.OnPowerOn(() => 26 | { 27 | if (_decrease.Power == PowerValue.Off) 28 | action(); 29 | }); 30 | } 31 | 32 | /// Configures the action to run when the encoder is rotated counter-clockwise 33 | /// The action to perform 34 | public void OnDecrease(Action action) 35 | { 36 | _decrease.OnPowerOn(() => 37 | { 38 | if (_increase.Power == PowerValue.Off) 39 | action(); 40 | }); 41 | } 42 | } -------------------------------------------------------------------------------- /SimpleGPIO/Components/SevenSegmentDisplay.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | using SimpleGPIO.Power; 3 | 4 | namespace SimpleGPIO.Components; 5 | 6 | /// A seven segment display 7 | public sealed class SevenSegmentDisplay 8 | { 9 | /// The set of pins connected to the component 10 | public sealed class PinSet : Set 11 | { 12 | } 13 | 14 | /// The set of power values for each segment in the component 15 | public sealed class PowerSet : Set 16 | { 17 | } 18 | 19 | public abstract class Set 20 | { 21 | public T? Center { get; init; } 22 | public T? UpperLeft { get; init; } 23 | public T? Top { get; init; } 24 | public T? UpperRight { get; init; } 25 | public T? LowerLeft { get; init; } 26 | public T? Bottom { get; init; } 27 | public T? LowerRight { get; init; } 28 | public T? Decimal { get; init; } 29 | } 30 | 31 | /// The set of pins connected to each segment in the component 32 | public PinSet Segments { get; } 33 | 34 | /// Creates a seven segment display 35 | /// The set of pins connected to the component 36 | public SevenSegmentDisplay(PinSet segments) 37 | => Segments = segments; 38 | 39 | private void SetPowerValues(PowerSet segments) 40 | { 41 | SetPowerValue(Segments.Center, segments.Center); 42 | SetPowerValue(Segments.UpperLeft, segments.UpperLeft); 43 | SetPowerValue(Segments.Top, segments.Top); 44 | SetPowerValue(Segments.UpperRight, segments.UpperRight); 45 | SetPowerValue(Segments.LowerLeft, segments.LowerLeft); 46 | SetPowerValue(Segments.Bottom, segments.Bottom); 47 | SetPowerValue(Segments.LowerRight, segments.LowerRight); 48 | SetPowerValue(Segments.Decimal, segments.Decimal); 49 | } 50 | 51 | private static void SetPowerValue(IPinInterface? segment, PowerValue value) 52 | { 53 | if (segment != null) 54 | { 55 | segment.Power = value; 56 | } 57 | } 58 | 59 | /// Displays a given character on the display 60 | /// The character to show 61 | public void Show(char character) 62 | { 63 | var showChar = CharacterMappings.ContainsKey(character) 64 | ? CharacterMappings[character] 65 | : CharacterMappings[' ']; 66 | showChar(); 67 | } 68 | 69 | private IDictionary? _mappings; 70 | 71 | private IDictionary CharacterMappings 72 | => _mappings ??= new Dictionary 73 | { 74 | { ' ', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 75 | { '"', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 76 | { '\'', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 77 | { '(', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 78 | { ')', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 79 | { ',', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 80 | { '-', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 81 | { '.', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.On }) }, 82 | { '/', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 83 | 84 | { '0', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 85 | { '1', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 86 | { '2', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 87 | { '3', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 88 | { '4', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 89 | { '5', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 90 | { '6', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 91 | { '7', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 92 | { '8', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 93 | { '9', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 94 | 95 | { '<', () => _mappings?['c']() }, 96 | { '=', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 97 | { '>', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 98 | 99 | { 'A', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 100 | { 'B', () => _mappings?['8']() }, 101 | { 'C', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 102 | { 'D', () => _mappings?['0']() }, 103 | { 'E', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 104 | { 'F', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 105 | { 'G', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 106 | { 'H', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 107 | { 'I', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 108 | { 'J', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 109 | { 'K', () => _mappings?['H']() }, 110 | { 'L', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 111 | { 'M', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 112 | { 'N', () => _mappings?['H']() }, 113 | { 'O', () => _mappings?['0']() }, 114 | { 'P', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 115 | { 'Q', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.On }) }, 116 | { 'R', () => _mappings?['A']() }, 117 | { 'S', () => _mappings?['5']() }, 118 | { 'T', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 119 | { 'U', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 120 | { 'V', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 121 | { 'W', () => _mappings?['U']() }, 122 | { 'X', () => _mappings?['H']() }, 123 | { 'Y', () => _mappings?['4']() }, 124 | { 'Z', () => _mappings?['2']() }, 125 | 126 | { '[', () => _mappings?['(']() }, 127 | { '\\', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 128 | { ']', () => _mappings?[')']() }, 129 | { '^', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 130 | { '_', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 131 | { '`', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 132 | 133 | { 'a', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 134 | { 'b', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 135 | { 'c', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 136 | { 'd', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 137 | { 'e', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 138 | { 'f', () => _mappings?['F']() }, 139 | { 'g', () => _mappings?['9']() }, 140 | { 'h', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 141 | { 'i', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 142 | { 'j', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 143 | { 'k', () => _mappings?['h']() }, 144 | { 'l', () => _mappings?['I']() }, 145 | { 'm', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 146 | { 'n', () => _mappings?['m']() }, 147 | { 'o', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 148 | { 'p', () => _mappings?['P']() }, 149 | { 'q', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.On, UpperRight = PowerValue.On, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 150 | { 'r', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 151 | { 's', () => _mappings?['S']() }, 152 | { 't', () => SetPowerValues(new PowerSet { Center = PowerValue.On, UpperLeft = PowerValue.On, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 153 | { 'u', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.Off, UpperRight = PowerValue.Off, LowerLeft = PowerValue.On, Bottom = PowerValue.On, LowerRight = PowerValue.On, Decimal = PowerValue.Off }) }, 154 | { 'v', () => _mappings?['u']() }, 155 | { 'w', () => _mappings?['u']() }, 156 | { 'x', () => _mappings?['X']() }, 157 | { 'y', () => _mappings?['Y']() }, 158 | { 'z', () => _mappings?['Z']() }, 159 | 160 | { '{', () => _mappings?['(']() }, 161 | { '|', () => _mappings?['I']() }, 162 | { '}', () => _mappings?[')']() }, 163 | { '~', () => SetPowerValues(new PowerSet { Center = PowerValue.Off, UpperLeft = PowerValue.Off, Top = PowerValue.On, UpperRight = PowerValue.Off, LowerLeft = PowerValue.Off, Bottom = PowerValue.Off, LowerRight = PowerValue.Off, Decimal = PowerValue.Off }) }, 164 | }; 165 | } -------------------------------------------------------------------------------- /SimpleGPIO/Components/ShiftRegister.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | using SimpleGPIO.Power; 3 | 4 | namespace SimpleGPIO.Components; 5 | 6 | /// A 595-style 8-bit shift register 7 | public sealed class ShiftRegister : SPI.Input 8 | { 9 | private readonly IPinInterface? _enabled; 10 | private readonly IPinInterface _output; 11 | private readonly IPinInterface? _clear; 12 | 13 | /// Creates a new shift register 14 | /// The pin controlling if the register is enabled 15 | /// The pin controlling the data input to the register 16 | /// The pin controlling when to shift bits 17 | /// The pin controlling when to output data 18 | /// The pin controlling when to clear the register 19 | public ShiftRegister(IPinInterface enabledPin, IPinInterface dataPin, IPinInterface shiftPin, IPinInterface outputPin, IPinInterface? clearPin = null) : base(dataPin, shiftPin) 20 | { 21 | _enabled = enabledPin; 22 | _output = outputPin; 23 | _clear = clearPin; 24 | 25 | if (_enabled != null) 26 | _enabled.PowerMode = PowerMode.Differential; 27 | 28 | if (_clear != null) 29 | _clear.PowerMode = PowerMode.Differential; 30 | } 31 | 32 | /// Sets the value stored in the register 33 | /// The value to store 34 | public void SetValue(byte value) => Send(value); 35 | 36 | /// Sets the power values stored in the register 37 | /// The values to store 38 | public void SetPowerValues(PowerSet values) 39 | { 40 | _enabled?.TurnOn(); 41 | Send(new[] { values.A, values.B, values.C, values.D, values.E, values.F, values.G, values.H }); 42 | _output.Spike(); 43 | } 44 | 45 | /// Clears the value stored in the register 46 | public void Clear() 47 | { 48 | _clear?.TurnOn(); 49 | _output.Spike(); 50 | _clear?.TurnOff(); 51 | } 52 | 53 | /// The set of power values stored in the register 54 | public sealed class PowerSet 55 | { 56 | public PowerValue A { get; init; } 57 | public PowerValue B { get; init; } 58 | public PowerValue C { get; init; } 59 | public PowerValue D { get; init; } 60 | public PowerValue E { get; init; } 61 | public PowerValue F { get; init; } 62 | public PowerValue G { get; init; } 63 | public PowerValue H { get; init; } 64 | } 65 | } -------------------------------------------------------------------------------- /SimpleGPIO/Device/GpioControllerWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Device.Gpio; 2 | 3 | namespace SimpleGPIO.Device; 4 | 5 | internal sealed class GpioControllerWrapper : IGpioController 6 | { 7 | public GpioController Base { get; } = new(); 8 | 9 | public bool IsPinOpen(byte pin) => Base.IsPinOpen(pin); 10 | public void OpenPin(byte pin) => Base.OpenPin(pin); 11 | public void ClosePin(byte pin) => Base.ClosePin(pin); 12 | 13 | public PinMode GetPinMode(byte pin) => Base.GetPinMode(pin); 14 | public void SetPinMode(byte pin, PinMode mode) => Base.SetPinMode(pin, mode); 15 | 16 | public PinValue Read(byte pin) => Base.Read(pin); 17 | public void Write(byte pin, PinValue value) => Base.Write(pin, value); 18 | 19 | public void RegisterCallbackForPinValueChangedEvent(byte pin, PinEventTypes rising, PinChangeEventHandler e) 20 | => Base.RegisterCallbackForPinValueChangedEvent(pin, rising, e); 21 | 22 | public void Dispose() => Base.Dispose(); 23 | } -------------------------------------------------------------------------------- /SimpleGPIO/Device/IGpioController.cs: -------------------------------------------------------------------------------- 1 | using System.Device.Gpio; 2 | 3 | namespace SimpleGPIO.Device; 4 | 5 | public interface IGpioController : IDisposable 6 | { 7 | GpioController Base { get; } 8 | bool IsPinOpen(byte pin); 9 | void OpenPin(byte pin); 10 | void ClosePin(byte pin); 11 | PinMode GetPinMode(byte pin); 12 | void SetPinMode(byte pin, PinMode mode); 13 | PinValue Read(byte pin); 14 | void Write(byte pin, PinValue value); 15 | void RegisterCallbackForPinValueChangedEvent(byte pin, PinEventTypes rising, PinChangeEventHandler e); 16 | } -------------------------------------------------------------------------------- /SimpleGPIO/Device/IPwmChannel.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Device; 2 | 3 | /// A wrapper around .NET's pulse width modulation implementation 4 | public interface IPwmChannel : IDisposable 5 | { 6 | /// Starts the PWM thread 7 | void Start(); 8 | 9 | /// Stops the PWM thread 10 | void Stop(); 11 | 12 | /// The frequency (in hertz) of the PWM 13 | int Frequency { get; set; } 14 | 15 | /// The duty cycle factor (from 0 to 1) that power is on 16 | double DutyCycle { get; set; } 17 | } -------------------------------------------------------------------------------- /SimpleGPIO/Device/PinInterfaceFactory.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | 3 | namespace SimpleGPIO.Device; 4 | 5 | internal static class PinInterfaceFactory 6 | { 7 | public static IPinInterface NewPinInterface(byte pin) 8 | => new SystemPinInterface(pin, GPIOController, PWM(pin)); 9 | 10 | private static readonly IGpioController GPIOController = new GpioControllerWrapper(); 11 | private static IPwmChannel PWM(byte pin) => new PwmChannelWrapper(pin, GPIOController); 12 | } -------------------------------------------------------------------------------- /SimpleGPIO/Device/PwmChannelWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Device.Pwm; 2 | using System.Device.Pwm.Drivers; 3 | 4 | namespace SimpleGPIO.Device; 5 | 6 | internal sealed class PwmChannelWrapper : IPwmChannel 7 | { 8 | private readonly PwmChannel _pwm; 9 | 10 | public PwmChannelWrapper(byte pin, IGpioController controller) 11 | => _pwm = new SoftwarePwmChannel(pin, 480, 0, true, controller.Base, false); 12 | 13 | public int Frequency 14 | { 15 | get => _pwm.Frequency; 16 | set => _pwm.Frequency = value; 17 | } 18 | 19 | public double DutyCycle 20 | { 21 | get => _pwm.DutyCycle; 22 | set => _pwm.DutyCycle = value; 23 | } 24 | 25 | public void Start() => _pwm.Start(); 26 | public void Stop() => _pwm.Stop(); 27 | 28 | public void Dispose() => _pwm.Dispose(); 29 | } -------------------------------------------------------------------------------- /SimpleGPIO/GPIO/IPinInterface.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.IO; 2 | using SimpleGPIO.Power; 3 | 4 | namespace SimpleGPIO.GPIO; 5 | 6 | /// An interface to a GPIO pin 7 | public interface IPinInterface : IDisposable 8 | { 9 | /// Whether the pin is enabled or not 10 | bool Enabled { get; set; } 11 | 12 | /// The I/O mode of the pin 13 | IOMode IOMode { get; set; } 14 | 15 | /// The direction of the pin 16 | Direction Direction { get; set; } 17 | 18 | /// The power mode of the pin 19 | IPowerMode PowerMode { get; set; } 20 | 21 | /// The power value of the pin 22 | PowerValue Power { get; set; } 23 | 24 | /// The voltage of the pin 25 | Voltage Voltage { get; set; } 26 | 27 | /// The percent strength (from 0 to 100) of the pin 28 | double Strength { get; set; } 29 | 30 | /// Enables the pin for use 31 | void Enable(); 32 | 33 | /// Disables the pin 34 | void Disable(); 35 | 36 | /// Turns on the pin 37 | void TurnOn(); 38 | 39 | /// Turns off the pin 40 | void TurnOff(); 41 | 42 | /// Quickly turns the pin on then off 43 | void Spike(); 44 | 45 | /// Turns the pin on for the specified duration, then turns it off 46 | /// The duration to turn on for 47 | Task TurnOnFor(TimeSpan length); 48 | 49 | /// Turns the pin off for the specified duration, then turns it off 50 | /// The duration to turn off for 51 | Task TurnOffFor(TimeSpan length); 52 | 53 | /// Turns the pin on if it is off, or off if it is on 54 | void Toggle(); 55 | 56 | /// Toggles the pin's power for a given duration 57 | /// The frequency to toggle power at 58 | /// The duration to toggle power for 59 | Task Toggle(double hz, TimeSpan duration); 60 | 61 | /// Toggles the pin's power for a given number of iterations 62 | /// The frequency to toggle power at 63 | /// The number of times to toggle power 64 | Task Toggle(double hz, ulong iterations); 65 | 66 | /// Fades the pin's strength from its current value up to 100% 67 | /// The duration to stretch the fade over 68 | Task FadeIn(TimeSpan duration); 69 | 70 | /// Fades the pin's strength from its current value down to 0% 71 | /// The duration to stretch the fade over 72 | Task FadeOut(TimeSpan duration); 73 | 74 | /// Fades the pin's strength from its current value to the given value 75 | /// The strength to fade to 76 | /// The duration to stretch the fade over 77 | Task FadeTo(double strength, TimeSpan duration); 78 | 79 | /// Sets the pin to full strength, then fades it out 80 | /// The duration to stretch the fade over 81 | Task Pulse(TimeSpan duration); 82 | 83 | /// Sets the pin to the given strength, then fades it out 84 | /// The maximum strength of the pulse 85 | /// The duration to stretch the fade over 86 | Task Pulse(double strength, TimeSpan duration); 87 | 88 | /// Perform an action when power to the pin is turned on 89 | /// The action to perform 90 | void OnPowerOn(Action action); 91 | 92 | /// Perform an action when power to the pin is turned off 93 | /// The action to perform 94 | void OnPowerOff(Action action); 95 | 96 | /// Perform an action when power to the pin is turned on or off 97 | /// The action to perform 98 | void OnPowerChange(Action action); 99 | } -------------------------------------------------------------------------------- /SimpleGPIO/GPIO/PinInterface.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using SimpleGPIO.IO; 3 | using SimpleGPIO.Power; 4 | 5 | namespace SimpleGPIO.GPIO; 6 | 7 | /// An abstract GPIO pin 8 | public abstract class PinInterface : IPinInterface 9 | { 10 | public abstract bool Enabled { get; set; } 11 | 12 | public IOMode IOMode 13 | { 14 | get => Direction.ToIOMode(); 15 | set => Direction = value.ToDirection(); 16 | } 17 | 18 | public abstract Direction Direction { get; set; } 19 | 20 | private IPowerMode _powerMode = SimpleGPIO.Power.PowerMode.Direct; 21 | 22 | public IPowerMode PowerMode 23 | { 24 | get => _powerMode; 25 | set 26 | { 27 | _powerMode = value; 28 | TurnOff(); 29 | } 30 | } 31 | 32 | public PowerValue Power 33 | { 34 | get => Voltage.ToPowerValue(PowerMode); 35 | set => Voltage = value == PowerValue.On ? PowerMode.On : PowerMode.Off; 36 | } 37 | 38 | public abstract Voltage Voltage { get; set; } 39 | 40 | protected double _strength; 41 | 42 | public double Strength 43 | { 44 | get => _strength; 45 | set 46 | { 47 | _strength = value.Clamp(0, 100); 48 | 49 | if (Power == PowerValue.On && System.Math.Abs(_strength) < double.Epsilon) 50 | { 51 | TurnOff(); 52 | } 53 | 54 | RefreshPWM(); 55 | 56 | if (Power != PowerValue.On && _strength > double.Epsilon) 57 | { 58 | TurnOn(); 59 | } 60 | } 61 | } 62 | 63 | protected abstract void RefreshPWM(); 64 | 65 | public void Enable() => Enabled = true; 66 | public void Disable() => Enabled = false; 67 | 68 | public void TurnOn() => Power = PowerValue.On; 69 | public void TurnOff() => Power = PowerValue.Off; 70 | 71 | public void Spike() 72 | { 73 | TurnOn(); 74 | TurnOff(); 75 | } 76 | 77 | public async Task TurnOnFor(TimeSpan length) 78 | { 79 | TurnOn(); 80 | await Task.Delay(length); 81 | TurnOff(); 82 | } 83 | 84 | public async Task TurnOffFor(TimeSpan length) 85 | { 86 | TurnOff(); 87 | await Task.Delay(length); 88 | TurnOn(); 89 | } 90 | 91 | 92 | public void Toggle() => Power = Power == PowerValue.Off ? PowerValue.On : PowerValue.Off; 93 | 94 | public async Task Toggle(double hz, TimeSpan duration) 95 | { 96 | var delay = Delay(hz); 97 | var stopwatch = Stopwatch.StartNew(); 98 | var expected = hz * duration.TotalSeconds; 99 | var count = 0; 100 | while (stopwatch.Elapsed.Ticks <= duration.Ticks && count++ < expected) 101 | await RunToggleIteration(stopwatch, delay); 102 | } 103 | 104 | public async Task Toggle(double hz, ulong iterations) 105 | { 106 | var delay = Delay(hz); 107 | var stopwatch = Stopwatch.StartNew(); 108 | ulong run = 0; 109 | while (run++ < iterations) 110 | await RunToggleIteration(stopwatch, delay); 111 | } 112 | 113 | private async Task RunToggleIteration(Stopwatch stopwatch, long delay) 114 | { 115 | await RunToggleHalfIteration(stopwatch, delay); 116 | await RunToggleHalfIteration(stopwatch, delay); 117 | } 118 | 119 | private async Task RunToggleHalfIteration(Stopwatch stopwatch, long delay) 120 | { 121 | var start = stopwatch.Elapsed.Ticks; 122 | Toggle(); 123 | var end = stopwatch.Elapsed.Ticks; 124 | var spent = end - start; 125 | await Task.Delay(TimeSpan.FromTicks(spent < delay ? delay - spent : 1)); 126 | } 127 | 128 | 129 | private static long Delay(double hz) => (long) (TimeSpan.TicksPerSecond / hz / 2); 130 | 131 | public async Task FadeIn(TimeSpan duration) => await FadeTo(100, duration); 132 | public async Task FadeOut(TimeSpan duration) => await FadeTo(0, duration); 133 | 134 | public async Task FadeTo(double strength, TimeSpan duration) 135 | { 136 | var initial = _strength; 137 | var timer = Stopwatch.StartNew(); 138 | 139 | while (timer.Elapsed < duration) 140 | { 141 | var progress = timer.Elapsed.TotalMilliseconds / duration.TotalMilliseconds; 142 | Strength = initial + (strength - initial) * progress; 143 | await Task.Delay(TimeSpan.FromSeconds(1.0 / 60)); 144 | } 145 | 146 | Strength = strength; 147 | } 148 | 149 | public async Task Pulse(double strength, TimeSpan duration) 150 | { 151 | Strength = strength; 152 | await FadeOut(duration); 153 | } 154 | 155 | public async Task Pulse(TimeSpan duration) => await Pulse(100, duration); 156 | 157 | public abstract void OnPowerOn(Action action); 158 | public abstract void OnPowerOff(Action action); 159 | public abstract void OnPowerChange(Action action); 160 | 161 | public void Dispose() 162 | { 163 | Dispose(true); 164 | GC.SuppressFinalize(this); 165 | } 166 | 167 | protected virtual void Dispose(bool disposing) 168 | { 169 | if (disposing) 170 | { 171 | Disable(); 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /SimpleGPIO/GPIO/SystemPinInterface.cs: -------------------------------------------------------------------------------- 1 | using System.Device.Gpio; 2 | using SimpleGPIO.Device; 3 | using SimpleGPIO.IO; 4 | using SimpleGPIO.Power; 5 | 6 | namespace SimpleGPIO.GPIO; 7 | 8 | /// A GPIO pin backed by .NET's IoT libraries 9 | public sealed class SystemPinInterface : PinInterface 10 | { 11 | private readonly byte _pin; 12 | private readonly IGpioController _controller; 13 | private readonly IPwmChannel _pwm; 14 | 15 | public SystemPinInterface(byte bcmIdentifier, IGpioController controller, IPwmChannel pwm) 16 | { 17 | _pin = bcmIdentifier; 18 | _controller = controller; 19 | _pwm = pwm; 20 | } 21 | 22 | public override bool Enabled 23 | { 24 | get => _controller.IsPinOpen(_pin); 25 | set 26 | { 27 | if (value) 28 | _controller.OpenPin(_pin); 29 | else 30 | _controller.ClosePin(_pin); 31 | } 32 | } 33 | 34 | public override Direction Direction 35 | { 36 | get 37 | { 38 | if (!Enabled) 39 | Enable(); 40 | 41 | return _controller.GetPinMode(_pin) == PinMode.Output ? Direction.Out : Direction.In; 42 | } 43 | set 44 | { 45 | if (!Enabled) 46 | Enable(); 47 | 48 | _controller.SetPinMode(_pin, value == Direction.Out ? PinMode.Output : PinMode.Input); 49 | } 50 | } 51 | 52 | public override Voltage Voltage 53 | { 54 | get 55 | { 56 | if (!Enabled) 57 | Enable(); 58 | 59 | return _controller.Read(_pin) == PinValue.High ? Voltage.High : Voltage.Low; 60 | } 61 | set 62 | { 63 | if (!Enabled) 64 | Enable(); 65 | 66 | if (IOMode != IOMode.Write) 67 | IOMode = IOMode.Write; 68 | 69 | _controller.Write(_pin, value == Voltage.High ? PinValue.High : PinValue.Low); 70 | 71 | RefreshPWM(); 72 | } 73 | } 74 | 75 | protected override void RefreshPWM() 76 | { 77 | _pwm.DutyCycle = _strength / 100; 78 | 79 | if (Power == PowerValue.On && _strength is > 0 and < 100) 80 | { 81 | _pwm.Start(); 82 | } 83 | else 84 | { 85 | _pwm.Stop(); 86 | } 87 | } 88 | 89 | public override void OnPowerOn(Action action) 90 | { 91 | if (!Enabled) 92 | Enable(); 93 | 94 | _controller.RegisterCallbackForPinValueChangedEvent(_pin, PinEventTypes.Rising, (_, _) => action()); 95 | } 96 | 97 | public override void OnPowerOff(Action action) 98 | { 99 | if (!Enabled) 100 | Enable(); 101 | 102 | _controller.RegisterCallbackForPinValueChangedEvent(_pin, PinEventTypes.Falling, (_, _) => action()); 103 | } 104 | 105 | public override void OnPowerChange(Action action) 106 | { 107 | OnPowerOn(action); 108 | OnPowerOff(action); 109 | } 110 | 111 | protected override void Dispose(bool disposing) 112 | { 113 | if (disposing) 114 | { 115 | _pwm.Dispose(); 116 | } 117 | 118 | base.Dispose(true); 119 | } 120 | } -------------------------------------------------------------------------------- /SimpleGPIO/IO/Direction.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.IO; 2 | 3 | /// The I/O status of a pin 4 | public enum Direction 5 | { 6 | /// The pin is reading / receiving electricity 7 | In, 8 | 9 | /// The pin is writing / outputting electricity 10 | Out 11 | } -------------------------------------------------------------------------------- /SimpleGPIO/IO/IOHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace SimpleGPIO.IO; 4 | 5 | public static class IOHelpers 6 | { 7 | /// Converts a direction to an I/O mode 8 | /// The direction of the pin 9 | /// The I/O mode of the pin 10 | public static IOMode ToIOMode(this Direction direction) 11 | => direction switch 12 | { 13 | Direction.In => IOMode.Read, 14 | Direction.Out => IOMode.Write, 15 | _ => throw new InvalidEnumArgumentException(nameof(direction)) 16 | }; 17 | 18 | /// Converts an I/O mode to a direction 19 | /// The I/O mode of the pin 20 | /// The direction of the pin 21 | public static Direction ToDirection(this IOMode io) 22 | => io switch 23 | { 24 | IOMode.Read => Direction.In, 25 | IOMode.Write => Direction.Out, 26 | _ => throw new InvalidEnumArgumentException(nameof(io)) 27 | }; 28 | } -------------------------------------------------------------------------------- /SimpleGPIO/IO/IOMode.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.IO; 2 | 3 | /// The I/O mode of a pin 4 | public enum IOMode 5 | { 6 | /// The pin is reading / receiving electricity 7 | Read = Direction.In, 8 | 9 | /// The pin is writing / outputting electricity 10 | Write = Direction.Out 11 | } -------------------------------------------------------------------------------- /SimpleGPIO/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | 3 | namespace System.Runtime.CompilerServices; 4 | 5 | using ComponentModel; 6 | 7 | // ReSharper disable once UnusedType.Global 8 | [EditorBrowsable(EditorBrowsableState.Never)] 9 | internal static class IsExternalInit 10 | { 11 | } -------------------------------------------------------------------------------- /SimpleGPIO/Math.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO; 2 | 3 | public static class Math 4 | { 5 | public static T Clamp(this T value, T min, T max) where T : IComparable 6 | => value.CompareTo(min) < 0 ? min 7 | : value.CompareTo(max) > 0 ? max 8 | : value; 9 | } -------------------------------------------------------------------------------- /SimpleGPIO/Power/Differential.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Power; 2 | 3 | /// Differential power mode, where electricity flows from a 3.3V or 5V pin to a GPIO pin 4 | public sealed class Differential : IPowerMode 5 | { 6 | public Voltage On => Voltage.Low; 7 | public Voltage Off => Voltage.High; 8 | } -------------------------------------------------------------------------------- /SimpleGPIO/Power/Direct.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Power; 2 | 3 | /// Direct power mode, where electricity flows from a GPIO pin to a ground pin 4 | public sealed class Direct : IPowerMode 5 | { 6 | public Voltage On => Voltage.High; 7 | public Voltage Off => Voltage.Low; 8 | } -------------------------------------------------------------------------------- /SimpleGPIO/Power/IPowerMode.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Power; 2 | 3 | /// The power mode of a pin 4 | public interface IPowerMode 5 | { 6 | /// The voltage when power is on 7 | Voltage On { get; } 8 | 9 | /// The voltage when power is off 10 | Voltage Off { get; } 11 | } -------------------------------------------------------------------------------- /SimpleGPIO/Power/PowerHelpers.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Power; 2 | 3 | public static class PowerHelpers 4 | { 5 | /// Converts a voltage value to a power value 6 | /// The voltage to convert 7 | /// The power mode of the pin 8 | /// The power value of the pin 9 | public static PowerValue ToPowerValue(this Voltage voltage, IPowerMode powerMode) 10 | => voltage == powerMode.On 11 | ? PowerValue.On 12 | : PowerValue.Off; 13 | } -------------------------------------------------------------------------------- /SimpleGPIO/Power/PowerMode.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Power; 2 | 3 | /// The power mode of a pin 4 | public static class PowerMode 5 | { 6 | private static IPowerMode? _direct; 7 | private static IPowerMode? _differential; 8 | 9 | /// Direct power mode, where electricity flows from a GPIO pin to a ground pin 10 | public static IPowerMode Direct => _direct ??= new Direct(); 11 | 12 | /// Differential power mode, where electricity flows from a 3.3V or 5V pin to a GPIO pin 13 | public static IPowerMode Differential => _differential ??= new Differential(); 14 | 15 | } -------------------------------------------------------------------------------- /SimpleGPIO/Power/PowerValue.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Power; 2 | 3 | /// The power value of a pin 4 | public enum PowerValue 5 | { 6 | /// Power is on, electricity flowing through the pin 7 | On = 1, 8 | 9 | /// Power is off, electricity not flowing through the pin 10 | Off = 0 11 | } -------------------------------------------------------------------------------- /SimpleGPIO/Power/Voltage.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleGPIO.Power; 2 | 3 | /// The voltage of of pin 4 | public enum Voltage 5 | { 6 | /// Low voltage 7 | Low = 0, 8 | 9 | /// High voltage 10 | High = 1 11 | } -------------------------------------------------------------------------------- /SimpleGPIO/SPI/Input.cs: -------------------------------------------------------------------------------- 1 | using SimpleGPIO.GPIO; 2 | using SimpleGPIO.Power; 3 | 4 | namespace SimpleGPIO.SPI; 5 | 6 | /// A component that accepts SPI input 7 | public abstract class Input 8 | { 9 | private readonly IPinInterface _clock; 10 | private readonly IPinInterface _data; 11 | 12 | protected Input(IPinInterface data, IPinInterface clock) 13 | { 14 | _clock = clock; 15 | _data = data; 16 | } 17 | 18 | /// Sends a power value to the component 19 | /// The power value to send 20 | public void Send(PowerValue value) 21 | { 22 | _data.Power = value; 23 | _clock.Spike(); 24 | } 25 | 26 | /// Sends a set of power values to the component 27 | /// The power values to send 28 | public void Send(IEnumerable values) 29 | { 30 | foreach (var value in values) 31 | { 32 | Send(value); 33 | } 34 | } 35 | 36 | /// Sends a byte of data to the component 37 | /// The byte to send 38 | public void Send(byte value) 39 | { 40 | var values = new[] 41 | { 42 | GetBinaryDigitPowerValue(value, 7), 43 | GetBinaryDigitPowerValue(value, 6), 44 | GetBinaryDigitPowerValue(value, 5), 45 | GetBinaryDigitPowerValue(value, 4), 46 | GetBinaryDigitPowerValue(value, 3), 47 | GetBinaryDigitPowerValue(value, 2), 48 | GetBinaryDigitPowerValue(value, 1), 49 | GetBinaryDigitPowerValue(value, 0) 50 | }; 51 | Send(values); 52 | } 53 | 54 | /// Sends a set of bytes to the component 55 | /// The bytes to send 56 | public void Send(IEnumerable values) 57 | { 58 | foreach (var value in values) 59 | { 60 | Send(value); 61 | } 62 | } 63 | 64 | private static PowerValue GetBinaryDigitPowerValue(byte value, byte digit) 65 | => ((value >> digit) & 1) == 1 66 | ? PowerValue.On 67 | : PowerValue.Off; 68 | } -------------------------------------------------------------------------------- /SimpleGPIO/SimpleGPIO.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.0.2 4 | netstandard2.0 5 | Library 6 | latest 7 | A simple, low-ceremony GPIO library for all your IoT needs 8 | ecoAPM LLC 9 | ecoAPM LLC 10 | ecoAPM LLC 11 | MIT 12 | https://github.com/ecoAPM/SimpleGPIO 13 | https://github.com/ecoAPM/SimpleGPIO 14 | true 15 | README.md 16 | true 17 | true 18 | enable 19 | enable 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------