├── .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 | 
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 | 
16 |
17 | ## Attach Antenna Cable to Meadow
18 |
19 | Attach the µFL connector to the Meadow external antenna connector as illustrated below:
20 |
21 | 
22 |
23 | ## Install the PCB
24 |
25 | Using (4) M2x6 screws, install the assembled PCB:
26 |
27 | 
28 |
29 | ## Install Cover
30 |
31 | Using the remaining (4) M2x6 screws, install the cover:
32 |
33 | 
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 | 
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 | 
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 | 
28 |
29 | Screw the bolt far enough that it pokes out the other side of the insert and pushes out the plastic:
30 |
31 | 
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 | 
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 | 
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 | 
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 | 
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 | 
8 |
9 | 
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 | 
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 | 
6 |
7 | ## Assembling
8 |
9 | Start by connecting the two steel tubes together like so:
10 |
11 | 
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 | 
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 | 
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 | 
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 | 
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 | 
32 |
33 | At this point, you should have something like this:
34 |
35 | 
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 | 
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 | 
44 |
45 | 
46 |
47 | About one inch/2.54cm above where the 2 steel tubes connect, mount the Clima Meadow module onto the tube.
48 |
49 | 
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 | 
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 | 
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 | 
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 | 
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 |
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 | 
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 |
--------------------------------------------------------------------------------