├── .github └── workflows │ └── develop-ci.yml ├── .gitignore ├── .vscode └── launch.json ├── Contributing.md ├── Docs ├── Clima.HackKit │ └── readme.md ├── Clima.MobileApp │ └── readme.md ├── Clima.Pro │ ├── Assembly_Instructions │ │ ├── Electronics_Installation │ │ │ ├── Antenna_Cable_Meadow.png │ │ │ ├── Antenna_Cable_Mount.png │ │ │ ├── Battery.png │ │ │ ├── Cover_Install.png │ │ │ ├── PCB_Install.png │ │ │ └── readme.md │ │ ├── Enclosure_Assembly │ │ │ ├── Enclosure_Bottom.png │ │ │ ├── M3_Heat_Set.png │ │ │ ├── M3_Heat_Set_Insert.png │ │ │ ├── M3_Plunge.png │ │ │ ├── M3_Plunge_Outside.png │ │ │ ├── M3_Trim.png │ │ │ ├── Mount.png │ │ │ └── readme.md │ │ ├── Enclosure_Mounting │ │ │ ├── IMG_9521.png │ │ │ ├── IMG_9522.png │ │ │ ├── IMG_9523_Cropped.png │ │ │ └── readme.md │ │ ├── Solar_Panel_Installation │ │ │ ├── Panel_Install_1.png │ │ │ ├── Panel_Install_2.png │ │ │ ├── Panel_Mount_Back_Install.png │ │ │ ├── Panel_Mount_Front_Install.png │ │ │ └── readme.md │ │ ├── Weather_Sensors │ │ │ ├── clima_anemometer.jpg │ │ │ ├── clima_anemometer_cabling_to_wind_vane.jpg │ │ │ ├── clima_complete.jpg │ │ │ ├── clima_connections.jpg │ │ │ ├── clima_meadow.jpg │ │ │ ├── clima_rainmeter_support.jpg │ │ │ ├── clima_rainmeter_under.jpg │ │ │ ├── clima_rainmeter_under_2.jpg │ │ │ ├── clima_screws.jpg │ │ │ ├── clima_support.jpg │ │ │ ├── clima_tubes.jpg │ │ │ ├── clima_wind_wire.jpg │ │ │ ├── clima_windvane.jpg │ │ │ ├── clima_windvane_anemometer.jpg │ │ │ ├── clima_windvane_connection.jpg │ │ │ └── readme.md │ │ └── readme.md │ ├── Bill_of_Materials.md │ └── Deploy_Instructions │ │ ├── GitHub_Clone.png │ │ └── readme.md └── readme.md ├── Hardware_Design ├── BOM_Wilderness Labs - Clima_2021-11-09.csv ├── v1.a │ └── Schematic_Wilderness Labs - Clima_2021-11-09.pdf ├── v1.b │ └── Gerber_Wilderness Labs - Clima PCB v1.b.zip ├── v3.d │ └── Schematic_Wilderness Labs - Clima_2023-05-04.pdf └── v4.b │ └── Schematic_Wilderness-Labs-Clima_2024-04-18.pdf ├── Image_Assets ├── Clima.HackKit.fzz ├── Clima.Pro_Logo.fig ├── Clima.Pro_Logo_PoweredByMeadow.png ├── Clima.Pro_Logo_PoweredByMeadow.svg ├── ClimaPro.jpg ├── Clima_w_new_Solar_Circuit.jpg ├── clima-banner.jpg └── wildernesslabs-clima-v3-specs.jpg ├── Industrial_Design ├── readme.md ├── v1 │ ├── Enclosure │ │ ├── STLs_for_Printing │ │ │ ├── Clima_Enclosure_Bottom.stl │ │ │ ├── Clima_Enclosure_Top.stl │ │ │ ├── Pipe_Mount_Clamp_Closure_Side.stl │ │ │ ├── Pipe_Mount_Enclosure_Side.stl │ │ │ └── Pipe_Mount_Solar_Adapter.stl │ │ └── Source │ │ │ ├── Clima.Pro Enclosure.f3d │ │ │ └── Clima_Clamp.f3d │ └── Enclosure_Design.png ├── v3 │ └── Enclosure │ │ ├── Clima_Enclosure.png │ │ ├── STLs_for_Printing │ │ ├── CL_Enclosure_Bottom.stl │ │ ├── CL_Enclosure_Top.stl │ │ ├── Pipe_Mount_Clamp_Closure_Side.stl │ │ ├── Pipe_Mount_Enclosure_Side.stl │ │ └── Pipe_Mount_Solar_Adapter.stl │ │ └── Source │ │ ├── Clima v3 Enclosure.f3z │ │ └── Clima_Clamp.f3d └── v4 │ ├── Clima4_Enclosure_Bottom.stl │ ├── Clima4_Enclosure_Top.stl │ ├── clima4-cleat-f.stl │ └── clima4-cleat-m.stl ├── LICENSE ├── README.md └── Source ├── .editorconfig ├── Additional Samples └── Clima_SQLite_Demo │ └── Controller │ └── ClimateMonitorAgent.cs ├── Clima_Demo ├── Clima_Demo.csproj ├── Commands │ └── RestartCommand.cs ├── MeadowApp.cs ├── app.build.yaml ├── app.config.yaml ├── meadow.config.yaml ├── readme.md └── wifi.config.yaml ├── Directory.Packages.props ├── Meadow.Clima.sln ├── Meadow.Clima ├── ClimaAppBase.cs ├── ClimaHardwareProvider.cs ├── Constants │ └── CloudEventIds.cs ├── Controllers │ ├── CloudController.cs │ ├── LocationController.cs │ ├── NetworkController.cs │ ├── NotificationController.cs │ ├── PowerController.cs │ └── SensorController.cs ├── Hardware │ ├── ClimaHardwareBase.cs │ ├── ClimaHardwareV2.cs │ ├── ClimaHardwareV3.cs │ ├── ClimaHardwareV4.cs │ └── IClimaHardware.cs ├── MainController.cs ├── Meadow.Clima.csproj ├── Models │ ├── PowerData.cs │ └── SensorData.cs └── icon.png └── Meadow.Packages.props /.github/workflows/develop-ci.yml: -------------------------------------------------------------------------------- 1 | name: Develop Build 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [ develop ] 7 | push: 8 | branches: [ develop ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - name: Checkout Meadow.Logging 17 | uses: actions/checkout@v4 18 | with: 19 | repository: WildernessLabs/Meadow.Logging 20 | path: Meadow.Logging 21 | ref: develop 22 | 23 | - name: Checkout Meadow.Units 24 | uses: actions/checkout@v4 25 | with: 26 | repository: WildernessLabs/Meadow.Units 27 | path: Meadow.Units 28 | ref: develop 29 | 30 | - name: Checkout Meadow.Contracts 31 | uses: actions/checkout@v4 32 | with: 33 | repository: WildernessLabs/Meadow.Contracts 34 | path: Meadow.Contracts 35 | ref: develop 36 | 37 | - name: Checkout Meadow.Core 38 | uses: actions/checkout@v4 39 | with: 40 | repository: WildernessLabs/Meadow.Core 41 | path: Meadow.Core 42 | ref: develop 43 | 44 | - name: Checkout MQTTnet 45 | uses: actions/checkout@v4 46 | with: 47 | repository: WildernessLabs/MQTTnet 48 | path: MQTTnet 49 | ref: develop 50 | 51 | - name: Checkout Meadow.Modbus 52 | uses: actions/checkout@v4 53 | with: 54 | repository: WildernessLabs/Meadow.Modbus 55 | path: Meadow.Modbus 56 | ref: develop 57 | 58 | - name: Checkout Meadow.Foundation 59 | uses: actions/checkout@v4 60 | with: 61 | repository: WildernessLabs/Meadow.Foundation 62 | path: Meadow.Foundation 63 | ref: develop 64 | 65 | - name: Checkout Maple 66 | uses: actions/checkout@v4 67 | with: 68 | repository: WildernessLabs/Maple 69 | path: Maple 70 | ref: develop 71 | 72 | - name: Checkout Clima 73 | uses: actions/checkout@v4 74 | with: 75 | path: Clima 76 | 77 | - name: Setup .NET 78 | uses: actions/setup-dotnet@v4 79 | with: 80 | dotnet-version: 81 | 8.x 82 | 83 | - name: Install Java SDK 84 | uses: actions/setup-java@v2 85 | with: 86 | distribution: 'adopt' 87 | java-version: '11' 88 | 89 | - name: Setup .NET SDK 90 | uses: actions/setup-dotnet@v4 91 | with: 92 | dotnet-version: 93 | 8.0.x 94 | 95 | - name: Install MAUI Workload 96 | run: dotnet workload install maui --ignore-failed-sources 97 | 98 | - name: Install MAUI Android Workload 99 | run: dotnet workload install maui-android --ignore-failed-sources 100 | 101 | - name: Build Meadow.Clima 102 | run: dotnet build -c Release Clima/Source/Meadow.Clima.sln 103 | -------------------------------------------------------------------------------- /.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 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | 353 | # macOS Garbage 354 | **/.DS_Store 355 | 356 | # secrets 357 | **/Secrets.cs 358 | Source/Clima/WildernessLabs.Clima.App/WildernessLabs.Clima.App.Android/Resources/Resource.designer.cs 359 | Source/Clima/WildernessLabs.Clima.Client/WildernessLabs.Clima.Client.Android/Resources/Resource.designer.cs 360 | Source/Clima/MobileApp/MobileApp.Android/Resources/Resource.designer.cs 361 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "COM9", 5 | "type": "meadow", 6 | "request": "launch", 7 | "preLaunchTask": "meadow: Build" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contribute to Clima 2 | 3 | **Clima** is an open-source project by [Wilderness Labs](https://www.wildernesslabs.co/) and we encourage community feedback and contributions. 4 | 5 | ## How to Contribute 6 | 7 | - **Found a bug?** [Report an issue](https://github.com/WildernessLabs/Meadow_Issues/issues) 8 | - Have a **feature idea or driver request?** [Open a new feature request](https://github.com/WildernessLabs/Meadow_Issues/issues) 9 | - Want to **contribute code?** Fork the [Clima](https://github.com/WildernessLabs/Clima) repository and submit a pull request against the `develop` branch 10 | 11 | ## Pull Requests 12 | 13 | 1. All PRs should target the `develop` branch on the Clima repository. 14 | 2. All new public or protected classes, methods, and properties need XML comment documentation. 15 | 3. Please try to follow the existing coding patterns and practices. 16 | 17 | ## Pull Request Steps 18 | 19 | 1. Fork the repository 20 | 2. Clone your fork locally: `git clone https://github.com/WildernessLabs/Clima` 21 | 3. Switch to the `develop` branch 22 | 4. Create a new branch: `git checkout -b feature/your-contribution` 23 | 5. Make your changes and commit: `git commit -m 'Added/Updated [feature/fix]` 24 | 6. Push to your fork: `git push origin feature/your-contribution` 25 | 7. Open a pull request at [Clima/pulls](https://github.com/WildernessLabs/Clima/pulls) targetting the `develop` branch 26 | ## Need Help? 27 | 28 | If you have questions or need assistance, please join the Wilderness Labs [community on Slack](http://slackinvite.wildernesslabs.co/). 29 | -------------------------------------------------------------------------------- /Docs/Clima.HackKit/readme.md: -------------------------------------------------------------------------------- 1 | # Clima Hack Kit Circuit 2 | 3 | With an LM35 Analog Temperature sensor, a TFT SPI ST7789 display and three push buttons (all included in the Hack Kit), wire your board like the following Fritzing Diagram: 4 | 5 | -------------------------------------------------------------------------------- /Docs/Clima.MobileApp/readme.md: -------------------------------------------------------------------------------- 1 | ## Build and deploy the companion mobile app -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Antenna_Cable_Meadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Antenna_Cable_Meadow.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Antenna_Cable_Mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Antenna_Cable_Mount.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Battery.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Cover_Install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/Cover_Install.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/PCB_Install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/PCB_Install.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Electronics_Installation/readme.md: -------------------------------------------------------------------------------- 1 | # Clima.Pro Electronics Installation 2 | 3 | ## Install the Antenna Cable and Mount 4 | 5 | Unscrew the antenna mount and place it through the hole in the enclosure with the threads up, as shown below. Use the lock-washer on top and the tooth-washer on the bottom: 6 | 7 | ![](Antenna_Cable_Mount.png) 8 | 9 | The antenna mount should fit snuggly, you may need to screw it into place for it to fit. 10 | 11 | ## Install Battery 12 | 13 | Install the 18650 LiIon battery into the holder, paying attention to the polarity. If there is a piece of tape on the battery, remove it: 14 | 15 | ![](Battery.png) 16 | 17 | ## Attach Antenna Cable to Meadow 18 | 19 | Attach the µFL connector to the Meadow external antenna connector as illustrated below: 20 | 21 | ![](Antenna_Cable_Meadow.png) 22 | 23 | ## Install the PCB 24 | 25 | Using (4) M2x6 screws, install the assembled PCB: 26 | 27 | ![](PCB_Install.png) 28 | 29 | ## Install Cover 30 | 31 | Using the remaining (4) M2x6 screws, install the cover: 32 | 33 | ![](Cover_Install.png) 34 | 35 | ## [Next - Mount the Enclosure](../Enclosure_Mounting/readme.md) -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/Enclosure_Bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/Enclosure_Bottom.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Heat_Set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Heat_Set.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Heat_Set_Insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Heat_Set_Insert.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Plunge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Plunge.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Plunge_Outside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Plunge_Outside.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Trim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/M3_Trim.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/Mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/Mount.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Assembly/readme.md: -------------------------------------------------------------------------------- 1 | # Clima.Pro 3D Printed Enclosure Assembly 2 | 3 | If you've ordered the enclosure from the Wilderness Labs store, it already comes assembled with the heat-set inserts in it, so you can skip this step and go directly to [Electronics Installation instructions](../Electronics_Installation/readme.md). 4 | 5 | However, if you're 3D printing at home, you'll need to follow the instructions below: 6 | 7 | ## Post-process 3D Print 8 | 9 | Stringing and burrs are a by-product of printing with PETG and are mostly cosmetic, but can easily be cleaned off with a box-cutter knife, razor blade, or similar. 10 | 11 | ## Install M3 Heat-Set Inserts into Enclosure Body 12 | 13 | Install a heat-set insert tool into your soldering iron, plug it in and turn it on. After a couple of minutes it should be hot enough. Place an M3 heat-set insert onto the end of the tool, being careful not to touch the hot tip: 14 | 15 | ![](M3_Heat_Set.png) 16 | 17 | Then, while holding the enclosure, guide the insert into one of the holes until it's just flush with the top of the hole: 18 | 19 | ![](M3_Heat_Set_Insert.png) 20 | 21 | Repeat this for all four heat set inserts on the main enclosure body. 22 | 23 | ## Plunge Excess Plastic from Heat-Set Inserts 24 | 25 | Install an M3 bolt through the M3 insert from the inside of the enclosure, to push any plastic that might have melted into the insert: 26 | 27 | ![](M3_Plunge.png) 28 | 29 | Screw the bolt far enough that it pokes out the other side of the insert and pushes out the plastic: 30 | 31 | ![](M3_Plunge.png) 32 | 33 | Remove the bolt and repeat for the remaining M3 inserts. 34 | 35 | Once all four have been plungd, using a knife, trim the excess plastic: 36 | 37 | ![](M3_Trim.png) 38 | 39 | ## Install the M3 Heat-Set Inserts into the Enclosure Mount 40 | 41 | Using the same method, install (4), M3 inserts into the enclosure mount from the backside, and then push excess plastic out with a bolt and trim: 42 | 43 | ![](Mount.png) 44 | 45 | ## Install the M2 Heat-Set Inserts into the Enclosure Body 46 | 47 | Finally, install the M2 heat-set inserts for the lid into the enclosure body: 48 | 49 | ![](Enclosure_Bottom.png) 50 | 51 | ## [Next - Electronics Installation](../Electronics_Installation/readme.md) 52 | -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Mounting/IMG_9521.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Mounting/IMG_9521.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Mounting/IMG_9522.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Mounting/IMG_9522.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Mounting/IMG_9523_Cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Enclosure_Mounting/IMG_9523_Cropped.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Enclosure_Mounting/readme.md: -------------------------------------------------------------------------------- 1 | # Enclosure Mounting 2 | 3 | ![](IMG_9521.png) 4 | 5 | ## [Next - Solar Panel Installation](../Solar_Panel_Installation/readme.md) -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Install_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Install_1.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Install_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Install_2.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Mount_Back_Install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Mount_Back_Install.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Mount_Front_Install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/Panel_Mount_Front_Install.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Solar_Panel_Installation/readme.md: -------------------------------------------------------------------------------- 1 | # Solar Panel Installation 2 | 3 | ## Solar Panel Mount 4 | 5 | Using the U-bolt bracket, attach the solar panel mount to the pole: 6 | 7 | ![](Panel_Mount_Front_Install.png) 8 | 9 | ![](Panel_Mount_Back_Install.png) 10 | 11 | ## Install Panel 12 | 13 | Using the plastic nuts on the solar panel, attach it to the solar panel bracket mount. Make sure to pull the solar panel power cable through the bracket hole: 14 | 15 | ![](Panel_Install_1.png) 16 | 17 | ## [Next - Deploy the Clima App](/Docs/Clima.Pro/Deploy_Instructions/) -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_anemometer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_anemometer.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_anemometer_cabling_to_wind_vane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_anemometer_cabling_to_wind_vane.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_complete.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_complete.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_connections.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_connections.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_meadow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_meadow.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_rainmeter_support.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_rainmeter_support.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_rainmeter_under.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_rainmeter_under.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_rainmeter_under_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_rainmeter_under_2.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_screws.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_screws.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_support.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_support.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_tubes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_tubes.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_wind_wire.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_wind_wire.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_windvane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_windvane.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_windvane_anemometer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_windvane_anemometer.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_windvane_connection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/clima_windvane_connection.jpg -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/Weather_Sensors/readme.md: -------------------------------------------------------------------------------- 1 | # Weather Sensors Assembly 2 | 3 | To assemble the whole Clima kit with all it's sensors, you'll need the set of 3 long screws with it's bolts, and 2 smaller screws. They should be in a little zip lock bag like this: 4 | 5 | ![](clima_screws.jpg) 6 | 7 | ## Assembling 8 | 9 | Start by connecting the two steel tubes together like so: 10 | 11 | ![](clima_tubes.jpg) 12 | 13 | Next, one of the tube ends has a small rounded gap and a hole right below it. In that end, place the sensor support for the wind vane and anemometer. Use one of the long screws and bolts to fix the support to the tube. 14 | 15 | ![](clima_support.jpg) 16 | 17 | On the right end of the support piece you can put the anemometer. It has three possible positions to connect it to the base, keep rotating it until you see the the hole to insert another long screw and bolt to secure it to the support. You can thread the 18 | 19 | ![](clima_anemometer.jpg) 20 | 21 | On the left side of the support you can place the wind vane. Like we did with the anemometer, make sure to position it correctly to use the third and final screw and bolt to secure the sensor to the support. 22 | 23 | ![](clima_windvane.jpg) 24 | 25 | Take the short cable from the anemometer and carefully feed/clip it through the provided cable tidies until it reaches the wind vane. 26 | 27 | ![](clima_anemometer_cabling_to_wind_vane.jpg) 28 | 29 | Under the wind vane you can see a small connection port. Connect the end of the anemometer into the wind vane port underneath. 30 | 31 | ![](clima_windvane_connection.jpg) 32 | 33 | At this point, you should have something like this: 34 | 35 | ![](clima_windvane_anemometer.jpg) 36 | 37 | About an inch/2.54cm below the tube end, where the support piece is, use the rain meter support piece and secure it onto the tube, making sure it is perpendicular to the other two sensors. 38 | 39 | ![](clima_rainmeter_support.jpg) 40 | 41 | Take the rain meter and place it on the support piece and secure it with the last two small screws: one goes in the bottom and the other on one of the sides: 42 | 43 | ![](clima_rainmeter_under.jpg) 44 | 45 | ![](clima_rainmeter_under_2.jpg) 46 | 47 | About one inch/2.54cm above where the 2 steel tubes connect, mount the Clima Meadow module onto the tube. 48 | 49 | ![](clima_meadow.jpg) 50 | 51 | Now you can connect the rain meter sensor in the middle port and the wind sensor on the other connector available. The round port is used for the solar panel. 52 | 53 | ![](clima_connections.jpg) 54 | 55 | Additionally, you can tidy the cable of the wind sensors by looping it around its support piece and/or tubes, making your way to the Meadow module. 56 | 57 | ![](clima_wind_wire.jpg) 58 | 59 | You can do the same with the rain meter sensor's cable, looping it around its support piece, making your way to the Meadow enclosure. 60 | 61 | ![](clima_complete.jpg) 62 | 63 | 64 | ## [Next - Enclosure Assembly](../Enclosure_Assembly/readme.md) 65 | -------------------------------------------------------------------------------- /Docs/Clima.Pro/Assembly_Instructions/readme.md: -------------------------------------------------------------------------------- 1 | ## Clima.Pro Assembly Instructions 2 | 3 | ![](/Image_Assets/ClimaPro.jpg) 4 | 5 | * **[Part 1 - 3D Printed Enclosure Assembly](Enclosure_Assembly/readme.md)** - Skip this step unless you're printing at home. 6 | * **[Part 2 - Electronics Installation](Electronics_Installation/readme.md)** - Installing the PCB, antenna, etc., into the enclosure. 7 | * **[Part 3 - Sensor Assembly](Weather_Sensors/readme.md)** - Assembling the weather gauges and pole. 8 | * **[Part 4 - Enclosure Mounting](Enclosure_Mounting/readme.md)** - Mounting the enclosure to the sensor pole. 9 | * **[Part 5 - Solar Panel Installation](Solar_Panel_Installation/readme.md)** - Installing the solar panel. 10 | -------------------------------------------------------------------------------- /Docs/Clima.Pro/Bill_of_Materials.md: -------------------------------------------------------------------------------- 1 | 2 | # Clima.Pro Bill of Materials (BoM) 3 | 4 | Complete Clima.Pro kits can be purchased at the [Wilderness Labs Store](https://store.wildernesslabs.co/collections/frontpage/products/clima-weather-station-kit), or you can source the components yourself. The Clima.Pro BoM is listed below: 5 | 6 | | Qty | Item | Cost/MSRP (approx.) | 7 | |-----|-------------------------|-----------| 8 | | 1 | [Shenzen Fine-Offset Electronics Weather Sensor Kit](https://www.sparkfun.com/products/15901) | $ 79.95 | 9 | | 1 | Clima PCB (see below) | ~$ 25.00 | 10 | | 1 | 3d Printed Enclosure and Pipe Mount (see below) | $ 3.00 | 11 | | 1 | [18650 LiIon Battery](https://www.amazon.com/s?k=18650+battery) | $ 5.00 | 12 | | 8 | [M3 Heat Inserts](https://www.amazon.com/gp/product/B087NBYF65) | $ 5.00 | 13 | | 8 | [M3x12 Hex Head Screws](https://www.amazon.com/gp/product/B01LZPZL3W) | $ 5.00 | 14 | | 4 | [M2x3 Heat-Set Inserts](https://www.amazon.com/gp/product/B07LBRL93N/) | $ 1.00 | 15 | | 8 | [M2x6 bolts (4 for lid, 4 for board)](https://www.amazon.com/Hilitchi-420pcs-Stainless-Socket-Assortment/dp/B014OO5KQG) | $ 1.00 | 16 | | 1 | [3.5 Watt, 6V Solar Panel](https://voltaicsystems.com/3-5-watt-panel/) | $ 30.00 | 17 | | 1 | [Solar Panel Bracket](https://voltaicsystems.com/small-bracket/) | $ 6.00 | 18 | | 1 | [U-Bolt With Mounting Plate, 1/4"-20 Thread Size, 1" Id](https://www.mcmaster.com/catalog/127/1720/) | $ 2.00 | 19 | | 1 | [Antenna - 2dBi SMA Plug](https://octopart.com/ant-2whip2-sma-rf+solutions-87213220?r=sp) | $ 3.50 | 20 | | 1 | [SMA to uFL/u.FL/IPX/IPEX RF Adapter Cable](https://www.adafruit.com/product/851) | $ 4.00 | 21 | 22 | 23 | ## 3D Printed Enclosure 24 | 25 | The enclosure can be 3D printed, but should be printed using PETG or some other UV-Resistant material. PLA will degrade quickly in the sun. Additionally, it should be a light color such as white to avoid passive solar radiation absorption which will interfere with the temperature and humidity readings. We use the [Duramic 3D Silk White](https://www.amazon.com/gp/product/B07TRPQ4MN) filament. 26 | 27 | If you're printing the enclosure yourself, you'll also need a [soldering iron](https://www.amazon.com/dp/B00B3SG6UQ) and [heat-set insert tips](https://www.amazon.com/dp/B08B17VQLD) to install the heat-set inserts. 28 | 29 | All of the enclosure files can be found in the [/Industrial_Design folder](/Industrial_Design/Enclosure/). 30 | 31 | ## Clima.Pro PCB 32 | 33 | The Clima.Pro PCB was created in EasyEDA, and the source files as well as component BoM can be found on [OSHW Lab](https://oshwlab.com/bryan_6020/wilderness-labs-clima). 34 | -------------------------------------------------------------------------------- /Docs/Clima.Pro/Deploy_Instructions/GitHub_Clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/Clima.Pro/Deploy_Instructions/GitHub_Clone.png -------------------------------------------------------------------------------- /Docs/Clima.Pro/Deploy_Instructions/readme.md: -------------------------------------------------------------------------------- 1 | ## Clima.Pro Application Deployment 2 | 3 | ### Step 1. - Prepare Dev Environment 4 | 5 | * **Download and install [Visual Studio](https://visualstudio.microsoft.com/) or [Visual Studio Code](https://code.visualstudio.com/)** - For either Windows or macOS. Community edition will work fine. 6 | 7 | * **Follow the [Meadow.OS Deployment Guide](http://developer.wildernesslabs.co/Meadow/Getting_Started/Deploying_Meadow/)**: 8 | * The above guide should show you how to install `Meadow.CLI` 9 | * Walk your through out to download the latest Meadow.OS and flash it to the Clima Meadow board. 10 | 11 | * **Follow the [`Hello, Meadow` Guide](http://developer.wildernesslabs.co/Meadow/Getting_Started/Hello_World/)**: 12 | * The above guid should show you to Install the Visual Studio and VSCode Extension(s) for Meadow. 13 | 14 | ### Expected Development version stack (RC-1) 15 | At this point, you should have the following versions on your development machine 16 | * Meadow.CLI - `v2.x` 17 | * Visual Studio 18 | * MacOS - `v1.9.x` 19 | * Windows - `v1.9.x` 20 | * VS Code - `v1.9.x` 21 | 22 | * Meadow 23 | 24 | And if you run this command line: `meadow device info`, you should see something similar to: 25 | ``` 26 | Meadow by Wilderness Labs 27 | Board Information 28 | Model: F7Micro 29 | Hardware version: F7CoreComputeV2 30 | Device name: MeadowF7 31 | 32 | Hardware Information 33 | Processor type: STM32F777IIK6 34 | ID: [Specific ID for your Meadow] 35 | Serial number: [Specific Serial No. for your Meadow] 36 | Coprocessor type: ESP32 37 | MAC Address - 38 | WiFi: [Specific Mac Address for your Meadow] 39 | 40 | Firmware Versions 41 | OS: 1.10.0.2 42 | Runtime: 1.10.0.2 43 | Coprocessor: 1.10.0.1 44 | Protocol: 8 45 | ``` 46 | 47 | ### Step 2. - Deploy the Clima App 48 | 49 | **Clone the Clima Repo**: 50 | 51 | [Clone](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/adding-and-cloning-repositories/cloning-and-forking-repositories-from-github-desktop) the [Clima(this) repository](https://github.com/wildernesslabs/Clima) locally. 52 | 53 | **Open the Clima VS Solution**: 54 | 55 | With either Visual Studio or VSCode running, navigate to the `source` folder and open the `Meadow.Clima.sln`. 56 | 57 | **Add WiFi Credentials**: 58 | 59 | Edit the `secrets.cs` file to have the correct credentials for the WiFi your Clima will connect to. 60 | 61 | **Deploy the MeadowClimaProKit App** 62 | -------------------------------------------------------------------------------- /Docs/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Docs/readme.md -------------------------------------------------------------------------------- /Hardware_Design/BOM_Wilderness Labs - Clima_2021-11-09.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Hardware_Design/BOM_Wilderness Labs - Clima_2021-11-09.csv -------------------------------------------------------------------------------- /Hardware_Design/v1.a/Schematic_Wilderness Labs - Clima_2021-11-09.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Hardware_Design/v1.a/Schematic_Wilderness Labs - Clima_2021-11-09.pdf -------------------------------------------------------------------------------- /Hardware_Design/v1.b/Gerber_Wilderness Labs - Clima PCB v1.b.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Hardware_Design/v1.b/Gerber_Wilderness Labs - Clima PCB v1.b.zip -------------------------------------------------------------------------------- /Hardware_Design/v3.d/Schematic_Wilderness Labs - Clima_2023-05-04.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Hardware_Design/v3.d/Schematic_Wilderness Labs - Clima_2023-05-04.pdf -------------------------------------------------------------------------------- /Hardware_Design/v4.b/Schematic_Wilderness-Labs-Clima_2024-04-18.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Hardware_Design/v4.b/Schematic_Wilderness-Labs-Clima_2024-04-18.pdf -------------------------------------------------------------------------------- /Image_Assets/Clima.HackKit.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Image_Assets/Clima.HackKit.fzz -------------------------------------------------------------------------------- /Image_Assets/Clima.Pro_Logo.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Image_Assets/Clima.Pro_Logo.fig -------------------------------------------------------------------------------- /Image_Assets/Clima.Pro_Logo_PoweredByMeadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Image_Assets/Clima.Pro_Logo_PoweredByMeadow.png -------------------------------------------------------------------------------- /Image_Assets/Clima.Pro_Logo_PoweredByMeadow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Image_Assets/ClimaPro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Image_Assets/ClimaPro.jpg -------------------------------------------------------------------------------- /Image_Assets/Clima_w_new_Solar_Circuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Image_Assets/Clima_w_new_Solar_Circuit.jpg -------------------------------------------------------------------------------- /Image_Assets/clima-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Image_Assets/clima-banner.jpg -------------------------------------------------------------------------------- /Image_Assets/wildernesslabs-clima-v3-specs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Image_Assets/wildernesslabs-clima-v3-specs.jpg -------------------------------------------------------------------------------- /Industrial_Design/readme.md: -------------------------------------------------------------------------------- 1 | # Industrial Design 2 | 3 | ![Rendering of the Clima enclosure.](v3/Enclosure/Clima_Enclosure.png) 4 | 5 | Clima.Pro uses a 3D-Printable enclosure that holds the Meadow board and the custom Clima PCB. 6 | 7 | The enclosure was designed used AutoDesk Fusion 360, and if you want to customize it, you can find the design source in the source folders: 8 | * [V1](v1/Enclosure/Source) 9 | * [v3](v3/Enclosure/Source) 10 | 11 | The STL files can be found in the `STLs_for_Printing` folder 12 | * [V1](v1/Enclosure/STLs_for_Printing) 13 | * [V3](v3/Enclosure/STLs_for_Printing) -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure/STLs_for_Printing/Clima_Enclosure_Bottom.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure/STLs_for_Printing/Clima_Enclosure_Bottom.stl -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure/STLs_for_Printing/Clima_Enclosure_Top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure/STLs_for_Printing/Clima_Enclosure_Top.stl -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure/STLs_for_Printing/Pipe_Mount_Clamp_Closure_Side.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure/STLs_for_Printing/Pipe_Mount_Clamp_Closure_Side.stl -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure/STLs_for_Printing/Pipe_Mount_Enclosure_Side.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure/STLs_for_Printing/Pipe_Mount_Enclosure_Side.stl -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure/STLs_for_Printing/Pipe_Mount_Solar_Adapter.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure/STLs_for_Printing/Pipe_Mount_Solar_Adapter.stl -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure/Source/Clima.Pro Enclosure.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure/Source/Clima.Pro Enclosure.f3d -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure/Source/Clima_Clamp.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure/Source/Clima_Clamp.f3d -------------------------------------------------------------------------------- /Industrial_Design/v1/Enclosure_Design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v1/Enclosure_Design.png -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/Clima_Enclosure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/Clima_Enclosure.png -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/STLs_for_Printing/CL_Enclosure_Bottom.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/STLs_for_Printing/CL_Enclosure_Bottom.stl -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/STLs_for_Printing/CL_Enclosure_Top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/STLs_for_Printing/CL_Enclosure_Top.stl -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/STLs_for_Printing/Pipe_Mount_Clamp_Closure_Side.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/STLs_for_Printing/Pipe_Mount_Clamp_Closure_Side.stl -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/STLs_for_Printing/Pipe_Mount_Enclosure_Side.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/STLs_for_Printing/Pipe_Mount_Enclosure_Side.stl -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/STLs_for_Printing/Pipe_Mount_Solar_Adapter.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/STLs_for_Printing/Pipe_Mount_Solar_Adapter.stl -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/Source/Clima v3 Enclosure.f3z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/Source/Clima v3 Enclosure.f3z -------------------------------------------------------------------------------- /Industrial_Design/v3/Enclosure/Source/Clima_Clamp.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v3/Enclosure/Source/Clima_Clamp.f3d -------------------------------------------------------------------------------- /Industrial_Design/v4/Clima4_Enclosure_Bottom.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v4/Clima4_Enclosure_Bottom.stl -------------------------------------------------------------------------------- /Industrial_Design/v4/Clima4_Enclosure_Top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v4/Clima4_Enclosure_Top.stl -------------------------------------------------------------------------------- /Industrial_Design/v4/clima4-cleat-f.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v4/clima4-cleat-f.stl -------------------------------------------------------------------------------- /Industrial_Design/v4/clima4-cleat-m.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Industrial_Design/v4/clima4-cleat-m.stl -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clima is a solar-powered, custom embedded-IoT solution that tracks climate from a suite of sensors, saves data locally for access via Bluetooth, uses a RESTful Web API, and synchronizes data to the cloud. 4 | 5 | ## Contents 6 | * [Clima Pro](#clima) 7 | * [Assembly Instructions](#assembly-instructions) 8 | * [Getting Started](#getting-started) 9 | * [Hardware Specifications](#hardware-specifications) 10 | * [Support](#support) 11 | 12 | ## Clima 13 | 14 | With this kit, it includes the complete package of sensors, PCB enclosure and mount to place this outdoors. You'll be able to measure wind speed/direction, rain volume, atmospheric conditions like temperature, pressure, humidity, CO2 levels and GPS Coordinates. 15 | 16 | 17 | 18 | ## Assembly Instructions 19 | 20 | A complete kit of Clima.Pro can be found on the [Wilderness Labs Store](https://store.wildernesslabs.co/collections/frontpage/products/clima-weather-station-kit) and the Instructions for assembly can be found [here](/Docs/Clima.Pro/Assembly_Instructions/readme.md). 21 | 22 | The store version is 100% kit complete, including the option to upgrade the PCB, Enclosure and Battery only, if you include a previous version of the kit. 23 | 24 | You can also source all of the components yourself. For a list of components see the [Clima Pro Bill of Material (BoM)](/Docs/Clima.Pro/Bill_of_Materials.md) 25 | 26 | ## Getting Started 27 | 28 | To simplify the way to use this Meadow-powered reference IoT product, we've created a NuGet package that instantiates and encapsulates the onboard hardware into a `Clima` class. 29 | 30 | 1. Add the ProjectLab Nuget package your project: 31 | - `dotnet add package Meadow.Clima`, or 32 | - [Meadow.Clima Nuget Package](https://www.nuget.org/packages/Meadow.Clima/) 33 | 34 | 2. Change the App type on your MeadowApp class to `ClimaAppBase` and initialize Clima's `MainController` passing the `Hardware` and a `INetworkAdapter` such as your WiFi adapter onboard the Meadow Core Compute Module: 35 | 36 | ```csharp 37 | public class ClimaApp : ClimaAppBase 38 | { 39 | public override Task Initialize() 40 | { 41 | Resolver.Log.Info($"Initialize..."); 42 | 43 | var mainController = new MainController(); 44 | 45 | var wifi = Hardware.ComputeModule.NetworkAdapters.Primary(); 46 | 47 | mainController.Initialize( 48 | hardware: Hardware, 49 | networkAdapter: wifi); 50 | . 51 | . 52 | . 53 | ``` 54 | 55 | 3. Run the [Clima_Demo](Source/Clima_Demo/) project that does periodic readings of all its sensors and sends them to [Meadow.Cloud](https://www.meadowcloud.co) if you have a Wilderness Labs account and have provisioned your device. 56 | 57 | ## Hardware Specifications 58 | 59 | 60 | 61 | You can find the schematics and other design files in the [Hardware_Design folder](Hardware_Design/). 62 | 63 | ## Support 64 | 65 | Having trouble building/running these projects? 66 | * File an [issue](https://github.com/WildernessLabs/Meadow.Desktop.Samples/issues) with a repro case to investigate, and/or 67 | * Join our [public Slack](http://slackinvite.wildernesslabs.co/), where we have an awesome community helping, sharing and building amazing things using Meadow. -------------------------------------------------------------------------------- /Source/.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | 7 | #### Core EditorConfig Options #### 8 | 9 | # Indentation and spacing 10 | indent_size = 4 11 | indent_style = space 12 | tab_width = 4 13 | 14 | # New line preferences 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET Coding Conventions #### 19 | 20 | # Organize usings 21 | dotnet_separate_import_directive_groups = false 22 | dotnet_sort_system_directives_first = false 23 | file_header_template = unset 24 | 25 | # this. and Me. preferences 26 | dotnet_style_qualification_for_event = false 27 | dotnet_style_qualification_for_field = false 28 | dotnet_style_qualification_for_method = false 29 | dotnet_style_qualification_for_property = false 30 | 31 | # Language keywords vs BCL types preferences 32 | dotnet_style_predefined_type_for_locals_parameters_members = true 33 | dotnet_style_predefined_type_for_member_access = true 34 | 35 | # Parentheses preferences 36 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity 37 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity 38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity 40 | 41 | # Modifier preferences 42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 43 | 44 | # Expression-level preferences 45 | dotnet_style_coalesce_expression = true 46 | dotnet_style_collection_initializer = true 47 | dotnet_style_explicit_tuple_names = true 48 | dotnet_style_namespace_match_folder = true 49 | dotnet_style_null_propagation = true 50 | dotnet_style_object_initializer = true 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | dotnet_style_prefer_auto_properties = true 53 | dotnet_style_prefer_collection_expression = true 54 | dotnet_style_prefer_compound_assignment = true 55 | dotnet_style_prefer_conditional_expression_over_assignment = true 56 | dotnet_style_prefer_conditional_expression_over_return = true 57 | dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed 58 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 59 | dotnet_style_prefer_inferred_tuple_names = true 60 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 61 | dotnet_style_prefer_simplified_boolean_expressions = true 62 | dotnet_style_prefer_simplified_interpolation = true 63 | 64 | # Field preferences 65 | dotnet_style_readonly_field = true 66 | 67 | # Parameter preferences 68 | dotnet_code_quality_unused_parameters = all 69 | 70 | # Suppression preferences 71 | dotnet_remove_unnecessary_suppression_exclusions = none 72 | 73 | # New line preferences 74 | dotnet_style_allow_multiple_blank_lines_experimental = false 75 | dotnet_style_allow_statement_immediately_after_block_experimental = false 76 | 77 | #### C# Coding Conventions #### 78 | 79 | # var preferences 80 | csharp_style_var_elsewhere = true 81 | csharp_style_var_for_built_in_types = true 82 | csharp_style_var_when_type_is_apparent = true 83 | 84 | # Expression-bodied members 85 | csharp_style_expression_bodied_accessors = true 86 | csharp_style_expression_bodied_constructors = false 87 | csharp_style_expression_bodied_indexers = true 88 | csharp_style_expression_bodied_lambdas = true 89 | csharp_style_expression_bodied_local_functions = false 90 | csharp_style_expression_bodied_methods = false 91 | csharp_style_expression_bodied_operators = false 92 | csharp_style_expression_bodied_properties = true 93 | 94 | # Pattern matching preferences 95 | csharp_style_pattern_matching_over_as_with_null_check = true 96 | csharp_style_pattern_matching_over_is_with_cast_check = true 97 | csharp_style_prefer_extended_property_pattern = true 98 | csharp_style_prefer_not_pattern = true 99 | csharp_style_prefer_pattern_matching = false 100 | csharp_style_prefer_switch_expression = true 101 | 102 | # Null-checking preferences 103 | csharp_style_conditional_delegate_call = true 104 | 105 | # Modifier preferences 106 | csharp_prefer_static_local_function = true 107 | csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async 108 | csharp_style_prefer_readonly_struct = true 109 | csharp_style_prefer_readonly_struct_member = true 110 | 111 | # Code-block preferences 112 | csharp_prefer_braces = true 113 | csharp_prefer_simple_using_statement = true 114 | csharp_style_namespace_declarations = file_scoped 115 | csharp_style_prefer_method_group_conversion = true 116 | csharp_style_prefer_primary_constructors = true 117 | csharp_style_prefer_top_level_statements = false 118 | 119 | # Expression-level preferences 120 | csharp_prefer_simple_default_expression = true 121 | csharp_style_deconstructed_variable_declaration = false 122 | csharp_style_implicit_object_creation_when_type_is_apparent = true 123 | csharp_style_inlined_variable_declaration = true 124 | csharp_style_prefer_index_operator = true 125 | csharp_style_prefer_local_over_anonymous_function = true 126 | csharp_style_prefer_null_check_over_type_check = true 127 | csharp_style_prefer_range_operator = true 128 | csharp_style_prefer_tuple_swap = true 129 | csharp_style_prefer_utf8_string_literals = true 130 | csharp_style_throw_expression = true 131 | csharp_style_unused_value_assignment_preference = discard_variable 132 | csharp_style_unused_value_expression_statement_preference = discard_variable 133 | 134 | # 'using' directive preferences 135 | csharp_using_directive_placement = outside_namespace 136 | 137 | # New line preferences 138 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false 139 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true 140 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true 141 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false 142 | csharp_style_allow_embedded_statements_on_same_line_experimental = true 143 | 144 | #### C# Formatting Rules #### 145 | 146 | # New line preferences 147 | csharp_new_line_before_catch = true 148 | csharp_new_line_before_else = true 149 | csharp_new_line_before_finally = true 150 | csharp_new_line_before_members_in_anonymous_types = true 151 | csharp_new_line_before_members_in_object_initializers = true 152 | csharp_new_line_before_open_brace = all 153 | csharp_new_line_between_query_expression_clauses = true 154 | 155 | # Indentation preferences 156 | csharp_indent_block_contents = true 157 | csharp_indent_braces = false 158 | csharp_indent_case_contents = true 159 | csharp_indent_case_contents_when_block = true 160 | csharp_indent_labels = one_less_than_current 161 | csharp_indent_switch_labels = true 162 | 163 | # Space preferences 164 | csharp_space_after_cast = false 165 | csharp_space_after_colon_in_inheritance_clause = true 166 | csharp_space_after_comma = true 167 | csharp_space_after_dot = false 168 | csharp_space_after_keywords_in_control_flow_statements = true 169 | csharp_space_after_semicolon_in_for_statement = true 170 | csharp_space_around_binary_operators = before_and_after 171 | csharp_space_around_declaration_statements = false 172 | csharp_space_before_colon_in_inheritance_clause = true 173 | csharp_space_before_comma = false 174 | csharp_space_before_dot = false 175 | csharp_space_before_open_square_brackets = false 176 | csharp_space_before_semicolon_in_for_statement = false 177 | csharp_space_between_empty_square_brackets = false 178 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 179 | csharp_space_between_method_call_name_and_opening_parenthesis = false 180 | csharp_space_between_method_call_parameter_list_parentheses = false 181 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 182 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 183 | csharp_space_between_method_declaration_parameter_list_parentheses = false 184 | csharp_space_between_parentheses = false 185 | csharp_space_between_square_brackets = false 186 | 187 | # Wrapping preferences 188 | csharp_preserve_single_line_blocks = true 189 | csharp_preserve_single_line_statements = true 190 | 191 | #### Naming styles #### 192 | 193 | # Naming rules 194 | 195 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 196 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 197 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 198 | 199 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 200 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 201 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 202 | 203 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 204 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 205 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 206 | 207 | # Symbol specifications 208 | 209 | dotnet_naming_symbols.interface.applicable_kinds = interface 210 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 211 | dotnet_naming_symbols.interface.required_modifiers = 212 | 213 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 214 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 215 | dotnet_naming_symbols.types.required_modifiers = 216 | 217 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 218 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 219 | dotnet_naming_symbols.non_field_members.required_modifiers = 220 | 221 | # Naming styles 222 | 223 | dotnet_naming_style.pascal_case.required_prefix = 224 | dotnet_naming_style.pascal_case.required_suffix = 225 | dotnet_naming_style.pascal_case.word_separator = 226 | dotnet_naming_style.pascal_case.capitalization = pascal_case 227 | 228 | dotnet_naming_style.begins_with_i.required_prefix = I 229 | dotnet_naming_style.begins_with_i.required_suffix = 230 | dotnet_naming_style.begins_with_i.word_separator = 231 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 232 | -------------------------------------------------------------------------------- /Source/Additional Samples/Clima_SQLite_Demo/Controller/ClimateMonitorAgent.cs: -------------------------------------------------------------------------------- 1 | using Clima_SQLite_Demo.Database; 2 | using Clima_SQLite_Demo.Models; 3 | using Meadow.Devices; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace Clima_SQLite_Demo 8 | { 9 | public class ClimateMonitorAgent 10 | { 11 | private static readonly Lazy instance = 12 | new Lazy(() => new ClimateMonitorAgent()); 13 | public static ClimateMonitorAgent Instance => instance.Value; 14 | 15 | public event EventHandler ClimateConditionsUpdated = delegate { }; 16 | 17 | IClimaHardware clima; 18 | 19 | bool IsSampling = false; 20 | 21 | public ClimateReading? Climate { get; set; } 22 | 23 | private ClimateMonitorAgent() { } 24 | 25 | public async Task Initialize() 26 | { 27 | clima = Meadow.Devices.ClimaHardwareProvider.Create(); 28 | 29 | await StartUpdating(TimeSpan.FromSeconds(30)); 30 | } 31 | 32 | async Task StartUpdating(TimeSpan updateInterval) 33 | { 34 | Console.WriteLine("ClimateMonitorAgent.StartUpdating()"); 35 | 36 | if (IsSampling) 37 | return; 38 | IsSampling = true; 39 | 40 | ClimateReading oldClimate; 41 | 42 | while (IsSampling) 43 | { 44 | Console.WriteLine("ClimateMonitorAgent: About to do a reading."); 45 | 46 | oldClimate = Climate ?? new ClimateReading(); 47 | 48 | Climate = await Read(); 49 | 50 | var result = new ClimateConditions(Climate, oldClimate); 51 | 52 | Console.WriteLine("ClimateMonitorAgent: Reading complete."); 53 | DatabaseManager.Instance.SaveReading(result?.New); 54 | 55 | ClimateConditionsUpdated.Invoke(this, result); 56 | 57 | await Task.Delay(updateInterval).ConfigureAwait(false); 58 | } 59 | } 60 | 61 | void StopUpdating() 62 | { 63 | if (!IsSampling) 64 | return; 65 | 66 | IsSampling = false; 67 | } 68 | 69 | public async Task Read() 70 | { 71 | var tempTask = clima.TemperatureSensor?.Read(); 72 | var pressureTask = clima.BarometricPressureSensor?.Read(); 73 | var humidityTask = clima.HumiditySensor?.Read(); 74 | var windVaneTask = clima.WindVane?.Read(); 75 | var anemometerTask = clima.Anemometer?.Read(); 76 | var rainFallTask = clima.RainGauge?.Read(); 77 | 78 | await Task.WhenAll(tempTask, humidityTask, pressureTask, anemometerTask, windVaneTask, rainFallTask); 79 | 80 | var climate = new ClimateReading() 81 | { 82 | DateTime = DateTime.Now, 83 | Temperature = tempTask?.Result, 84 | Pressure = pressureTask?.Result, 85 | Humidity = humidityTask?.Result, 86 | RainFall = rainFallTask?.Result, 87 | WindDirection = windVaneTask?.Result.Compass16PointCardinalName, 88 | WindSpeed = anemometerTask?.Result, 89 | }; 90 | 91 | return climate; 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /Source/Clima_Demo/Clima_Demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.1 4 | true 5 | Library 6 | App 7 | 10 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | Always 16 | 17 | 18 | Always 19 | 20 | 21 | Always 22 | 23 | 24 | Always 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/Clima_Demo/Commands/RestartCommand.cs: -------------------------------------------------------------------------------- 1 | using Meadow; 2 | using Meadow.Cloud; 3 | using System.Threading; 4 | 5 | namespace Clima_Demo.Commands; 6 | 7 | /// 8 | /// Restart Clima 9 | /// 10 | public class RestartCommand : IMeadowCommand 11 | { 12 | /// 13 | /// Delay to restart 14 | /// 15 | public int Delay { get; set; } 16 | 17 | /// 18 | /// Initialise the command and register handler. 19 | /// 20 | public static void Initialise() 21 | { 22 | Resolver.CommandService.Subscribe(command => 23 | { 24 | Resolver.Log.Info($"RestartCommand: Meadow will restart in {command.Delay} seconds."); 25 | Thread.Sleep(command.Delay * 1000); 26 | Resolver.Device.PlatformOS.Reset(); 27 | }); 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /Source/Clima_Demo/MeadowApp.cs: -------------------------------------------------------------------------------- 1 | using Clima_Demo.Commands; 2 | using Meadow; 3 | using Meadow.Devices; 4 | using Meadow.Devices.Esp32.MessagePayloads; 5 | using Meadow.Hardware; 6 | using System.Threading.Tasks; 7 | 8 | namespace Clima_Demo; 9 | 10 | public class ClimaApp : ClimaAppBase 11 | { 12 | private MainController? mainController; 13 | 14 | public override Task Initialize() 15 | { 16 | Resolver.Log.Info($"Initialize..."); 17 | 18 | mainController = new MainController(); 19 | 20 | var reliabilityService = Resolver.Services.Get(); 21 | reliabilityService!.MeadowSystemError += OnMeadowSystemError; 22 | 23 | if (reliabilityService.LastBootWasFromCrash) 24 | { 25 | mainController.LogAppStartupAfterCrash(reliabilityService.GetCrashData()); 26 | reliabilityService.ClearCrashData(); 27 | } 28 | 29 | var wifi = Hardware.ComputeModule.NetworkAdapters.Primary(); 30 | mainController.Initialize(Hardware, wifi); 31 | 32 | return Task.CompletedTask; 33 | } 34 | 35 | public override Task Run() 36 | { 37 | var svc = Resolver.UpdateService; 38 | 39 | // Uncomment to clear any persisted update info. This allows installing 40 | // the same update multiple times, such as you might do during development. 41 | // svc.ClearUpdates(); 42 | 43 | svc.StateChanged += (sender, updateState) => 44 | { 45 | Resolver.Log.Info($"UpdateState {updateState}"); 46 | if (updateState == UpdateState.DownloadingFile) 47 | { 48 | mainController?.StopUpdating(); 49 | } 50 | }; 51 | 52 | svc.RetrieveProgress += (updateService, info) => 53 | { 54 | short percentage = (short)((double)info.DownloadProgress / info.FileSize * 100); 55 | 56 | Resolver.Log.Info($"Downloading... {percentage}%"); 57 | }; 58 | 59 | svc.UpdateAvailable += async (updateService, info) => 60 | { 61 | Resolver.Log.Info($"Update available!"); 62 | 63 | // Queue update for retrieval "later" 64 | await Task.Delay(5000); 65 | 66 | updateService.RetrieveUpdate(info); 67 | }; 68 | 69 | svc.UpdateRetrieved += async (updateService, info) => 70 | { 71 | Resolver.Log.Info($"Update retrieved!"); 72 | 73 | await Task.Delay(5000); 74 | 75 | updateService.ApplyUpdate(info); 76 | }; 77 | 78 | RestartCommand.Initialise(); 79 | 80 | return Task.CompletedTask; 81 | } 82 | 83 | private void OnMeadowSystemError(MeadowSystemErrorInfo error, bool recommendReset, out bool forceReset) 84 | { 85 | if (error is Esp32SystemErrorInfo espError) 86 | { 87 | Resolver.Log.Warn($"The ESP32 has had an error ({espError.StatusCode})."); 88 | } 89 | else 90 | { 91 | Resolver.Log.Info($"We've had a system error: {error}"); 92 | } 93 | 94 | if (recommendReset) 95 | { 96 | Resolver.Log.Warn($"Meadow is recommending a device reset"); 97 | } 98 | 99 | forceReset = recommendReset; 100 | } 101 | } -------------------------------------------------------------------------------- /Source/Clima_Demo/app.build.yaml: -------------------------------------------------------------------------------- 1 | Deploy: 2 | NoLink: [ Clima ] -------------------------------------------------------------------------------- /Source/Clima_Demo/app.config.yaml: -------------------------------------------------------------------------------- 1 | # Uncomment additional options as needed. 2 | # To learn more about these config options, including custom application configuration settings, check out the Application Settings Configuration documentation. 3 | # http://developer.wildernesslabs.co/Meadow/Meadow.OS/Configuration/Application_Settings_Configuration/ 4 | 5 | Lifecycle: 6 | # Control whether Meadow will restart when an unhandled app exception occurs. Combine with Lifecycle > AppFailureRestartDelaySeconds to control restart timing. 7 | RestartOnAppFailure: false 8 | # # When app set to restart automatically on app failure, 9 | # AppFailureRestartDelaySeconds: 15 10 | 11 | # Logging configuration. 12 | Logging: 13 | 14 | # Adjust the level of logging detail. 15 | LogLevel: 16 | 17 | # Trace, Debug, Information, Warning, or Error 18 | Default: Trace 19 | 20 | MeadowCloud: 21 | 22 | # Enable Logging, Events, Command + Control 23 | Enabled: true 24 | 25 | # Enable Over-the-air Updates 26 | EnableUpdates: true 27 | 28 | # Enable Health Metrics 29 | EnableHealthMetrics: true 30 | 31 | # How often to send metrics to Meadow.Cloud 32 | HealthMetricsIntervalMinutes: 5 33 | 34 | 35 | # Settings for Clima 36 | Clima: 37 | OffsetToNorth: 0.0 -------------------------------------------------------------------------------- /Source/Clima_Demo/meadow.config.yaml: -------------------------------------------------------------------------------- 1 | # Acceptable values for true: true, 1, yes 2 | # Acceptable values for false: false, 0, no 3 | 4 | # Main Device Config 5 | Device: 6 | 7 | # Name of the device on the network. 8 | Name: Clima 9 | 10 | # Uncomment if SD card hardware present on this hardware 11 | # (e.g., Core-Compute module with SD add-on)? Optional; default value is `false`. 12 | # SdStorageSupported: true 13 | 14 | # Control how the ESP coprocessor will start and operate. 15 | Coprocessor: 16 | 17 | # Should the ESP32 automatically attempt to connect to an access point at startup? 18 | # If set to true, wifi.yaml credentials must be stored in the device. 19 | AutomaticallyStartNetwork: false 20 | 21 | # Should the ESP32 automatically reconnect to the configured access point? 22 | AutomaticallyReconnect: true 23 | 24 | # Maximum number of retry attempts for connections etc. before an error code is returned. 25 | MaximumRetryCount: 99 26 | 27 | # Network configuration. 28 | Network: 29 | 30 | # Default Interface 31 | DefaultInterface: WiFi 32 | 33 | # Automatically attempt to get the time at startup? 34 | GetNetworkTimeAtStartup: true 35 | 36 | # Time synchronization period in seconds. 37 | NtpRefreshPeriod: 600 38 | 39 | # Name of the NTP servers. 40 | NtpServers: 41 | - 0.pool.ntp.org 42 | - 1.pool.ntp.org 43 | - 2.pool.ntp.org 44 | - 3.pool.ntp.org 45 | 46 | # IP addresses of the DNS servers. 47 | DnsServers: 48 | - 1.1.1.1 49 | - 8.8.8.8 -------------------------------------------------------------------------------- /Source/Clima_Demo/readme.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | flowchart TD 3 | %% Nodes 4 | A("Boot") 5 | B{"Connect to Cloud"} 6 | C("Deliver Data") 7 | D("Shutdown network") 8 | E("Device Sleep") 9 | F("Device Wake") 10 | G("Collect Telemetry") 11 | H{{"`tick++ % pubcount`"}} 12 | 13 | ne_0("== 0") 14 | eq_0("!= 0") 15 | 16 | %% Edge connections between nodes 17 | A --> B 18 | B --> yes --> C 19 | B --> no --> E 20 | C --> D 21 | D --> E 22 | E -.-> F 23 | F --> G 24 | G --> H 25 | H --> eq_0 --> E 26 | H --> ne_0 --> B 27 | ``` -------------------------------------------------------------------------------- /Source/Clima_Demo/wifi.config.yaml: -------------------------------------------------------------------------------- 1 | # Uncomment to set the default Wi-Fi credentials. (This file will be processed into secure storage on the ESP32 and then deleted from the device.) 2 | # To learn more about these config options, including custom application configuration settings, check out the Application Settings Configuration documentation. 3 | # http://developer.wildernesslabs.co/Meadow/Meadow.OS/Configuration/WiFi_Configuration/ 4 | 5 | # # To enable automatically connecting to a default network, make sure to enable the Coprocessor > AutomaticallyStartNetwork value in meadow.config.yaml. 6 | Credentials: 7 | Ssid: TELUSDC1E 8 | Password: tnrXFa6MVqAU 9 | -------------------------------------------------------------------------------- /Source/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | true 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Source/Meadow.Clima/ClimaAppBase.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Devices.Clima.Hardware; 2 | 3 | namespace Meadow.Devices; 4 | 5 | /// 6 | /// Base class for Clima applications. 7 | /// 8 | public abstract class ClimaAppBase : App 9 | { } -------------------------------------------------------------------------------- /Source/Meadow.Clima/ClimaHardwareProvider.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Devices.Clima.Hardware; 2 | using Meadow.Foundation.ICs.IOExpanders; 3 | using Meadow.Hardware; 4 | using Meadow.Logging; 5 | using System; 6 | 7 | namespace Meadow.Devices; 8 | 9 | /// 10 | /// Represents the Clima hardware 11 | /// 12 | public class ClimaHardwareProvider : IMeadowAppEmbeddedHardwareProvider 13 | { 14 | /// 15 | /// Initializes a new instance of the ClimaHardwareProvider class. 16 | /// 17 | public ClimaHardwareProvider() 18 | { } 19 | 20 | /// 21 | /// Creates an instance of the Clima hardware. 22 | /// 23 | /// An instance of IClimaHardware. 24 | public static IClimaHardware Create() 25 | { 26 | return new ClimaHardwareProvider() 27 | .Create(Resolver.Services.Get()!); 28 | } 29 | 30 | /// 31 | /// Create an instance of the Clima class 32 | /// 33 | /// 34 | /// 35 | public IClimaHardware Create(IMeadowDevice device) 36 | { 37 | IClimaHardware hardware; 38 | Logger? logger = Resolver.Log; 39 | II2cBus i2cBus; 40 | 41 | logger?.Debug("Initializing Clima..."); 42 | 43 | if (device == null) 44 | { 45 | var msg = "Clima instance must be created no earlier than App.Initialize()"; 46 | logger?.Error(msg); 47 | throw new Exception(msg); 48 | } 49 | 50 | i2cBus = device.CreateI2cBus(); 51 | 52 | logger?.Debug("I2C Bus instantiated"); 53 | 54 | if (device is IF7FeatherMeadowDevice { } feather) 55 | { 56 | logger?.Info("Instantiating Clima v2 specific hardware"); 57 | hardware = new ClimaHardwareV2(feather, i2cBus); 58 | } 59 | else if (device is IF7CoreComputeMeadowDevice { } ccm) 60 | { 61 | Mcp23008? mcpVersion = null; 62 | byte version = 0; 63 | 64 | try 65 | { 66 | logger?.Info("Instantiating version MCP23008"); 67 | 68 | var resetPort = ccm.Pins.D02.CreateDigitalOutputPort(); 69 | 70 | mcpVersion = new Mcp23008(i2cBus, address: 0x27, resetPort: resetPort); 71 | 72 | version = mcpVersion.ReadFromPorts(); 73 | } 74 | catch 75 | { 76 | logger?.Info("Failed to instantiate version MCP23008"); 77 | } 78 | 79 | logger?.Info($"MCP Version: {version}"); 80 | 81 | if (version >= 4) 82 | { 83 | logger?.Info("Instantiating Clima v4 specific hardware"); 84 | hardware = new ClimaHardwareV4(ccm, i2cBus, mcpVersion!); 85 | } 86 | else 87 | { 88 | logger?.Info("Instantiating Clima v3 specific hardware"); 89 | hardware = new ClimaHardwareV3(ccm, i2cBus, mcpVersion!); 90 | } 91 | } 92 | else 93 | { 94 | throw new NotSupportedException(); 95 | } 96 | 97 | return hardware; 98 | } 99 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Constants/CloudEventIds.cs: -------------------------------------------------------------------------------- 1 | namespace Meadow.Devices.Clima.Constants; 2 | 3 | /// 4 | /// Enumeration of cloud event IDs. 5 | /// 6 | public enum CloudEventIds 7 | { 8 | /// 9 | /// Event ID for when the device starts. 10 | /// 11 | DeviceStarted = 100, 12 | 13 | /// 14 | /// Event ID for telemetry data. 15 | /// 16 | Telemetry = 110, 17 | 18 | /// 19 | /// Event ID for booting from a crash. 20 | /// 21 | BootFromCrash = 200 22 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Controllers/CloudController.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Cloud; 2 | using Meadow.Devices.Clima.Constants; 3 | using Meadow.Devices.Clima.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace Meadow.Devices.Clima.Controllers; 10 | 11 | /// 12 | /// Controller for handling cloud-related operations. 13 | /// 14 | public class CloudController 15 | { 16 | /// 17 | /// Waits for all data to be sent to the cloud. 18 | /// 19 | public async Task WaitForDataToSend() 20 | { 21 | // TODO: add a timeout here 22 | while (Resolver.MeadowCloudService.QueueCount > 0) 23 | { 24 | // Resolver.Log.Info($"Waiting for {Resolver.MeadowCloudService.QueueCount} items to be delivered..."); 25 | await Task.Delay(1000); 26 | } 27 | Resolver.Log.Info($"All cloud data has been sent"); 28 | } 29 | 30 | /// 31 | /// Logs the application startup after a crash. 32 | /// 33 | public void LogAppStartupAfterCrash() 34 | { 35 | SendEvent(CloudEventIds.DeviceStarted, $"Device restarted after crash"); 36 | } 37 | 38 | /// 39 | /// Logs the application startup with the specified hardware revision. 40 | /// 41 | /// The hardware revision of the device. 42 | public void LogAppStartup(string hardwareRevision) 43 | { 44 | SendEvent(CloudEventIds.DeviceStarted, $"Device started (hardware {hardwareRevision})"); 45 | } 46 | 47 | /// 48 | /// Logs the device information including name and location. 49 | /// 50 | /// The name of the device. 51 | /// The latitude of the device location. 52 | /// The longitude of the device location. 53 | public void LogDeviceInfo(string deviceName, double latitude, double longitude) 54 | { 55 | Resolver.Log.Info("LogDeviceInfo: Create CloudEvent"); 56 | CloudEvent cloudEvent = new CloudEvent 57 | { 58 | Description = "Clima Position Telemetry", 59 | Timestamp = DateTime.UtcNow, 60 | EventId = 109, 61 | Measurements = new Dictionary { { "device_name", deviceName }, { "lat", latitude }, { "long", longitude } } 62 | }; 63 | 64 | SendEvent(cloudEvent); 65 | } 66 | 67 | /// 68 | /// Logs a warning message. 69 | /// 70 | /// The warning message to log. 71 | public void LogWarning(string message) 72 | { 73 | SendLog(message, "warning"); 74 | } 75 | 76 | /// 77 | /// Logs an informational message. 78 | /// 79 | /// The informational message to log. 80 | public void LogMessage(string message) 81 | { 82 | SendLog(message, "information"); 83 | } 84 | 85 | /// 86 | /// Logs telemetry data from sensors and power data. 87 | /// 88 | /// The sensor data to log. 89 | /// The power data to log. 90 | public void LogTelemetry(SensorData sensorData, PowerData powerData) 91 | { 92 | var measurements = sensorData 93 | .AsTelemetryDictionary() 94 | .Concat(powerData.AsTelemetryDictionary()) 95 | .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); 96 | 97 | var cloudEvent = new CloudEvent 98 | { 99 | Description = "Clima Telemetry", 100 | Timestamp = DateTime.UtcNow, 101 | EventId = (int)CloudEventIds.Telemetry, 102 | Measurements = measurements 103 | }; 104 | 105 | SendEvent(cloudEvent); 106 | } 107 | 108 | private void SendLog(string message, string severity) 109 | { 110 | if (Resolver.MeadowCloudService == null) 111 | { 112 | Resolver.Log.Warn($"CLOUD SERVICE IS NULL"); 113 | return; 114 | } 115 | 116 | if (!Resolver.MeadowCloudService.IsEnabled) 117 | { 118 | Resolver.Log.Warn($"CLOUD INTEGRATION IS DISABLED"); 119 | return; 120 | } 121 | 122 | Resolver.Log.Info($"Sending cloud log"); 123 | 124 | Resolver.MeadowCloudService.SendLog( 125 | new CloudLog 126 | { 127 | Message = message, 128 | Timestamp = DateTime.UtcNow, 129 | Severity = severity 130 | }); 131 | } 132 | 133 | private void SendEvent(CloudEventIds eventId, string message) 134 | { 135 | SendEvent(new CloudEvent 136 | { 137 | EventId = (int)eventId, 138 | Description = message, 139 | Timestamp = DateTime.UtcNow, 140 | }); 141 | } 142 | 143 | private void SendEvent(CloudEvent cloudEvent) 144 | { 145 | if (Resolver.MeadowCloudService == null) 146 | { 147 | Resolver.Log.Warn($"CLOUD SERVICE IS NULL"); 148 | return; 149 | } 150 | 151 | if (!Resolver.MeadowCloudService.IsEnabled) 152 | { 153 | Resolver.Log.Warn($"CLOUD INTEGRATION IS DISABLED"); 154 | return; 155 | } 156 | 157 | Resolver.Log.Info($"Sending cloud event"); 158 | 159 | try 160 | { 161 | Resolver.MeadowCloudService.SendEvent(cloudEvent); 162 | } 163 | catch (Exception ex) 164 | { 165 | Resolver.Log.Warn($"Failed to send cloud event: {ex.Message}"); 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Controllers/LocationController.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Devices.Clima.Hardware; 2 | using Meadow.Peripherals.Sensors.Location.Gnss; 3 | using Meadow.Units; 4 | using System; 5 | using System.Threading; 6 | 7 | namespace Meadow.Devices.Clima.Controllers; 8 | 9 | /// 10 | /// Controller for handling GNSS location data. 11 | /// 12 | public class LocationController 13 | { 14 | private readonly IGnssSensor? gnss = null; 15 | 16 | /// 17 | /// Gets or sets a value indicating whether to log data. 18 | /// 19 | public bool LogData { get; set; } = false; 20 | 21 | /// 22 | /// Event that is triggered when a GNSS position is received. 23 | /// 24 | public event EventHandler? PositionReceived = null; 25 | 26 | private ManualResetEvent positionReceived = new ManualResetEvent(false); 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// The Clima hardware interface. 32 | public LocationController(IClimaHardware clima) 33 | { 34 | if (clima.Gnss is { } gnss) 35 | { 36 | this.gnss = gnss; 37 | this.gnss.GnssDataReceived += OnGnssDataReceived; 38 | } 39 | } 40 | 41 | /// 42 | /// Gets the current geographic position as a . 43 | /// 44 | /// 45 | /// The geographic position, including latitude, longitude, and altitude, if available. 46 | /// 47 | /// 48 | /// This property is updated when valid GNSS data is received. It represents the last known position 49 | /// and remains unchanged until new valid data is processed. 50 | /// 51 | public GeographicCoordinate? Position { get; private set; } = default; 52 | 53 | private void OnGnssDataReceived(object sender, IGnssResult e) 54 | { 55 | if (e is GnssPositionInfo pi) 56 | { 57 | if (pi.IsValid && pi.Position != null) 58 | { 59 | // remember our position 60 | Position = pi.Position; 61 | // we only need one position fix - weather stations don't move 62 | Resolver.Log.InfoIf(LogData, $"GNSS Position: lat: [{pi.Position.Latitude}], long: [{pi.Position.Longitude}]"); 63 | positionReceived.Set(); 64 | PositionReceived?.Invoke(this, pi); 65 | StopUpdating(); 66 | } 67 | } 68 | } 69 | 70 | /// 71 | /// Starts the GNSS sensor to begin updating location data. 72 | /// 73 | /// 74 | /// This method invokes the method on the associated GNSS sensor, 75 | /// if it is available, to start receiving GNSS data updates. 76 | /// 77 | public void StartUpdating(bool forced = false) 78 | { 79 | // start updating if forced to find new data or we don;t have current location 80 | if (forced || !positionReceived.WaitOne(0)) 81 | { 82 | gnss?.StartUpdating(); 83 | }; 84 | } 85 | 86 | /// 87 | /// Stops the GNSS sensor from updating location data. 88 | /// 89 | /// 90 | /// This method halts the GNSS data updates by invoking the 91 | /// method on the associated GNSS sensor, if it is available. 92 | /// 93 | public void StopUpdating() 94 | { 95 | // stop listening to data arriving from GNSS 96 | gnss?.StopUpdating(); 97 | 98 | // TODO: can we tell GNSS sensor to stop calculating GPS location and stop sending data to reduce power consumption? 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Source/Meadow.Clima/Controllers/NetworkController.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Hardware; 2 | using Meadow.Logging; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Meadow.Devices.Clima.Controllers; 8 | 9 | /// 10 | /// Controller for managing network connections and reporting network status. 11 | /// 12 | public class NetworkController 13 | { 14 | /// 15 | /// Event triggered when the connection state changes. 16 | /// 17 | public event EventHandler? ConnectionStateChanged; 18 | 19 | /// 20 | /// Event triggered when the network is down for a specified period. 21 | /// 22 | public event EventHandler? NetworkDown; 23 | 24 | private readonly INetworkAdapter networkAdapter; 25 | private DateTimeOffset? lastDown; 26 | private readonly Timer downEventTimer; 27 | 28 | /// 29 | /// Gets a value indicating whether the network is connected. 30 | /// 31 | public bool IsConnected => networkAdapter.IsConnected; 32 | 33 | /// 34 | /// Gets the total time the network has been down. 35 | /// 36 | public TimeSpan DownTime => lastDown == null ? TimeSpan.Zero : DateTime.UtcNow - lastDown.Value; 37 | 38 | /// 39 | /// Gets the period for triggering network down events. 40 | /// 41 | public TimeSpan DownEventPeriod { get; } = TimeSpan.FromSeconds(30); 42 | 43 | 44 | /// 45 | /// Port used for UdpLogging. 46 | /// 47 | /// 48 | /// Default set in constructor is port 5100 49 | /// 50 | private int UdpLoggingPort { get; set; } 51 | 52 | /// 53 | /// Instance of UdpLogger. Use to remove UdpLogger if the network disconnects 54 | /// 55 | private UdpLogger? UdpLogger { get; set; } 56 | 57 | private string WifiSsid { get; set; } = "SSID"; 58 | private string WifiPassword { get; set; } = "PASSWORD"; 59 | 60 | 61 | /// 62 | /// Initializes a new instance of the class. 63 | /// 64 | /// The network adapter 65 | public NetworkController(INetworkAdapter networkAdapter) 66 | { 67 | if (networkAdapter is IWiFiNetworkAdapter wifi) 68 | { 69 | if (wifi.IsConnected) 70 | { 71 | _ = ReportWiFiScan(wifi); 72 | } 73 | 74 | // TODO: make this configurable 75 | wifi.SetAntenna(AntennaType.External); 76 | } 77 | this.networkAdapter = networkAdapter; 78 | 79 | networkAdapter.NetworkConnected += OnNetworkConnected; 80 | networkAdapter.NetworkDisconnected += OnNetworkDisconnected; 81 | 82 | downEventTimer = new Timer(DownEventTimerProc, null, -1, -1); 83 | } 84 | 85 | /// 86 | /// Connects to the cloud. 87 | /// 88 | /// A task representing the asynchronous operation. 89 | public async Task ConnectToCloud() 90 | { 91 | if (networkAdapter is IWiFiNetworkAdapter wifi) 92 | { 93 | if (!wifi.IsConnected) 94 | { 95 | Resolver.Log.Info($"Connecting to network: {WifiSsid}"); 96 | await wifi.Connect(WifiSsid, WifiPassword); 97 | } 98 | } 99 | 100 | Resolver.Log.Info($"Connecting to network {(networkAdapter.IsConnected ? "succeeded" : "FAILED")}"); 101 | 102 | return networkAdapter.IsConnected; 103 | } 104 | 105 | /// 106 | /// Shuts down the network. 107 | /// 108 | /// A task representing the asynchronous operation. 109 | public async Task ShutdownNetwork() 110 | { 111 | if (networkAdapter is IWiFiNetworkAdapter wifi) 112 | { 113 | Resolver.Log.Info("Disconnecting network..."); 114 | try 115 | { 116 | await wifi.Disconnect(true); 117 | Resolver.Log.Info("Network disconnected"); 118 | } 119 | catch (Exception ex) 120 | { 121 | Resolver.Log.Info($"Network disconnect failed: {ex.Message}"); 122 | } 123 | } 124 | } 125 | 126 | private void DownEventTimerProc(object _) 127 | { 128 | if (networkAdapter.IsConnected) 129 | { 130 | downEventTimer.Change(-1, -1); 131 | return; 132 | } 133 | 134 | NetworkDown?.Invoke(this, DownTime); 135 | downEventTimer.Change(DownEventPeriod, TimeSpan.FromMilliseconds(-1)); 136 | } 137 | 138 | private void OnNetworkDisconnected(INetworkAdapter sender, NetworkDisconnectionEventArgs args) 139 | { 140 | // Remove the UdpLogger if it's in the LogProviderCollection. 141 | if (UdpLogger != null) 142 | { 143 | Resolver.Log.RemoveProvider(UdpLogger); 144 | UdpLogger.Dispose(); 145 | UdpLogger = null; 146 | } 147 | 148 | lastDown = DateTimeOffset.UtcNow; 149 | downEventTimer.Change(DownEventPeriod, TimeSpan.FromMilliseconds(-1)); 150 | ConnectionStateChanged?.Invoke(this, false); 151 | } 152 | 153 | private async Task ReportWiFiScan(IWiFiNetworkAdapter wifi) 154 | { 155 | var networks = await wifi.Scan(); 156 | 157 | Resolver.Log.Info("WiFi Scan Results"); 158 | if (networks.Count == 0) 159 | { 160 | Resolver.Log.Info("No networks found"); 161 | } 162 | else 163 | { 164 | foreach (var network in networks) 165 | { 166 | if (string.IsNullOrEmpty(network.Ssid)) 167 | { 168 | Resolver.Log.Info($"[no ssid]: {network.SignalDbStrength}dB"); 169 | } 170 | else 171 | { 172 | Resolver.Log.Info($"{network.Ssid}: {network.SignalDbStrength}dB"); 173 | } 174 | } 175 | } 176 | } 177 | 178 | private void OnNetworkConnected(INetworkAdapter sender, NetworkConnectionEventArgs args) 179 | { 180 | Resolver.Log.Info("Add UdpLogger"); 181 | Resolver.Log.AddProvider(UdpLogger = new UdpLogger(UdpLoggingPort)); 182 | 183 | if (sender is IWiFiNetworkAdapter wifi) 184 | { 185 | _ = ReportWiFiScan(wifi); 186 | } 187 | 188 | lastDown = null; 189 | ConnectionStateChanged?.Invoke(this, true); 190 | } 191 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Controllers/NotificationController.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Peripherals.Leds; 2 | using System; 3 | 4 | namespace Meadow.Devices.Clima.Controllers; 5 | 6 | /// 7 | /// Controller for handling system notifications and warnings using an RGB LED. 8 | /// 9 | public class NotificationController 10 | { 11 | /// 12 | /// Enum representing various warning states. 13 | /// 14 | [Flags] 15 | public enum Warnings 16 | { 17 | /// 18 | /// No warnings. 19 | /// 20 | None = 0, 21 | /// 22 | /// Network is disconnected. 23 | /// 24 | NetworkDisconnected = 1 << 0, 25 | /// 26 | /// Solar load is low. 27 | /// 28 | SolarLoadLow = 1 << 1, 29 | /// 30 | /// Battery is low. 31 | /// 32 | BatteryLow = 1 << 2, 33 | } 34 | 35 | /// 36 | /// Enum representing various system statuses. 37 | /// 38 | public enum SystemStatus 39 | { 40 | /// 41 | /// System is in low power mode. 42 | /// 43 | LowPower, 44 | /// 45 | /// System is starting. 46 | /// 47 | Starting, 48 | /// 49 | /// System is searching for a network. 50 | /// 51 | SearchingForNetwork, 52 | /// 53 | /// System is connected to the network. 54 | /// 55 | NetworkConnected, 56 | /// 57 | /// System is connecting to the cloud. 58 | /// 59 | ConnectingToCloud, 60 | /// 61 | /// System is connected to the cloud. 62 | /// 63 | Connected, 64 | } 65 | 66 | private readonly IRgbPwmLed? rgbLed; 67 | private Warnings activeWarnings = Warnings.None; 68 | 69 | /// 70 | /// Initializes a new instance of the class. 71 | /// 72 | /// The RGB LED to use for notifications. 73 | public NotificationController(IRgbPwmLed? rgbLed) 74 | { 75 | this.rgbLed = rgbLed; 76 | } 77 | 78 | /// 79 | /// Sets the system status and updates the RGB LED accordingly. 80 | /// 81 | /// The system status to set. 82 | public void SetSystemStatus(SystemStatus status) 83 | { 84 | Resolver.Log.Info($"SetSystemStatus = {status}"); 85 | switch (status) 86 | { 87 | case SystemStatus.LowPower: 88 | if (rgbLed != null) 89 | { 90 | rgbLed.StopAnimation(); 91 | rgbLed.IsOn = false; 92 | } 93 | break; 94 | case SystemStatus.Starting: 95 | rgbLed?.SetColor(RgbLedColors.Red); 96 | break; 97 | case SystemStatus.SearchingForNetwork: 98 | rgbLed?.StartBlink(RgbLedColors.Red); 99 | break; 100 | case SystemStatus.NetworkConnected: 101 | rgbLed?.StopAnimation(); 102 | rgbLed?.SetColor(RgbLedColors.Magenta); 103 | break; 104 | case SystemStatus.ConnectingToCloud: 105 | rgbLed?.StartBlink(RgbLedColors.Cyan); 106 | break; 107 | case SystemStatus.Connected: 108 | rgbLed?.StopAnimation(); 109 | rgbLed?.SetColor(RgbLedColors.Green); 110 | break; 111 | } 112 | } 113 | 114 | /// 115 | /// Sets a warning and updates the RGB LED accordingly. 116 | /// 117 | /// The warning to set. 118 | public void SetWarning(Warnings warning) 119 | { 120 | activeWarnings |= warning; 121 | ReportWarnings(); 122 | } 123 | 124 | /// 125 | /// Clears a warning and updates the RGB LED accordingly. 126 | /// 127 | /// The warning to clear. 128 | public void ClearWarning(Warnings warning) 129 | { 130 | activeWarnings &= ~warning; 131 | ReportWarnings(); 132 | } 133 | 134 | /// 135 | /// Reports the current warnings by updating the RGB LED. 136 | /// 137 | private void ReportWarnings() 138 | { 139 | if (activeWarnings != Warnings.None) 140 | { 141 | rgbLed?.SetColor(RgbLedColors.Yellow); 142 | } 143 | else 144 | { 145 | rgbLed?.SetColor(RgbLedColors.Green); 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Controllers/PowerController.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Devices.Clima.Hardware; 2 | using Meadow.Devices.Clima.Models; 3 | using Meadow.Units; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace Meadow.Devices.Clima.Controllers; 8 | 9 | /// 10 | /// Controls the power management for the Clima device, including monitoring 11 | /// solar and battery voltages and issuing warnings when levels are low. 12 | /// 13 | public class PowerController 14 | { 15 | private readonly IClimaHardware clima; 16 | 17 | /// 18 | /// Event triggered when the solar voltage is below the warning level. 19 | /// 20 | public event EventHandler? SolarVoltageWarning; 21 | 22 | /// 23 | /// Event triggered when the battery voltage is below the warning level. 24 | /// 25 | public event EventHandler? BatteryVoltageWarning; 26 | 27 | private bool inBatteryWarningState = false; 28 | private bool inSolarWarningState = false; 29 | 30 | /// 31 | /// Gets or sets a value indicating whether power data should be logged. 32 | /// 33 | public bool LogPowerData { get; set; } = false; 34 | 35 | /// 36 | /// Gets the interval at which power data is updated. 37 | /// 38 | public TimeSpan UpdateInterval { get; private set; } = TimeSpan.FromSeconds(10); 39 | 40 | /// 41 | /// Gets the voltage level below which a battery warning is issued. 42 | /// 43 | public Voltage LowBatteryWarningLevel { get; } = 3.3.Volts(); 44 | 45 | /// 46 | /// Gets the voltage level below which a solar warning is issued. 47 | /// 48 | public Voltage LowSolarWarningLevel { get; } = 3.0.Volts(); 49 | 50 | /// 51 | /// Gets the voltage deadband for resetting warnings. 52 | /// 53 | public Voltage WarningDeadband { get; } = 0.25.Volts(); 54 | 55 | /// 56 | /// Initializes a new instance of the class. 57 | /// 58 | /// The Clima hardware interface. 59 | public PowerController(IClimaHardware clima) 60 | { 61 | this.clima = clima; 62 | } 63 | 64 | /// 65 | /// Remove event handler and stop updating 66 | /// 67 | public void StopUpdating() 68 | { 69 | Resolver.Log.Info($"PowerController: Stop Updating"); 70 | if (clima.SolarVoltageInput is { } solarVoltage) 71 | { 72 | solarVoltage.Updated -= SolarVoltageUpdated; 73 | solarVoltage.StopUpdating(); 74 | } 75 | 76 | if (clima.BatteryVoltageInput is { } batteryVoltage) 77 | { 78 | batteryVoltage.Updated -= BatteryVoltageUpdated; 79 | batteryVoltage.StopUpdating(); 80 | } 81 | } 82 | 83 | /// 84 | /// Add event handler and start updating 85 | /// 86 | /// 87 | public void StartUpdating(TimeSpan powerControllerUpdateInterval) 88 | { 89 | UpdateInterval = powerControllerUpdateInterval; 90 | 91 | if (clima.SolarVoltageInput is { } solarVoltage) 92 | { 93 | solarVoltage.Updated += SolarVoltageUpdated; 94 | solarVoltage.StartUpdating(UpdateInterval); 95 | } 96 | 97 | if (clima.BatteryVoltageInput is { } batteryVoltage) 98 | { 99 | batteryVoltage.Updated += BatteryVoltageUpdated; 100 | batteryVoltage.StartUpdating(UpdateInterval); 101 | } 102 | } 103 | 104 | /// 105 | /// Gets the current power data including battery and solar voltages. 106 | /// 107 | /// A task that represents the asynchronous operation. The task result contains the power data. 108 | public Task GetPowerData() 109 | { 110 | return Task.FromResult(new PowerData 111 | { 112 | BatteryVoltage = clima.BatteryVoltageInput?.Voltage ?? null, 113 | SolarVoltage = clima.SolarVoltageInput?.Voltage ?? null, 114 | }); 115 | } 116 | 117 | /// 118 | /// Puts the device to sleep for the specified duration. 119 | /// 120 | /// The duration to sleep. 121 | public void TimedSleep(TimeSpan duration) 122 | { 123 | Resolver.Log.Info("Going to sleep..."); 124 | 125 | Resolver.Device.PlatformOS.Sleep(duration); 126 | 127 | Resolver.Log.Info("PowerController completed sleep"); 128 | } 129 | 130 | private void SolarVoltageUpdated(object sender, IChangeResult e) 131 | { 132 | Resolver.Log.InfoIf(LogPowerData, $"Solar Voltage: {e.New.Volts:0.#} volts"); 133 | 134 | if (e.New < LowSolarWarningLevel) 135 | { 136 | if (!inSolarWarningState) 137 | { 138 | SolarVoltageWarning?.Invoke(this, true); 139 | 140 | inSolarWarningState = true; 141 | } 142 | } 143 | else 144 | { 145 | if (inSolarWarningState) 146 | { 147 | var resetVoltage = LowBatteryWarningLevel + WarningDeadband; 148 | 149 | if (e.New > resetVoltage) 150 | { 151 | SolarVoltageWarning?.Invoke(this, false); 152 | 153 | inSolarWarningState = false; 154 | } 155 | } 156 | } 157 | } 158 | 159 | private void BatteryVoltageUpdated(object sender, IChangeResult e) 160 | { 161 | Resolver.Log.InfoIf(LogPowerData, $"Battery Voltage: {e.New.Volts:0.#} volts"); 162 | 163 | if (e.New < LowBatteryWarningLevel) 164 | { 165 | if (!inBatteryWarningState) 166 | { 167 | BatteryVoltageWarning?.Invoke(this, true); 168 | 169 | inBatteryWarningState = true; 170 | } 171 | } 172 | else 173 | { 174 | if (inBatteryWarningState) 175 | { 176 | var resetVoltage = LowBatteryWarningLevel + WarningDeadband; 177 | 178 | if (e.New > resetVoltage) 179 | { 180 | BatteryVoltageWarning?.Invoke(this, false); 181 | 182 | inBatteryWarningState = false; 183 | } 184 | } 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Controllers/SensorController.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Devices.Clima.Hardware; 2 | using Meadow.Devices.Clima.Models; 3 | using Meadow.Units; 4 | using System; 5 | using System.Threading.Tasks; 6 | using YamlDotNet.Core.Tokens; 7 | 8 | namespace Meadow.Devices.Clima.Controllers; 9 | 10 | /// 11 | /// Controller for managing sensor data from various sensors in the Clima hardware. 12 | /// 13 | public class SensorController 14 | { 15 | private readonly IClimaHardware clima; 16 | private readonly CircularBuffer windVaneBuffer = new CircularBuffer(12); 17 | private readonly CircularBuffer windSpeedBuffer = new CircularBuffer(12); 18 | private readonly SensorData latestData; 19 | 20 | /// 21 | /// Gets or sets a value indicating whether to log sensor data. 22 | /// 23 | private bool LogSensorData { get; set; } = false; 24 | 25 | /// 26 | /// Gets the interval at which sensor data is updated. 27 | /// 28 | public TimeSpan UpdateInterval { get; private set; } = TimeSpan.FromSeconds(15); 29 | 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | /// The Clima hardware interface. 34 | public SensorController(IClimaHardware clima) 35 | { 36 | latestData = new SensorData(); 37 | this.clima = clima; 38 | 39 | if (Resolver.App.Settings.TryGetValue("Clima.OffsetToNorth", out string offsetToNorthSetting)) 40 | { 41 | if (Double.TryParse(offsetToNorthSetting, out double trueNorth)) 42 | { 43 | OffsetToNorth = new Azimuth(trueNorth); 44 | } 45 | } 46 | } 47 | 48 | /// 49 | /// Stop the update events and remove event handler. 50 | /// 51 | public void StopUpdating() 52 | { 53 | if (clima.TemperatureSensor is { } temperatureSensor) 54 | { 55 | temperatureSensor.Updated -= TemperatureUpdated; 56 | temperatureSensor.StopUpdating(); 57 | } 58 | if (clima.BarometricPressureSensor is { } pressureSensor) 59 | { 60 | pressureSensor.Updated -= PressureUpdated; 61 | // barometric pressure is slow to change 62 | pressureSensor.StopUpdating(); 63 | } 64 | 65 | if (clima.HumiditySensor is { } humiditySensor) 66 | { 67 | humiditySensor.Updated -= HumidityUpdated; 68 | // humidity is slow to change 69 | humiditySensor.StopUpdating(); 70 | } 71 | 72 | if (clima.CO2ConcentrationSensor is { } co2Sensor) 73 | { 74 | co2Sensor.Updated -= Co2Updated; 75 | // CO2 levels are slow to change 76 | co2Sensor.StopUpdating(); 77 | } 78 | 79 | if (clima.WindVane is { } windVane) 80 | { 81 | windVane.Updated -= WindvaneUpdated; 82 | windVane.StopUpdating(); 83 | } 84 | 85 | if (clima.RainGauge is { } rainGuage) 86 | { 87 | rainGuage.Updated -= RainGaugeUpdated; 88 | // rain does not change frequently 89 | rainGuage.StopUpdating(); 90 | } 91 | 92 | if (clima.Anemometer is { } anemometer) 93 | { 94 | anemometer.Updated -= AnemometerUpdated; 95 | anemometer.StopUpdating(); 96 | } 97 | } 98 | 99 | /// 100 | /// Add event handlers and start updating 101 | /// 102 | /// 103 | public void StartUpdating(TimeSpan updateInterval) 104 | { 105 | UpdateInterval = updateInterval; 106 | 107 | if (clima.TemperatureSensor is { } temperatureSensor) 108 | { 109 | temperatureSensor.Updated += TemperatureUpdated; 110 | temperatureSensor.StartUpdating(UpdateInterval); 111 | } 112 | 113 | if (clima.BarometricPressureSensor is { } pressureSensor) 114 | { 115 | pressureSensor.Updated += PressureUpdated; 116 | pressureSensor.StartUpdating(UpdateInterval); 117 | } 118 | 119 | if (clima.HumiditySensor is { } humiditySensor) 120 | { 121 | humiditySensor.Updated += HumidityUpdated; 122 | humiditySensor.StartUpdating(UpdateInterval); 123 | } 124 | 125 | if (clima.CO2ConcentrationSensor is { } co2Sensor) 126 | { 127 | co2Sensor.Updated += Co2Updated; 128 | co2Sensor.StartUpdating(UpdateInterval); 129 | } 130 | 131 | if (clima.WindVane is { } windVane) 132 | { 133 | windVane.Updated += WindvaneUpdated; 134 | windVane.StartUpdating(UpdateInterval); 135 | } 136 | 137 | if (clima.RainGauge is { } rainGuage) 138 | { 139 | rainGuage.Updated += RainGaugeUpdated; 140 | 141 | rainGuage.StartUpdating(UpdateInterval); 142 | } 143 | 144 | if (clima.Anemometer is { } anemometer) 145 | { 146 | anemometer.Updated += AnemometerUpdated; 147 | anemometer.StartUpdating(UpdateInterval); 148 | } 149 | } 150 | 151 | /// 152 | /// Gets the latest sensor data. 153 | /// 154 | /// A task that represents the asynchronous operation. The task result contains the latest sensor data. 155 | public Task GetSensorData() 156 | { 157 | lock (latestData) 158 | { 159 | var data = latestData.Copy(); 160 | 161 | latestData.Clear(); 162 | 163 | return Task.FromResult(data); 164 | } 165 | } 166 | 167 | private void TemperatureUpdated(object sender, IChangeResult e) 168 | { 169 | lock (latestData) 170 | { 171 | latestData.Temperature = e.New; 172 | } 173 | 174 | Resolver.Log.InfoIf(LogSensorData, $"Temperature: {e.New.Celsius:0.#}C"); 175 | } 176 | 177 | private void PressureUpdated(object sender, IChangeResult e) 178 | { 179 | lock (latestData) 180 | { 181 | latestData.Pressure = e.New; 182 | } 183 | 184 | Resolver.Log.InfoIf(LogSensorData, $"Pressure: {e.New.Millibar:0.#}mbar"); 185 | } 186 | 187 | private void HumidityUpdated(object sender, IChangeResult e) 188 | { 189 | lock (latestData) 190 | { 191 | latestData.Humidity = e.New; 192 | } 193 | 194 | Resolver.Log.InfoIf(LogSensorData, $"Humidity: {e.New.Percent:0.#}%"); 195 | } 196 | 197 | private void Co2Updated(object sender, IChangeResult e) 198 | { 199 | lock (latestData) 200 | { 201 | latestData.Co2Level = e.New; 202 | } 203 | Resolver.Log.InfoIf(LogSensorData, $"CO2: {e.New.PartsPerMillion:0.#}ppm"); 204 | } 205 | 206 | private void AnemometerUpdated(object sender, IChangeResult e) 207 | { 208 | Speed? mean = new Speed(0); 209 | lock (latestData) 210 | { 211 | // sanity check on windspeed to avoid reporting Infinity 212 | if (e.New.KilometersPerHour <= 250) 213 | { 214 | latestData.WindSpeed = e.New; 215 | 216 | windSpeedBuffer.Append(e.New); 217 | latestData.WindSpeedAverage = mean = windSpeedBuffer.Mean(); 218 | } 219 | } 220 | 221 | //Resolver.Log.InfoIf(true, $"Anemometer: {e.New.MetersPerSecond:0.#} m/s"); 222 | Resolver.Log.InfoIf(LogSensorData, $"Anemometer: {e.New.KilometersPerHour:0.#} km/hr, Average = {mean?.KilometersPerHour:0.#} km/hr"); 223 | 224 | } 225 | 226 | private void RainGaugeUpdated(object sender, IChangeResult e) 227 | { 228 | lock (latestData) 229 | { 230 | latestData.Rain = e.New; 231 | } 232 | 233 | Resolver.Log.InfoIf(LogSensorData, $"Rain Gauge: {e.New.Millimeters:0.#} mm"); 234 | } 235 | 236 | /// 237 | /// 0 to 360 degree offset to true north updated from app.config.yaml 238 | /// 239 | /// 240 | /// After install of Clima, point wind vane at true north, run Clima_Demo and record uncalibrated WindDirection. 241 | /// Update app.config.yaml with the uncalibrated WindDirection. 242 | /// Deploy the updated app.config.yaml and this direction will be reported as 0 Degrees. 243 | /// 244 | /// 245 | /// # Settings for Clima 246 | /// Clima: 247 | /// OffsetToNorth: 0.0 248 | /// 249 | public Azimuth OffsetToNorth { get; private set; } = new Azimuth(0.0); 250 | 251 | private void WindvaneUpdated(object sender, IChangeResult e) 252 | { 253 | Azimuth newAzimuth = e.New - OffsetToNorth; 254 | windVaneBuffer.Append(newAzimuth); 255 | 256 | Azimuth mean = windVaneBuffer.Mean(); 257 | 258 | lock (latestData) 259 | { 260 | latestData.WindDirection = mean; 261 | } 262 | 263 | Resolver.Log.InfoIf(LogSensorData, $"Wind Vane: {newAzimuth}, Average: {mean.DecimalDegrees} (uncalibrated: {e.New})"); 264 | } 265 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Hardware/ClimaHardwareBase.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Foundation.Sensors.Atmospheric; 2 | using Meadow.Foundation.Sensors.Gnss; 3 | using Meadow.Hardware; 4 | using Meadow.Logging; 5 | using Meadow.Peripherals.Leds; 6 | using Meadow.Peripherals.Sensors; 7 | using Meadow.Peripherals.Sensors.Atmospheric; 8 | using Meadow.Peripherals.Sensors.Environmental; 9 | using Meadow.Peripherals.Sensors.Weather; 10 | using System; 11 | 12 | namespace Meadow.Devices.Clima.Hardware; 13 | 14 | /// 15 | /// Contains common elements of Clima hardware 16 | /// 17 | public abstract class ClimaHardwareBase : IClimaHardware 18 | { 19 | private IConnector?[]? _connectors; 20 | private Bme688? _atmosphericSensor; 21 | private ISamplingTemperatureSensor? _temperatureSensor; 22 | private IHumiditySensor? _humiditySensor; 23 | private IBarometricPressureSensor? _barometricPressureSensor; 24 | private IGasResistanceSensor? _gasResistanceSensor; 25 | internal IWindVane? _windVane; 26 | internal IRainGauge? _rainGauge; 27 | internal IAnemometer? _anemometer; 28 | internal IRgbPwmLed? _rgbLed; 29 | internal NeoM8? _gnss; 30 | 31 | /// 32 | /// Get a reference to Meadow Logger 33 | /// 34 | protected Logger? Logger { get; } = Resolver.Log; 35 | 36 | /// 37 | public abstract II2cBus I2cBus { get; } 38 | 39 | /// 40 | public Bme688? AtmosphericSensor => GetAtmosphericSensor(); 41 | 42 | /// 43 | public ISamplingTemperatureSensor? TemperatureSensor => GetTemperatureSensor(); 44 | 45 | /// 46 | public IHumiditySensor? HumiditySensor => GetHumiditySensor(); 47 | 48 | /// 49 | public IBarometricPressureSensor? BarometricPressureSensor => GetBarometricPressureSensor(); 50 | 51 | /// 52 | public IGasResistanceSensor? GasResistanceSensor => GetGasResistanceSensor(); 53 | 54 | /// 55 | public virtual ICO2ConcentrationSensor? CO2ConcentrationSensor => throw new NotImplementedException(); 56 | 57 | /// 58 | public IWindVane? WindVane => GetWindVane(); 59 | 60 | /// 61 | public IRainGauge? RainGauge => GetRainGauge(); 62 | 63 | /// 64 | public IAnemometer? Anemometer => GetAnemometer(); 65 | 66 | /// 67 | public IObservableAnalogInputPort? SolarVoltageInput { get; protected set; } 68 | 69 | /// 70 | public IObservableAnalogInputPort? BatteryVoltageInput { get; protected set; } 71 | 72 | /// 73 | public IRgbPwmLed? RgbLed => GetRgbPwmLed(); 74 | 75 | /// 76 | public abstract string RevisionString { get; } 77 | 78 | /// 79 | /// The Neo GNSS sensor 80 | /// 81 | public NeoM8? Gnss => GetNeoM8(); 82 | 83 | /// 84 | public I2cConnector? Qwiic => (I2cConnector?)Connectors[0]; 85 | 86 | /// 87 | public IMeadowDevice ComputeModule { get; } 88 | 89 | internal ClimaHardwareBase(IMeadowDevice device) 90 | { 91 | ComputeModule = device; 92 | } 93 | 94 | internal virtual I2cConnector? CreateQwiicConnector() 95 | { 96 | return null; 97 | } 98 | 99 | /// 100 | /// Collection of connectors on the Clima board 101 | /// 102 | public IConnector?[] Connectors 103 | { 104 | get 105 | { 106 | if (_connectors == null) 107 | { 108 | _connectors = new IConnector[1]; 109 | _connectors[0] = CreateQwiicConnector(); 110 | } 111 | 112 | return _connectors; 113 | } 114 | } 115 | 116 | private Bme688? GetAtmosphericSensor() 117 | { 118 | if (_atmosphericSensor == null) 119 | { 120 | InitializeBme688(); 121 | } 122 | 123 | return _atmosphericSensor; 124 | } 125 | 126 | private ISamplingTemperatureSensor? GetTemperatureSensor() 127 | { 128 | if (_temperatureSensor == null) 129 | { 130 | InitializeBme688(); 131 | } 132 | 133 | return _temperatureSensor; 134 | } 135 | 136 | private IHumiditySensor? GetHumiditySensor() 137 | { 138 | if (_humiditySensor == null) 139 | { 140 | InitializeBme688(); 141 | } 142 | 143 | return _humiditySensor; 144 | } 145 | 146 | private IBarometricPressureSensor? GetBarometricPressureSensor() 147 | { 148 | if (_barometricPressureSensor == null) 149 | { 150 | InitializeBme688(); 151 | } 152 | 153 | return _barometricPressureSensor; 154 | } 155 | 156 | private IGasResistanceSensor? GetGasResistanceSensor() 157 | { 158 | if (_gasResistanceSensor == null) 159 | { 160 | InitializeBme688(); 161 | } 162 | 163 | return _gasResistanceSensor; 164 | } 165 | 166 | /// 167 | /// Get the Wind Vane on the Clima board 168 | /// 169 | protected abstract IWindVane? GetWindVane(); 170 | 171 | /// 172 | /// Get the Rain Gauge on the Clima board 173 | /// 174 | protected abstract IRainGauge? GetRainGauge(); 175 | 176 | /// 177 | /// Get the Anemometer on the Clima board 178 | /// 179 | protected abstract IAnemometer? GetAnemometer(); 180 | 181 | /// 182 | /// Get the RGB LED on the Clima board 183 | /// 184 | protected abstract IRgbPwmLed? GetRgbPwmLed(); 185 | 186 | /// 187 | /// Get the Neo GNSS sensor 188 | /// 189 | protected abstract NeoM8? GetNeoM8(); 190 | 191 | private void InitializeBme688() 192 | { 193 | try 194 | { 195 | Logger?.Trace("Instantiating atmospheric sensor"); 196 | var bme = new Bme688(I2cBus, (byte)Bme68x.Addresses.Address_0x76); 197 | _atmosphericSensor = bme; 198 | _temperatureSensor = bme; 199 | _humiditySensor = bme; 200 | _barometricPressureSensor = bme; 201 | _gasResistanceSensor = bme; 202 | Resolver.SensorService.RegisterSensor(_atmosphericSensor); 203 | Logger?.Trace("Atmospheric sensor up"); 204 | } 205 | catch (Exception ex) 206 | { 207 | Logger?.Error($"Unable to create the BME688 atmospheric sensor: {ex.Message}"); 208 | } 209 | } 210 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Hardware/ClimaHardwareV2.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Foundation.Leds; 2 | using Meadow.Foundation.Sensors.Gnss; 3 | using Meadow.Foundation.Sensors.Weather; 4 | using Meadow.Hardware; 5 | using Meadow.Peripherals.Leds; 6 | using Meadow.Peripherals.Sensors.Weather; 7 | using System; 8 | 9 | namespace Meadow.Devices.Clima.Hardware; 10 | 11 | /// 12 | /// Represents the Clima v2.x hardware 13 | /// 14 | public class ClimaHardwareV2 : ClimaHardwareBase 15 | { 16 | private readonly IF7FeatherMeadowDevice _device; 17 | 18 | /// 19 | public sealed override II2cBus I2cBus { get; } 20 | 21 | /// 22 | public override string RevisionString => "v2.x"; 23 | 24 | /// 25 | /// Create a new ClimaHardwareV2 object 26 | /// 27 | /// The meadow device 28 | /// The I2C bus 29 | public ClimaHardwareV2(IF7FeatherMeadowDevice device, II2cBus i2cBus) 30 | : base(device) 31 | { 32 | _device = device; 33 | 34 | I2cBus = i2cBus; 35 | 36 | // See hack in Meadow.Core\source\implementations\f7\Meadow.F7\Devices\DeviceChannelManager.cs 37 | // Must initialise any PWM based I/O first 38 | GetRgbPwmLed(); 39 | 40 | try 41 | { 42 | Logger?.Trace("Instantiating Solar Voltage Input"); 43 | SolarVoltageInput = device.Pins.A02.CreateAnalogInputPort(5); 44 | Logger?.Trace("Solar Voltage Input up"); 45 | } 46 | catch (Exception ex) 47 | { 48 | Logger?.Error($"Unable to create the Solar Voltage Input: {ex.Message}"); 49 | } 50 | } 51 | 52 | /// 53 | protected override IRgbPwmLed? GetRgbPwmLed() 54 | { 55 | if (_rgbLed == null) 56 | { 57 | try 58 | { 59 | Logger?.Trace("Instantiating RGB LED"); 60 | _rgbLed = new RgbPwmLed( 61 | redPwmPin: _device.Pins.OnboardLedRed, 62 | greenPwmPin: _device.Pins.OnboardLedGreen, 63 | bluePwmPin: _device.Pins.OnboardLedBlue, 64 | CommonType.CommonAnode); 65 | Logger?.Trace("RGB LED up"); 66 | } 67 | catch (Exception ex) 68 | { 69 | Logger?.Error($"Unable to create the RGB LED: {ex.Message}"); 70 | } 71 | } 72 | 73 | return _rgbLed; 74 | } 75 | 76 | /// 77 | protected override NeoM8? GetNeoM8() 78 | { 79 | if (_gnss == null) 80 | { 81 | try 82 | { 83 | Logger?.Trace("Instantiating GNSS"); 84 | _gnss = new NeoM8(_device, _device.PlatformOS.GetSerialPortName("COM4")!, null, null); 85 | Logger?.Trace("GNSS initialized"); 86 | } 87 | catch (Exception e) 88 | { 89 | Logger?.Error($"Err initializing GNSS: {e.Message}"); 90 | } 91 | } 92 | return _gnss; 93 | } 94 | 95 | /// 96 | protected override IWindVane? GetWindVane() 97 | { 98 | if (_windVane == null) 99 | { 100 | try 101 | { 102 | Logger?.Trace("Instantiating Wind Vane"); 103 | _windVane = new WindVane(_device.Pins.A00); 104 | Resolver.SensorService.RegisterSensor(_windVane); 105 | Logger?.Trace("Wind Vane up"); 106 | } 107 | catch (Exception ex) 108 | { 109 | Logger?.Error($"Unable to create the Wind Vane: {ex.Message}"); 110 | } 111 | } 112 | return _windVane; 113 | } 114 | 115 | /// 116 | protected override IRainGauge? GetRainGauge() 117 | { 118 | if (_rainGauge == null) 119 | { 120 | try 121 | { 122 | Logger?.Trace("Instantiating Rain Gauge"); 123 | _rainGauge = new SwitchingRainGauge(_device.Pins.D11); 124 | Resolver.SensorService.RegisterSensor(_rainGauge); 125 | Logger?.Trace("Rain Gauge up"); 126 | } 127 | catch (Exception ex) 128 | { 129 | Logger?.Error($"Unable to create the Rain Gauge: {ex.Message}"); 130 | } 131 | } 132 | return _rainGauge; 133 | } 134 | 135 | /// 136 | protected override IAnemometer? GetAnemometer() 137 | { 138 | if (_anemometer == null) 139 | { 140 | try 141 | { 142 | Logger?.Trace("Instantiating Anemometer"); 143 | _anemometer = new SwitchingAnemometer(_device.Pins.A01); 144 | Resolver.SensorService.RegisterSensor(_anemometer); 145 | Logger?.Trace("Anemometer up"); 146 | } 147 | catch (Exception ex) 148 | { 149 | Logger?.Error($"Unable to create the Anemometer: {ex.Message}"); 150 | } 151 | } 152 | return _anemometer; 153 | } 154 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Hardware/ClimaHardwareV3.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Foundation.ICs.IOExpanders; 2 | using Meadow.Foundation.Leds; 3 | using Meadow.Foundation.Sensors.Environmental; 4 | using Meadow.Foundation.Sensors.Gnss; 5 | using Meadow.Foundation.Sensors.Weather; 6 | using Meadow.Hardware; 7 | using Meadow.Peripherals.Leds; 8 | using Meadow.Peripherals.Sensors.Environmental; 9 | using Meadow.Peripherals.Sensors.Weather; 10 | using System; 11 | 12 | namespace Meadow.Devices.Clima.Hardware; 13 | 14 | /// 15 | /// Represents the Clima v3.x hardware 16 | /// 17 | public class ClimaHardwareV3 : ClimaHardwareBase 18 | { 19 | /// 20 | /// The Meadow CCM device 21 | /// 22 | protected readonly IF7CoreComputeMeadowDevice _device; 23 | 24 | private Scd40? _environmentalSensor; 25 | private ICO2ConcentrationSensor? _co2ConcentrationSensor; 26 | 27 | /// 28 | public sealed override II2cBus I2cBus { get; } 29 | 30 | /// 31 | /// The SCD40 environmental sensor on the Clima board 32 | /// 33 | public Scd40? EnvironmentalSensor => GetEnvironmentalSensor(); 34 | 35 | /// 36 | public override ICO2ConcentrationSensor? CO2ConcentrationSensor => GetCO2ConcentrationSensor(); 37 | 38 | /// 39 | /// The MCP23008 IO expander that contains the Clima hardware version 40 | /// 41 | public Mcp23008 McpVersion { get; protected set; } 42 | 43 | /// 44 | public override string RevisionString => "v3.x"; 45 | 46 | /// 47 | /// Analog inputs to measure Solar voltage has Resistor Divider with R1 = 1000 Ohm, R2 = 680 Ohm 48 | /// Measured analogue voltage needs to be scaled to RETURN actual input voltage 49 | /// Input Voltage = AIN / Resistor Divider 50 | /// 51 | protected const double SolarVoltageResistorDivider = 680.0 / (1000.0 + 680.0); 52 | 53 | /// 54 | /// Analog inputs to measure Solar voltage has Resistor Divider with R1 = 1000 Ohm, R2 = 680 Ohm 55 | /// Measured analogue voltage needs to be scaled to RETURN actual input voltage 56 | /// Input Voltage = AIN / Resistor Divider 57 | /// 58 | protected const double BatteryVoltageResistorDivider = 2000.0 / (1000.0 + 2000.0); 59 | 60 | /// 61 | /// Create a new ClimaHardwareV3 object 62 | /// 63 | /// The meadow device 64 | /// The I2C bus 65 | /// The Mcp23008 used to read version information 66 | public ClimaHardwareV3(IF7CoreComputeMeadowDevice device, II2cBus i2cBus, Mcp23008 mcpVersion) 67 | : base(device) 68 | { 69 | McpVersion = mcpVersion; 70 | 71 | _device = device; 72 | 73 | I2cBus = i2cBus; 74 | 75 | // See hack in Meadow.Core\source\implementations\f7\Meadow.F7\Devices\DeviceChannelManager.cs 76 | // Must initialise any PWM based I/O first 77 | GetRgbPwmLed(); 78 | 79 | try 80 | { 81 | Logger?.Trace("Instantiating Solar Voltage Input"); 82 | SolarVoltageInput = device.Pins.A02.CreateAnalogInputPort(5, 83 | AnalogInputPort.DefaultSampleInterval, 84 | AnalogInputPort.DefaultReferenceVoltage / SolarVoltageResistorDivider); 85 | Logger?.Trace("Solar Voltage Input up"); 86 | } 87 | catch (Exception ex) 88 | { 89 | Logger?.Error($"Unable to create the Solar Voltage Input: {ex.Message}"); 90 | } 91 | 92 | try 93 | { 94 | Logger?.Trace("Instantiating Battery Voltage Input"); 95 | BatteryVoltageInput = device.Pins.A04.CreateAnalogInputPort(5, 96 | AnalogInputPort.DefaultSampleInterval, 97 | AnalogInputPort.DefaultReferenceVoltage / BatteryVoltageResistorDivider); 98 | 99 | Logger?.Trace("Battery Voltage Input up"); 100 | } 101 | catch (Exception ex) 102 | { 103 | Logger?.Error($"Unable to create the Battery Voltage Input: {ex.Message}"); 104 | } 105 | } 106 | 107 | private Scd40? GetEnvironmentalSensor() 108 | { 109 | if (_environmentalSensor == null) 110 | { 111 | InitializeScd40(); 112 | } 113 | 114 | return _environmentalSensor; 115 | } 116 | 117 | private ICO2ConcentrationSensor? GetCO2ConcentrationSensor() 118 | { 119 | if (_co2ConcentrationSensor == null) 120 | { 121 | InitializeScd40(); 122 | } 123 | 124 | return _co2ConcentrationSensor; 125 | } 126 | 127 | private void InitializeScd40() 128 | { 129 | try 130 | { 131 | Logger?.Trace("Instantiating environmental sensor"); 132 | var scd = new Scd40(I2cBus, (byte)Scd40.Addresses.Default); 133 | _co2ConcentrationSensor = scd; 134 | _environmentalSensor = scd; 135 | Resolver.SensorService.RegisterSensor(scd); 136 | Logger?.Trace("Environmental sensor up"); 137 | } 138 | catch (Exception ex) 139 | { 140 | Logger?.Error($"Unable to create the SCD40 Environmental Sensor: {ex.Message}"); 141 | } 142 | } 143 | 144 | /// 145 | protected override IRgbPwmLed? GetRgbPwmLed() 146 | { 147 | if (_rgbLed == null) 148 | { 149 | try 150 | { 151 | Logger?.Trace("Instantiating RGB LED"); 152 | _rgbLed = new RgbPwmLed( 153 | redPwmPin: _device.Pins.D09, 154 | greenPwmPin: _device.Pins.D10, 155 | bluePwmPin: _device.Pins.D11, 156 | CommonType.CommonAnode); 157 | Logger?.Trace("RGB LED up"); 158 | } 159 | catch (Exception ex) 160 | { 161 | Logger?.Error($"Unable to create the RGB LED: {ex.Message}"); 162 | } 163 | } 164 | 165 | return _rgbLed; 166 | } 167 | 168 | /// 169 | protected override NeoM8? GetNeoM8() 170 | { 171 | if (_gnss == null) 172 | { 173 | try 174 | { 175 | Logger?.Trace("Instantiating GNSS"); 176 | _gnss = new NeoM8(_device, _device.PlatformOS.GetSerialPortName("COM4")!, _device.Pins.D05, _device.Pins.A03); 177 | Logger?.Trace("GNSS initialized"); 178 | } 179 | catch (Exception e) 180 | { 181 | Logger?.Error($"Err initializing GNSS: {e.Message}"); 182 | } 183 | } 184 | return _gnss; 185 | } 186 | 187 | /// 188 | protected override IWindVane? GetWindVane() 189 | { 190 | if (_windVane == null) 191 | { 192 | try 193 | { 194 | Logger?.Trace("Instantiating Wind Vane"); 195 | _windVane = new WindVane(_device.Pins.A00); 196 | Resolver.SensorService.RegisterSensor(_windVane); 197 | Logger?.Trace("Wind Vane up"); 198 | } 199 | catch (Exception ex) 200 | { 201 | Logger?.Error($"Unable to create the Wind Vane: {ex.Message}"); 202 | } 203 | } 204 | return _windVane; 205 | } 206 | 207 | /// 208 | protected override IRainGauge? GetRainGauge() 209 | { 210 | if (_rainGauge == null) 211 | { 212 | try 213 | { 214 | Logger?.Trace("Instantiating Rain Gauge"); 215 | _rainGauge = new SwitchingRainGauge(_device.Pins.D16); 216 | Resolver.SensorService.RegisterSensor(_rainGauge); 217 | Logger?.Trace("Rain Gauge up"); 218 | } 219 | catch (Exception ex) 220 | { 221 | Logger?.Error($"Unable to create the Rain Gauge: {ex.Message}"); 222 | } 223 | } 224 | return _rainGauge; 225 | } 226 | 227 | /// 228 | protected override IAnemometer? GetAnemometer() 229 | { 230 | if (_anemometer == null) 231 | { 232 | try 233 | { 234 | Logger?.Trace("Instantiating Anemometer"); 235 | _anemometer = new SwitchingAnemometer(_device.Pins.A01); 236 | Resolver.SensorService.RegisterSensor(_anemometer); 237 | Logger?.Trace("Anemometer up"); 238 | } 239 | catch (Exception ex) 240 | { 241 | Logger?.Error($"Unable to create the Anemometer: {ex.Message}"); 242 | } 243 | } 244 | return _anemometer; 245 | } 246 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Hardware/ClimaHardwareV4.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Foundation.ICs.IOExpanders; 2 | using Meadow.Hardware; 3 | 4 | namespace Meadow.Devices.Clima.Hardware; 5 | 6 | /// 7 | /// Represents the Clima v4.x hardware 8 | /// 9 | public class ClimaHardwareV4 : ClimaHardwareV3 10 | { 11 | /// 12 | public override string RevisionString => "v4.x"; 13 | 14 | /// 15 | /// Create a new ClimaHardwareV4 object 16 | /// 17 | /// The meadow device 18 | /// The I2C bus 19 | /// The Mcp23008 used to read version information 20 | public ClimaHardwareV4(IF7CoreComputeMeadowDevice device, II2cBus i2cBus, Mcp23008 mcpVersion) 21 | : base(device, i2cBus, mcpVersion) 22 | { } 23 | 24 | internal override I2cConnector? CreateQwiicConnector() 25 | { 26 | Logger?.Trace("Creating Qwiic I2C connector"); 27 | 28 | return new I2cConnector( 29 | nameof(Qwiic), 30 | new PinMapping 31 | { 32 | new PinMapping.PinAlias(I2cConnector.PinNames.SCL, _device.Pins.I2C1_SCL), 33 | new PinMapping.PinAlias(I2cConnector.PinNames.SDA, _device.Pins.I2C1_SDA), 34 | }, 35 | new I2cBusMapping(_device, 1)); 36 | } 37 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Hardware/IClimaHardware.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Foundation.Sensors.Gnss; 2 | using Meadow.Hardware; 3 | using Meadow.Peripherals.Leds; 4 | using Meadow.Peripherals.Sensors; 5 | using Meadow.Peripherals.Sensors.Atmospheric; 6 | using Meadow.Peripherals.Sensors.Environmental; 7 | using Meadow.Peripherals.Sensors.Weather; 8 | 9 | namespace Meadow.Devices.Clima.Hardware; 10 | 11 | /// 12 | /// Contract for the Clima hardware definitions 13 | /// 14 | public interface IClimaHardware : IMeadowAppEmbeddedHardware 15 | { 16 | /// 17 | /// The I2C Bus 18 | /// 19 | II2cBus I2cBus { get; } 20 | 21 | /// 22 | /// Gets the ITemperatureSensor on the Clima board 23 | /// 24 | ISamplingTemperatureSensor? TemperatureSensor { get; } 25 | 26 | /// 27 | /// Gets the IHumiditySensor on the Clima board 28 | /// 29 | IHumiditySensor? HumiditySensor { get; } 30 | 31 | /// 32 | /// Gets the IBarometricPressureSensor on the Clima board 33 | /// 34 | IBarometricPressureSensor? BarometricPressureSensor { get; } 35 | 36 | /// 37 | /// Gets the IGasResistanceSensor on the Clima board 38 | /// 39 | IGasResistanceSensor? GasResistanceSensor { get; } 40 | 41 | /// 42 | /// Gets the ICO2ConcentrationSensor on the Clima board 43 | /// 44 | ICO2ConcentrationSensor? CO2ConcentrationSensor { get; } 45 | 46 | /// 47 | /// The Neo GNSS sensor 48 | /// 49 | NeoM8? Gnss { get; } 50 | 51 | /// 52 | /// The Wind Vane on the Clima board 53 | /// 54 | IWindVane? WindVane { get; } 55 | 56 | /// 57 | /// The Switching Rain Gauge on the Clima board 58 | /// 59 | IRainGauge? RainGauge { get; } 60 | 61 | /// 62 | /// The Switching Anemometer on the Clima board 63 | /// 64 | IAnemometer? Anemometer { get; } 65 | 66 | /// 67 | /// The Solar Voltage Input on the Clima board 68 | /// 69 | IObservableAnalogInputPort? SolarVoltageInput { get; } 70 | 71 | /// 72 | /// The Battery Voltage Input on the Clima board 73 | /// 74 | IObservableAnalogInputPort? BatteryVoltageInput { get; } 75 | 76 | /// 77 | /// The RGB PWM LED on the Clima board 78 | /// 79 | IRgbPwmLed? RgbLed { get; } 80 | 81 | /// 82 | /// Gets the Qwiic connector on the Clima board. 83 | /// 84 | I2cConnector? Qwiic { get; } 85 | 86 | /// 87 | /// The hardware revision string for the Clima board 88 | /// 89 | string RevisionString { get; } 90 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/MainController.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Devices.Clima.Controllers; 2 | using Meadow.Devices.Clima.Hardware; 3 | using Meadow.Hardware; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using static Meadow.Devices.Clima.Controllers.NotificationController; 9 | 10 | namespace Meadow.Devices; 11 | 12 | /// 13 | /// Main controller for Clima. 14 | /// 15 | public class MainController 16 | { 17 | private NotificationController notificationController; 18 | private SensorController sensorController; 19 | private PowerController powerController; 20 | private LocationController locationController; 21 | private NetworkController? networkController; 22 | private CloudController cloudController; 23 | private int tick; 24 | private const int SensorReadPeriodSeconds = 10; 25 | private const int PublicationPeriodMinutes = 1; 26 | private bool lowPowerMode = false; 27 | private Timer sleepSimulationTimer; 28 | 29 | /// 30 | /// Gets the telemetry publication period. 31 | /// 32 | public TimeSpan TelemetryPublicationPeriod { get; } = TimeSpan.FromMinutes(1); 33 | 34 | /// 35 | /// Initializes the MainController with the clima hardware and network adapter. 36 | /// 37 | /// The Clima hardware to use. 38 | /// The network adapter to use, or null if no network adapter is available. 39 | public Task Initialize(IClimaHardware hardware, INetworkAdapter? networkAdapter) 40 | { 41 | Resolver.Log.Info("Initialize hardware..."); 42 | 43 | notificationController = new NotificationController(hardware.RgbLed); 44 | Resolver.Services.Add(notificationController); 45 | 46 | notificationController.SetSystemStatus(NotificationController.SystemStatus.Starting); 47 | 48 | cloudController = new CloudController(); 49 | 50 | Resolver.Log.Info($"Running on Clima Hardware {hardware.RevisionString}"); 51 | 52 | sensorController = new SensorController(hardware); 53 | sensorController.StartUpdating(sensorController.UpdateInterval); // TODO: consider calling after network, time etc are ready? 54 | 55 | powerController = new PowerController(hardware); 56 | powerController.StartUpdating(powerController.UpdateInterval); // TODO: consider calling after network, time 57 | 58 | powerController.SolarVoltageWarning += OnSolarVoltageWarning; 59 | powerController.BatteryVoltageWarning += OnBatteryVoltageWarning; 60 | 61 | 62 | 63 | 64 | 65 | if (networkAdapter == null) 66 | { 67 | Resolver.Log.Error("No network adapter found!"); 68 | } 69 | else 70 | { 71 | networkController = new NetworkController(networkAdapter); 72 | networkController.ConnectionStateChanged += OnNetworkConnectionStateChanged; 73 | networkController.NetworkDown += OnNetworkStillDown; 74 | 75 | if (!networkController.IsConnected) 76 | { 77 | notificationController.SetSystemStatus(NotificationController.SystemStatus.SearchingForNetwork); 78 | Resolver.Log.Info("Network is down"); 79 | } 80 | else 81 | { 82 | Resolver.Log.Info("Network is connected."); 83 | notificationController.SetSystemStatus(NotificationController.SystemStatus.NetworkConnected); 84 | if (Resolver.MeadowCloudService.ConnectionState == CloudConnectionState.Connecting) 85 | { 86 | notificationController.SetSystemStatus(NotificationController.SystemStatus.ConnectingToCloud); 87 | } 88 | } 89 | } 90 | 91 | Resolver.MeadowCloudService.ConnectionStateChanged += OnMeadowCloudServiceConnectionStateChanged; 92 | cloudController.LogAppStartup(hardware.RevisionString); 93 | 94 | locationController = new LocationController(hardware); 95 | locationController.PositionReceived += OnPositionReceived; 96 | 97 | Resolver.Device.PlatformOS.AfterWake += PlatformOS_AfterWake; 98 | 99 | if (!lowPowerMode) 100 | { 101 | sleepSimulationTimer = new Timer((_) => PlatformOS_AfterWake(this, WakeSource.Unknown), null, -1, -1); 102 | } 103 | 104 | _ = SystemPreSleepStateProc(); 105 | 106 | Resolver.Log.Info($"Initialize hardware Task.CompletedTask"); 107 | 108 | return Task.CompletedTask; 109 | } 110 | 111 | /// 112 | /// Start sensor and power controller updates 113 | /// 114 | public void StartUpdating() 115 | { 116 | Resolver.Log.Info($"Start Updating"); 117 | sensorController.StartUpdating(sensorController.UpdateInterval); 118 | powerController.StartUpdating(powerController.UpdateInterval); 119 | locationController.StartUpdating(); 120 | } 121 | 122 | /// 123 | /// Stop sensor and power controller updates 124 | /// 125 | public void StopUpdating() 126 | { 127 | Resolver.Log.Info($"Stop Updating"); 128 | sensorController.StopUpdating(); 129 | powerController.StopUpdating(); 130 | locationController.StopUpdating(); 131 | sleepSimulationTimer.Change(-1, -1); // stop timer 132 | } 133 | 134 | private void OnPositionReceived(object sender, Peripherals.Sensors.Location.Gnss.GnssPositionInfo e) 135 | { 136 | if (e.Position != null) 137 | { 138 | // crop to 2 decimal places (~1km accuracy) for privacy 139 | var lat = Math.Round(e.Position.Latitude, 2); 140 | var lon = Math.Round(e.Position.Longitude, 2); 141 | 142 | cloudController.LogDeviceInfo(Resolver.Device.Information.DeviceName, lat, lon); 143 | } 144 | } 145 | 146 | private void PlatformOS_AfterWake(object sender, WakeSource e) 147 | { 148 | Resolver.Log.Info("PlatformOS_AfterWake"); 149 | SystemPostWakeStateProc(); 150 | } 151 | 152 | private async Task SystemPreSleepStateProc() 153 | { 154 | await CollectTelemetry(); 155 | 156 | // connect to cloud 157 | if (networkController != null) 158 | { 159 | notificationController.SetSystemStatus(SystemStatus.SearchingForNetwork); 160 | var connected = await networkController.ConnectToCloud(); 161 | if (connected) 162 | { 163 | if (cloudController != null) 164 | { 165 | await cloudController.WaitForDataToSend(); 166 | } 167 | 168 | if (lowPowerMode) 169 | { 170 | await networkController.ShutdownNetwork(); 171 | } 172 | } 173 | } 174 | 175 | notificationController.SetSystemStatus(SystemStatus.LowPower); 176 | if (lowPowerMode) 177 | { 178 | powerController.TimedSleep(TimeSpan.FromSeconds(SensorReadPeriodSeconds)); 179 | } 180 | else 181 | { 182 | Resolver.Log.Info("Simulating sleep"); 183 | sleepSimulationTimer.Change(TimeSpan.FromSeconds(SensorReadPeriodSeconds), TimeSpan.FromMilliseconds(-1)); 184 | } 185 | } 186 | 187 | private void SystemPostWakeStateProc() 188 | { 189 | // collect data 190 | 191 | if (++tick % PublicationPeriodMinutes * 60 / SensorReadPeriodSeconds == 0) 192 | { 193 | _ = SystemPreSleepStateProc(); 194 | } 195 | else 196 | { 197 | if (lowPowerMode) 198 | { 199 | powerController.TimedSleep(TimeSpan.FromSeconds(SensorReadPeriodSeconds)); 200 | } 201 | else 202 | { 203 | sleepSimulationTimer.Change(TimeSpan.FromSeconds(SensorReadPeriodSeconds), TimeSpan.FromMilliseconds(-1)); 204 | } 205 | } 206 | } 207 | 208 | private void OnMeadowCloudServiceConnectionStateChanged(object sender, CloudConnectionState e) 209 | { 210 | switch (e) 211 | { 212 | case CloudConnectionState.Connected: 213 | notificationController.SetSystemStatus(NotificationController.SystemStatus.Connected); 214 | 215 | locationController.StartUpdating(); 216 | break; 217 | 218 | case CloudConnectionState.Disconnected: 219 | case CloudConnectionState.Unknown: 220 | locationController.StopUpdating(); 221 | break; 222 | 223 | default: 224 | notificationController.SetSystemStatus(NotificationController.SystemStatus.ConnectingToCloud); 225 | break; 226 | } 227 | } 228 | 229 | private async Task CollectTelemetry() 230 | { 231 | // collect telemetry every tick 232 | Resolver.Log.Info($"Collecting telemetry"); 233 | 234 | try 235 | { 236 | // publish telemetry to the cloud every N ticks 237 | cloudController.LogTelemetry( 238 | await sensorController.GetSensorData(), 239 | await powerController.GetPowerData()); 240 | } 241 | catch (Exception ex) 242 | { 243 | Resolver.Log.Warn($"Failed to log telemetry: {ex.Message}"); 244 | } 245 | } 246 | 247 | private void OnNetworkStillDown(object sender, System.TimeSpan e) 248 | { 249 | Resolver.Log.Info($"Network has been down for {e.TotalSeconds:N0} seconds"); 250 | 251 | // TODO: after some period, should we force-restart the device? 252 | if (e.TotalMinutes > 5) 253 | { 254 | Resolver.Log.Info($"Network Connection timeout. Resetting the device."); 255 | Resolver.Device.PlatformOS.Reset(); 256 | } 257 | } 258 | 259 | private void OnNetworkConnectionStateChanged(object sender, bool e) 260 | { 261 | if (e) 262 | { 263 | Resolver.Log.Info($"Network connected"); 264 | notificationController.ClearWarning(NotificationController.Warnings.NetworkDisconnected); 265 | notificationController.SetSystemStatus(NotificationController.SystemStatus.NetworkConnected); 266 | } 267 | else 268 | { 269 | Resolver.Log.Info($"Network disconnected"); 270 | notificationController.SetWarning(NotificationController.Warnings.NetworkDisconnected); 271 | } 272 | } 273 | 274 | private void OnBatteryVoltageWarning(object sender, bool e) 275 | { 276 | if (e) 277 | { 278 | var message = $"Battery voltage dropped below {powerController.LowBatteryWarningLevel.Volts:N1}"; 279 | Resolver.Log.Warn(message); 280 | 281 | //notificationController.SetWarning(NotificationController.Warnings.BatteryLow); 282 | cloudController.LogWarning(message); 283 | } 284 | else 285 | { 286 | var message = $"Battery voltage is back above minimum"; 287 | Resolver.Log.Info(message); 288 | 289 | notificationController.ClearWarning(NotificationController.Warnings.BatteryLow); 290 | cloudController.LogMessage(message); 291 | } 292 | } 293 | 294 | private void OnSolarVoltageWarning(object sender, bool e) 295 | { 296 | if (e) 297 | { 298 | var message = $"Solar voltage dropped below {powerController.LowSolarWarningLevel.Volts:N1}"; 299 | Resolver.Log.Warn(message); 300 | 301 | //notificationController.SetWarning(NotificationController.Warnings.SolarLoadLow); 302 | cloudController.LogWarning(message); 303 | } 304 | else 305 | { 306 | var message = $"Solar voltage is back above minimum"; 307 | Resolver.Log.Info(message); 308 | 309 | notificationController.ClearWarning(NotificationController.Warnings.SolarLoadLow); 310 | cloudController.LogMessage(message); 311 | } 312 | } 313 | 314 | /// 315 | /// Logs the application startup after a crash. 316 | /// 317 | /// Crash report data 318 | public void LogAppStartupAfterCrash(IEnumerable crashReports) 319 | { 320 | // the cloud service's health reporter will log this for us automatically, so no need to manually do so 321 | Resolver.Log.Warn("Boot after crash!"); 322 | 323 | foreach (var report in crashReports) 324 | { 325 | Resolver.Log.Info(report); 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /Source/Meadow.Clima/Meadow.Clima.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Wilderness Labs, Inc 4 | Wilderness Labs, Inc 5 | true 6 | Apache-2.0 7 | 12 8 | enable 9 | icon.png 10 | netstandard2.1 11 | Library 12 | Clima 13 | https://github.com/WildernessLabs/Clima 14 | Meadow.Clima 15 | https://github.com/WildernessLabs/Clima 16 | Meadow.Clima,Meadow,Clima,atmospheric,environmental,sensors,accelerator 17 | true 18 | Library for the Meadow Clima IoT accelerator 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Source/Meadow.Clima/Models/PowerData.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Units; 2 | using System.Collections.Generic; 3 | 4 | namespace Meadow.Devices.Clima.Models; 5 | 6 | /// 7 | /// Represents power data including solar voltage and battery voltage. 8 | /// 9 | public record PowerData 10 | { 11 | /// 12 | /// Gets or sets the solar voltage. 13 | /// 14 | public Voltage? SolarVoltage { get; set; } 15 | 16 | /// 17 | /// Gets or sets the battery voltage. 18 | /// 19 | public Voltage? BatteryVoltage { get; set; } 20 | 21 | /// 22 | /// Converts the power data to a telemetry dictionary. 23 | /// 24 | /// The telemetry dictionary. 25 | public Dictionary AsTelemetryDictionary() 26 | { 27 | var d = new Dictionary(); 28 | if (SolarVoltage != null) 29 | { 30 | d.Add(nameof(SolarVoltage), SolarVoltage.Value.Volts); 31 | } 32 | if (BatteryVoltage != null) 33 | { 34 | d.Add(nameof(BatteryVoltage), BatteryVoltage.Value.Volts); 35 | } 36 | 37 | return d; 38 | } 39 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/Models/SensorData.cs: -------------------------------------------------------------------------------- 1 | using Meadow.Units; 2 | using System.Collections.Generic; 3 | 4 | namespace Meadow.Devices.Clima.Models; 5 | 6 | /// 7 | /// Represents the clima sensor data 8 | /// 9 | public class SensorData 10 | { 11 | /// 12 | /// Gets or sets the temperature. 13 | /// 14 | public Temperature? Temperature { get; set; } 15 | 16 | /// 17 | /// Gets or sets the pressure. 18 | /// 19 | public Pressure? Pressure { get; set; } 20 | 21 | /// 22 | /// Gets or sets the relative humidity. 23 | /// 24 | public RelativeHumidity? Humidity { get; set; } 25 | 26 | /// 27 | /// Gets or sets the CO2 level. 28 | /// 29 | public Concentration? Co2Level { get; set; } 30 | 31 | /// 32 | /// Gets or sets the wind speed. 33 | /// 34 | public Speed? WindSpeed { get; set; } 35 | 36 | /// 37 | /// Gets or sets the average wind speed. 38 | /// 39 | public Speed? WindSpeedAverage { get; set; } 40 | 41 | /// 42 | /// Gets or sets the wind direction. 43 | /// 44 | public Azimuth? WindDirection { get; set; } 45 | 46 | /// 47 | /// Gets or sets the rain length. 48 | /// 49 | public Length? Rain { get; set; } 50 | 51 | /// 52 | /// Gets or sets the illuminance. 53 | /// 54 | public Illuminance? Light { get; set; } 55 | 56 | /// 57 | /// Clears all the sensor data. 58 | /// 59 | public void Clear() 60 | { 61 | Temperature = null; 62 | Pressure = null; 63 | Humidity = null; 64 | Co2Level = null; 65 | WindSpeed = null; 66 | WindSpeedAverage = null; 67 | WindDirection = null; 68 | Rain = null; 69 | Light = null; 70 | } 71 | 72 | /// 73 | /// Creates a copy of the SensorData object. 74 | /// 75 | public SensorData Copy() 76 | { 77 | return new SensorData 78 | { 79 | Temperature = Temperature, 80 | Pressure = Pressure, 81 | Humidity = Humidity, 82 | Co2Level = Co2Level, 83 | WindSpeed = WindSpeed, 84 | WindSpeedAverage = WindSpeedAverage, 85 | WindDirection = WindDirection, 86 | Rain = Rain, 87 | Light = Light, 88 | }; 89 | } 90 | 91 | 92 | /// 93 | /// Converts the SensorData object to a dictionary suitable for telemetry. 94 | /// 95 | /// A dictionary containing the telemetry data. 96 | public Dictionary AsTelemetryDictionary() 97 | { 98 | var d = new Dictionary(); 99 | if (Temperature != null) 100 | { 101 | d.Add(nameof(Temperature), Temperature.Value.Celsius); 102 | } 103 | if (Pressure != null) 104 | { 105 | d.Add(nameof(Pressure), Pressure.Value.Bar); 106 | } 107 | if (Humidity != null) 108 | { 109 | d.Add(nameof(Humidity), Humidity.Value.Percent); 110 | } 111 | if (Co2Level != null) 112 | { 113 | d.Add(nameof(Co2Level), Co2Level.Value.PartsPerMillion); 114 | } 115 | if (WindSpeed != null) 116 | { 117 | d.Add(nameof(WindSpeed), WindSpeed.Value.KilometersPerHour); 118 | } 119 | if (WindSpeedAverage != null) 120 | { 121 | d.Add(nameof(WindSpeedAverage), WindSpeedAverage.Value.KilometersPerHour); 122 | } 123 | if (WindDirection != null) 124 | { 125 | d.Add(nameof(WindDirection), WindDirection.Value.DecimalDegrees); 126 | } 127 | if (Rain != null) 128 | { 129 | d.Add(nameof(Rain), Rain.Value.Millimeters); 130 | } 131 | if (Light != null) 132 | { 133 | d.Add(nameof(Light), Light.Value.Lux); 134 | } 135 | 136 | return d; 137 | } 138 | } -------------------------------------------------------------------------------- /Source/Meadow.Clima/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildernessLabs/Clima/e5c2ea55dd8e1f368247a906ec007758c7c236e9/Source/Meadow.Clima/icon.png -------------------------------------------------------------------------------- /Source/Meadow.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | --------------------------------------------------------------------------------