diff --git a/.gitignore b/.gitignore index dfcfd56f..d432aa94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,350 +1,9 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ +.DS_Store +.venv +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +.ipynb_checkpoints diff --git a/1-getting-started/README.md b/1-getting-started/README.md new file mode 100644 index 00000000..e5b3d0f8 --- /dev/null +++ b/1-getting-started/README.md @@ -0,0 +1,16 @@ +# Getting Started with IoT + +In this section of the curriculum, you will be introduced to the Internet of Things, and learn the basic concepts including building your first 'Hello World' IoT project connecting to the cloud. This project is a nightlight that lights up as light levels measured by a sensor drop. + +![The LED connected to the WIO turning on and off as the light level changes](../images/wio-running-assignment-1-1.gif) + +## Topics + +1. [Introduction to IoT](lessons/1-introduction-to-iot/README.md) +1. [A deeper dive into IoT](lessons/2-deeper-dive/README.md) +1. [Interact with the physical world with sensors and actuators](lessons/3-sensors-and-actuators/README.md) +1. [Connect your device to the Internet](lessons/4-connect-internet/README.md) + +## Credits + +All the lessons were written with โ™ฅ๏ธ by [Jim Bennett](https://GitHub.com/JimBobBennett) diff --git a/1-getting-started/lessons/1-introduction-to-iot/README.md b/1-getting-started/lessons/1-introduction-to-iot/README.md new file mode 100644 index 00000000..4936c8f9 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/README.md @@ -0,0 +1,222 @@ +# Introduction to IoT + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/1) + +## Introduction + +This lesson covers some of the introductory topics around the Internet of Things, and gets you going setting up your hardware. + +In this lesson we'll cover: + +* [What is the 'Internet of Things'?](#what-is-the-internet-of-things) +* [IoT devices](#iot-devices) +* [Set up your device](#set-up-your-device) +* [Applications of IoT](#applications-of-iot) +* [Examples of IoT devices you may have around you](#examples-of-iot-devices-you-may-have-around-you) + +## What is the 'Internet of Things'? + +The term 'Internet of Things' was coined by [Kevin Ashton](https://wikipedia.org/wiki/Kevin_Ashton) in 1999 to refer to connecting the Internet to the physical world via sensors. Since then the term has been used to describe any device that interacts with the physical world around it either by gathering data from sensors, or providing real-world interactions via actuators (devices that do something like turn on a switch or light an LED), generally connected to other devices or the Internet. + +> **Sensors** gather information from the world, such as measuring speed, temperature or location. +> +> **Actuators** convert electrical signals into real-world interactions such as levers, turning on lights, making sounds, or sending control signals to other hardware such as to turn on a power socket + +IoT as a technology area is more than just devices - it includes cloud based services that can process the sensor data, or send requests to actuators connected to IoT devices. It also includes devices that don't have connectivity, often referred to as edge devices, that can process and respond to sensor data themselves, usually using AI models trained in the cloud. + +IoT is a fast growing technology field. It is estimated that by the end of 2020, 30 billion IoT devices were deployed and connected to the Internet. Looking to the future, it is estimated that by 2025, IoT devices will be gathering almost 80 zettabytes of data, or 80 trillion gigabytes. That's a lot of data! + +![A graph showing active IoT devices over time, with an upward trend from under 5 billion in 2015 to over 30 billion in 2025](../../../images/connected-iot-devices.svg) + +โœ… Do a little research: how much of the data generated by IoT devices is actually used, and how much is wasted? Why is so much data ignored? + +This data is the key to IoT's success. To be a successful IoT developer, you need to understand the data you need to gather, how to gather it, how to make decisions based off it, and how to use those decisions to interact back with the physical world if needed. + +## IoT devices + +The **T** in IoT stands for **Things** - devices that interact with the physical world around them either by gathering data from sensors, or providing real-world interactions via actuators. + +Devices for production or commercial use, such as the consumer fitness trackers, or industrial machine controllers, are usually custom made. They use custom circuit boards, maybe even custom processors, designed to meet the needs of a particular task, whether that's being small enough to fit on a wrist, or rugged enough to work in a high temperature, high stress, high vibration factory environment. + +As a developer, either learning about IoT or creating a prototype device, you'll need to start with a developer kit. These are general purpose IoT devices designed for developers to use, often with features that you wouldn't see on a production device, such as a set of external pins to connect sensors or actuators to, hardware to support debugging, or additional resources that would add unnecessary cost when doing a large manufacturing run. + +These developer kits usually fall into two categories - microcontrollers and single-board computers. These will be introduced here, and we'll go into them in more detail in the next lesson. + +> ๐Ÿ’ Your phone can also be considered to be a general-purpose IoT device, with sensors and actuators built in, with different apps using the sensors and actuators in different ways with different cloud services. You can even find some IoT tutorials that use a phone app as an IoT device. + +### Microcontrollers + +A microcontroller (also referred to as an MCU, short for microcontroller unit) is a small computer consisting of: + +๐Ÿง  One or more central processing units (CPUs) - the 'brain' of the microcontroller that runs your program + +๐Ÿ’พ Memory (RAM and program memory) - where your program, data, and variables are stored + +๐Ÿ”Œ Programmable input/output (I/O) connections - to talk to external peripherals (connected devices) such as sensors or actuators + +Microcontrollers are typically low cost computing devices, with average prices for the ones used in custom hardware dropping to around US$0.50, with some devices as cheap as US$0.03. Developer kits can start as low as US$4, with costs rising as you add more features. The [Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html), a microcontroller developer kit from [Seeed studios](https://www.seeedstudio.com) that has sensors, actuators, WiFi and a screen costs around US$30. + +![A Wio Terminal](../../../images/wio-terminal.png) + +> ๐Ÿ’ When searching the Internet for microcontrollers be wary of searching for the term **MCU** as this will bring back a lot of results for the Marvel Cinematic Universe, not microcontrollers. + +Microcontrollers are designed to be programmed to do a limited number of very specific tasks, rather than being general-purpose computers like PCs or Macs. Except for very specific scenarios, you can't connect a monitor, keyboard and mouse and use them for general purpose tasks. + +Microcontroller developer kits usually come with additional sensors and actuators on board. Most boards will have one or more LEDs you can program, along with other devices such as standard plugs for adding more sensors or actuators using various manufacturers ecosystems or built in sensors (usually the most popular ones such as temperature). Some microcontrollers have built in wireless connectivity such as Bluetooth or WiFi, or have additional microcontrollers on the board to add this connectivity. + +> ๐Ÿ’ Microcontrollers are usually programmed in C/C++. + +### Single-board computers + +A single-board computer is a small computing devices that has all the elements of a complete computer contained on a single small board. These are devices that have specifications close to a desktop or laptop PC or Mac, run a full operating system, but are small, use lower power, and are substantially cheaper. + +![A Raspberry Pi 4](../../../images/raspberry-pi-4.jpg) + +***Raspberry Pi 4. Michael Henzler / [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page) / [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)*** + +The Raspberry Pi is one of the most popular single-board computers. + +Like a microcontroller, single-board computers have a CPU, memory and input/output pins, but they have additional features such as a graphics chip to allow you to connect monitors, audio outputs, and USB ports to connect keyboards mice and other standard USB devices like webcams or external storage. Programs are stored on SD cards or hard drives along with an operating system, instead of a memory chip built into the board. + +> ๐ŸŽ“ You can think of a single-board computer as a smaller, cheaper version of the PC or Mac you are reading this on, with the addition of GPIO pins to interact with sensors and actuators. + +SIngle-board computers are fully-featured computers, so can be programmed in any language. IoT devices are typically programmed in Python. + +### Hardware choices for the rest of the lessons + +All the subsequent lessons include assignments using an IoT device to interact with the physical world, and communicate with the cloud. Each lesson supports 3 device choices - Arduino (using a Seeed Studios Wio Terminal), or a single-board computer, either a physical device (a Raspberry Pi 4), or a virtual single-board computer running on your PC or Mac. + +You can read about the hardware needed to complete all the assignments in the [hardware guide](../../hardware.md). + +> ๐Ÿ’ You don't need to purchase any IoT hardware to complete the assignments, you can do everything using a virtual single-board computer. + +Which hardware you choose is up to you - it depends on what you have available either at home on in your school, and what programming language you know or plan to learn. Both hardware variants will use the same sensor ecosystem, so if you start down one path, you can change to the other without having to replace most of the kit. The virtual single-board computer will be the equivalent of learning on a Raspberry Pi, with most of the code transferrable to the Pi if you eventually get a device and sensors. + +### Arduino developer kit + +If you are interested in learning microcontroller development, you can complete the assignments using an Arduino device. You will need a basic understanding of C/C++ programming, as the lessons will only teach code that is relevant to the Arduino framework, the sensors and actuators being used, and the libraries that interact with the cloud. + +The assignments will use [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=academic-17441-jabenn) with the [PlatformIO extension for microcontroller development](https://platformio.org). You can also use the Arduino IDE if you are experienced with this tool, as instructions will not be provided. + +### Single-board computer developer kit + +If you are interested in learning IoT development using single-board computers, you can complete the assignments using a Raspberry Pi, or a virtual device running on your PC or Mac. + +You will need a basic understanding of Python programming, as the lessons will only teach code that is relevant to the sensors and actuators being used, and the libraries that interact with the cloud. + +> ๐Ÿ’ If you want to learn to code in Python, check out the following two video series: +> +> * [Python for beginners](https://channel9.msdn.com/Series/Intro-to-Python-Development?WT.mc_id=academic-17441-jabenn) +> * [More Python for beginners](https://channel9.msdn.com/Series/More-Python-for-Beginners?WT.mc_id=academic-7372-jabenn) + +The assignments will use [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=academic-17441-jabenn). + +If you are using a Raspberry Pi, you can either run your Pi using the full desktop version of Raspberry Pi OS, and do all the coding directly on the Pi using [the Raspberry Pi OS version of VS Code](https://code.visualstudio.com/docs/setup/raspberry-pi?WT.mc_id=academic-17441-jabenn), or run your Pi as a headless device and code from your PC or Mac using VS Code with the [Remote SSH extension](https://code.visualstudio.com/docs/remote/ssh?WT.mc_id=academic-17441-jabenn) that allows you to connect to your Pi and edit, debug and run code as if you were coding on it directly. + +If you use the virtual device option, you will code directly on your computer. Instead of accessing sensors and actuators, you will use a tool to simulate this hardware providing sensor values that you can define, and showing the results of actuators on screen. + +## Set up your device + +Before you can get started with programming your IoT device, you will need to do a small amount of setup. Follow the relevant instructions below depending on which device you will be using. + +> ๐Ÿ’ If you don't have a device yet, refer to the [hardware guide](../../../hardware.md) to help decide which device you are going to use, and what additional hardware you need to purchase. You don't need to purchase hardware, as all the projects can be run on virtual hardware. + +These instructions do include links to third-party websites from the creators of the hardware or tools you will be using. This is to ensure you are always using the most up-to-date instructions for the various tools and hardware. + +Work through the relevant guide to set your device up and complete a 'Hello World' project. This will be the first step in creating an IoT nightlight over the 4 lessons in this getting started part. + +* [Arduino - Wio Terminal](wio-terminal.md) +* [Single-board computer - Raspberry Pi](pi.md) +* [Single-board computer - Virtual device](virtual-device.md) + +## Applications of IoT + +IoT covers a huge range of use cases, across a few broad groups: + +* Consumer IoT +* Commercial IoT +* Industrial IoT +* Infrastructure IoT + +โœ… Do a little research: For each of the areas described below, find one concrete example that's not given in the text. + +### Consumer IoT + +Consumer IoT refers to IoT devices that consumers will buy and use around the home. Some of these devices are incredibly useful, such as smart speakers, smart heating systems and robotic vacuum cleaners. Others are questionable in their usefulness, such as voice controlled taps that then mean you cannot turn them off as the voice control cannot hear you over the sound of running water. + +Consumer IoT devices are empowering people to achieve more in their surroundings, especially the 1 billion who have a disability. Robotic vacuum cleaners can provide clean floors to people with mobility issues who cannot vacuum themselves, voice controlled ovens allow people with limited vision or motor control to heat their ovens with only their voice, health monitors can allow patients to monitor chronic conditions themselves with more regular and more detailed updates on their conditions. These devices are becoming so ubiquitous that even young children are using them as part of their daily lives, for example students doing virtual schooling during the COVID pandemic setting timers on smart home devices to track their schoolwork or alarms to remind them of upcoming class meetings. + +โœ… What consumer IoT devices do you have on your person or in your home? + +### Commercial IoT + +Commercial IoT covers the use of IoT in the workplace. In an office setting there may be occupancy sensors and motion detectors to manage lighting and heating to only keep the lights and heat off when not needed, reducing cost and carbon emissions. In a factory, IoT devices can monitor for safety hazards such as workers not wearing hard hats or noise that has reached dangerous levels. In retail, IoT devices can measure the temperature of cold storage, alerting the shop owner if a fridge or freezer is outside the required temperature range, or they can monitor items on shelves to direct employees to refill produce that has been sold. The transport industry is relying more and more on IoT to monitor vehicle locations, track on-road mileage for road user charging, track driver hours and break compliance, or notify staff when a vehicle is approaching a depot to prepare for loading or unloading. + +โœ… What commercial IoT devices do you have in your school or workplace? + +### Industrial IoT (IIoT) + +Industrial IoT, or IIoT, is the use of IoT devices to control and manage machinery on a large scale. This covers a wide range of use cases, from factories to digital agriculture. + +Factories use IoT devices in many different ways. Machinery can be monitored with multiple sensors to track things like temperature, vibration and rotation speed. This data can then be monitored to allow the machine to be stopped if it goes outside of certain tolerances - it runs to hot and gets shut down for example. This data can also be gathered and analyzed over time to do predictive maintenance, where AI models will look at the data leading up to a failure, and use that to predict other failures before they happen. + +Digital agriculture is important if the planet is to feed the growing population, especially for the 2 billion people in 500 million households that survive on [subsistence farming](https://wikipedia.org/wiki/Subsistence_agriculture). Digital agriculture can range from a few single digit dollar sensors, to massive commercial setups. A farmer can start by monitoring temperatures and using [growing degree days](https://wikipedia.org/wiki/Growing_degree-day) to predict when a crop will be ready for harvest. They can connect soil moisture monitoring to automated watering systems to give their plants as much water as is needed, but no more to ensure their crops don't dry out without wasting water. Farmers are even taking it further and using drones, satellite data and AI to monitor crop growth, disease and soil quality over huge areas of farmland. + +โœ… What other IoT devices could help farmers? + +### Infrastructure IoT + +Infrastructure IoT is monitoring and controlling the local and global infrastructure that people use every day. + +[Smart Cities](https://wikipedia.org/wiki/Smart_city) are urban areas that use IoT devices to gather data about the city and use that to improve how the city runs. These cities are usually run with collaborations between local governments, academia and local businesses, tracking and managing things varying from transport to parking and pollution. For example, in Copenhagen, Denmark, air pollution is important to the local residents, so it is measured and the data is used to provide information on the cleanest cycling and jogging routes. + +[Smart power grids](https://wikipedia.org/wiki/Smart_grid) allow better analytics of power demand by gathering usage data at the level of individual homes. This data can guide decisions at a country level including where to build new power stations, and at a personal level by giving users insights into how much power they are using, when they are using it, and even suggestions on how to reduce costs, such as charging electric cars at night. + +โœ… If you could add IoT devices to measure anything where you live, what would it be? + +## Examples of IoT devices you may have around you + +You'd be amazed by just how many IoT devices you have around you. I'm writing this from home and I have the following devices connected to the Internet with smart features such as app control, voice control, or the ability to send data to me via my phone: + +* Multiple smart speakers +* Fridge, dishwasher, oven and microwave +* Electricity monitor for solar panels +* Smart plugs +* Video doorbell and security cameras +* Smart thermostat with multiple smart room sensors +* Garage door opener +* Home entertainment systems and voice controlled TVs +* Lights +* Fitness and health trackers + +All these types of devices have sensors and/or actuators and talk to the Internet. I can tell from my phone if my garage door is open, and ask my smart speaker to close it for me. I can even set it to a timer so if it's still open at night, it will close automatically. When my doorbell rings, I can see from my phone who is there wherever I am in the world, and talk to them via a speaker and microphone built into the doorbell. I can monitor my blood glucose, heart rate and sleep patterns, looking for patterns in the data to improve my health. And I can control my lights via the cloud, and sit in the dark when my Internet connection goes down. + +--- + +## ๐Ÿš€ Challenge + +List as many IoT devices as you can that are in your home, school or workplace - there may be more than you think! + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/2) + +## Review & Self Study + +Read up on the benefits and failures of consumer IoT projects. Check news sites for articles on when it has gone wrong, such as privacy issues, hardware problems or problems caused by lack of connectivity. + +Some examples: + +* Check out the Twitter account **[Internet of Sh*t](https://twitter.com/internetofshit)** *(profanity warning)* for some good examples of failures with consumer IoT. +* [c|net - My Apple Watch saved my life: 5 people share their stories](https://www.cnet.com/news/apple-watch-lifesaving-health-features-read-5-peoples-stories/) +* [c|net - ADT technician pleads guilty to spying on customer camera feeds for years](https://www.cnet.com/news/adt-home-security-technician-pleads-guilty-to-spying-on-customer-camera-feeds-for-years/) *(trigger warning - non-consensual voyeurism)* + +## Assignment + +[Investigate an IoT project](assignment.md) diff --git a/1-getting-started/lessons/1-introduction-to-iot/assignment.md b/1-getting-started/lessons/1-introduction-to-iot/assignment.md new file mode 100644 index 00000000..80b9d19d --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/assignment.md @@ -0,0 +1,13 @@ +# Investigate an IoT project + +## Instructions + +There are many large and small scale IoT projects being rolled out globally, from smart farms to smart cities, healthcare monitoring, transport, or the use of public spaces. + +Search the web for details of a project that interests you, ideally one close you where you live. Explain the upsides and the downsides of the project, such as what benefit comes from it, any problems it causes and how privacy is taken into consideration. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Explain the upsides and downsides | Gave a clear explanation of the upsides and downsides of the project | Gave a brief explanation of the upsides and downsides | Didn't explain the upsides or the downsides | diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/pi/nightlight/app.py b/1-getting-started/lessons/1-introduction-to-iot/code/pi/nightlight/app.py new file mode 100644 index 00000000..f0b303f3 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/pi/nightlight/app.py @@ -0,0 +1 @@ +print('Hello World!') \ No newline at end of file diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/virtual-device/nightlight/app.py b/1-getting-started/lessons/1-introduction-to-iot/code/virtual-device/nightlight/app.py new file mode 100644 index 00000000..157c1624 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/virtual-device/nightlight/app.py @@ -0,0 +1,5 @@ +from counterfit_connection import CounterFitConnection + +CounterFitConnection.init('127.0.0.1', 5000) + +print('Hello World!') \ No newline at end of file diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/.gitignore b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/.vscode/extensions.json b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/app.py b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/app.py new file mode 100644 index 00000000..f33ca45e --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/app.py @@ -0,0 +1,20 @@ +from counterfit_shims_grove.counterfit_connection import CounterFitConnection +import time +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor +from counterfit_shims_grove.grove_led import GroveLed + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +while True: + light = light_sensor.light + print('Light level:', light) + + if light < 200: + led.on() + else: + led.off() + + time.sleep(1) \ No newline at end of file diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/include/README b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/lib/README b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/platformio.ini b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/platformio.ini new file mode 100644 index 00000000..4e03e890 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/src/main.cpp b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/src/main.cpp new file mode 100644 index 00000000..3987fc66 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/src/main.cpp @@ -0,0 +1,17 @@ +#include + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); +} + +void loop() +{ + Serial.println("Hello World"); + delay(5000); +} \ No newline at end of file diff --git a/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/test/README b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal/nightlight/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/1-getting-started/lessons/1-introduction-to-iot/pi.md b/1-getting-started/lessons/1-introduction-to-iot/pi.md new file mode 100644 index 00000000..119279c5 --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/pi.md @@ -0,0 +1,247 @@ +# Raspberry Pi + +The [Raspberry Pi](https://raspberrypi.org) is a single-board computer. You can add sensors and actuators using a wide range of devices and ecosystems, and for these lessons using a hardware ecosystem called [Grove](https://www.seeedstudio.com/category/Grove-c-1003.html). You will code your Pi and access the Grove sensors using Python. + +![A Raspberry Pi 4](../../../images/raspberry-pi-4.jpg) + +***Raspberry Pi 4. Michael Henzler / [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page) / [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)*** + +## Setup + +If you are using a Raspberry Pi as your IoT hardware, you have two choices - you can work through all these lessons and code directly on the Pi, or you can connect remotely to a 'headless' Pi and code from your computer. + +Before you begin, you also need to connect the Grove Base Hat to your Pi. + +### Task + +Install the Grove base hat on your Pi and configure the Pi + +1. Connect the Grove base hat to your Pi. The socket on the hat fits over all of the GPIO pins on the Pi, sliding all the way down the pins to sit firmly on the base. It sits over the Pi, covering it. + + ![Fitting the grove hat](../../../images/pi-grove-hat-fitting.gif) + +1. Decide how you want to program you Pi, and head to the relevant section below: + + * [Work directly on your Pi](#work-directly-on-your-pi) + * [Remote access to code the Pi](#remote-access-to-code-the-pi) + +### Work directly on your Pi + +If you want to work directly on your Pi, you can use the desktop version of Raspberry Pi OS and install all the tools you need. + +#### Task + +Set up your Pi for development. + +1. Follow the instructions in the [Raspberry Pi setup guide](https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up) to set up your Pi, connect it to a keyboard/mouse/monitor, connect it to your WiFi or ethernet network, and update the software. The OS you want to install is **Raspberry Pi OS (32 bit)**, it is marked as the recommended OS when using the Raspberry Pi Imager to image your SD card. + +To program the Pi using the Grove sensors and actuators, you will need to install an editor to allow you to write the device code, and various libraries and tools that interact with the Grove hardware. + +1. Once your Pi has rebooted, launch the Terminal by clicking the **Terminal** icon on the top menu bar, or choose *Menu -> Accessories -> Terminal* + +1. Run the following command to ensure the OS and installed software is up to date: + + ```sh + sudo apt update && sudo apt full-upgrade --yes + ``` + +1. Run the following command to install all the needed libraries for the Grove hardware: + + ```sh + curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s - + ``` + + One of the powerful features of Python is the ability to install [pip packages](https://pypi.org) - these are packages of code written by other people and published to the Internet. You can install a pip package onto your computer with one command, then use that package in your code. This Grove install script will install the pip packages you will use to work with the Grove hardware from Python. + +1. Reboot the Pi either using the menu, or running the following command in the Terminal: + + ```sh + sudo reboot + ``` + +1. Once the Pi has rebooted, relaunch the Terminal and run the following command to install [Visual Studio Code (VS Code)](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) - this is the editor you will be using to write your device code in Python. + + ```sh + sudo apt install code + ``` + + Once this is installed, VS Code will be available from the top menu. + + > ๐Ÿ’ You are free to use any Python IDE or editor for these lessons if you have a preferred tool, but the lessons will give instructions based off using VS Code. + +1. Install Pylance. This is an extension for VS Code that provides Python language support. Refer to the [Pylance extension documentation](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) for instructions on installing this extension in VS Code. + +### Remote access to code the Pi + +Rather than coding directly on the Pi, it can run 'headless', that is not connected to a keyboard/mouse/monitor, and configure and code on it from your computer, using Visual Studio Code. + +#### Set up the Pi OS + +To code remotely, the Pi OS needs to be installed on an SD Card. + +##### Task + +Set up the headless Pi OS. + +1. Download the **Raspberry Pi Imager** from the [Raspberry Pi OS software page](https://www.raspberrypi.org/software/) and install it + +1. Insert an SD card into your computer, using an adapter if necessary + +1. Launch the Raspberry Pi Imager + +1. From the Raspberry Pi Imager, select the **CHOOSE OS** button, then select *Raspberry Pi OS (Other)*, followed by *Raspberry Pi OS Lite (32-bit)* + + ![The Raspberry Pi Imager with Raspberry Pi OS Lite selected](../../../images/raspberry-pi-imager.png) + + > ๐Ÿ’ Raspberry Pi OS Lite is a version of Raspberry Pi OS that doesn't have the desktop UI, or UI based tools. These aren't needed for a headless Pi, and makes the install smaller and boot up time faster. + +1. Select the **CHOOSE STORAGE** button, then select your SD card + +1. Launch the **Advanced Options** by pressing `Ctrl+Shift+X`. These options allow some pre-configuration of the Raspberry Pi OS before it is imaged to the SD card. + + 1. Check the **Enable SSH** check box, and set a password for the `pi` user. This is the password you will use to log in to the Pi later. + + 1. If you are planning to connect to the Pi over WiFi, check the **Configure WiFi** check box, and enter your WiFi SSID and password, as well as selecting your WiFi country. You do not need to do this if you will use an ethernet cable. Make sure the network you connect to is the same one your computer is on. + + 1. Check the **Set locale settings** check box, and set your country and timezone + + 1. Select the **SAVE** button + +1. Select the **WRITE** button to write the OS to the SD card. If you are using macOS, you will be asked to enter your password as the underlying tool that writes disk images needs privileged access. + +The OS will be written to the SD card, and once compete the card will be ejected by the OS, and you will be notified. Remove the SD card from your computer, insert it into the Pi and power up the Pi. + +#### Connect to the Pi + +The next step is to remotely access the Pi. You can do this using `ssh`, which is available on macOS, Linux and recent versions of Windows. + +##### Task + +Remotely access the Pi. + +1. Launch a Terminal or Command Prompt, and enter the following command to connect to the Pi: + + ```sh + ssh pi@raspberrypi.local + ``` + + If you are on Windows using an older version that doesn't have `ssh` installed, you can use OpenSSH. You can find the installation instructions in the [OpenSSH installation documentation](https://docs.microsoft.com//windows-server/administration/openssh/openssh_install_firstuse?WT.mc_id=academic-17441-jabenn). + +1. This should connect to your Pi and ask for the password. + + Being able to find computers on your network by using `.local` is a fairly recent addition to Linux and Windows. If you are using Linux or Windows and you get any errors about the Hostname not being found, you will need to install additional software to enable ZeroConf networking (also referred to by Apple as Bonjour): + + 1. If you are using Linux, install Avahi using the following command: + + ```sh + sudo apt-get install avahi-daemon + ``` + + 1. If you are using Windows, the easiest way to enable ZeroConf is to install [Bonjour Print Services for Windows](http://support.apple.com/kb/DL999). You can also install [iTunes for Windows](https://www.apple.com/itunes/download/) to get a newer version of the utility (which is not available standalone). + + > ๐Ÿ’ If you cannot connect using `raspberrypi.local`, then you can use the IP address of your Pi. Refer to the [Raspberry Pi IP address documentation](https://www.raspberrypi.org/documentation/remote-access/ip-address.md) for instructions of a number of ways to get the IP address. + +1. Enter the password you set in the Raspberry Pi Imager Advanced Options + +#### Configure software on the Pi + +Once you are connected to the Pi, you need to ensure the OS is up to date, and install various libraries and tools that interact with the Grove hardware. + +##### Task + +Configure the installed Pi software and install the Grove libraries. + +1. From your `ssh` session, run the following command to update then reboot the Pi: + + ```sh + sudo apt update && sudo apt full-upgrade --yes && sudo reboot + ``` + + The Pi will be updated and rebooted. The `ssh` session will end when the Pi is rebooted, so leave it about 30 seconds then reconnect. + +1. From the reconnected `ssh` session, run the following command to install all the needed libraries for the Grove hardware: + + ```sh + curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s - + ``` + + One of the powerful features of Python is the ability to install [pip packages](https://pypi.org) - these are packages of code written by other people and published to the Internet. You can install a pip package onto your computer with one command, then use that package in your code. This Grove install script will install the pip packages you will use to work with the Grove hardware from Python. + +1. Reboot the Pi by running the following command: + + ```sh + sudo reboot + ``` + + The `ssh` session will end when the Pi is rebooted. There is no need to reconnect. + +#### Configure VS Code for remote access + +Once the Pi is configured, you can connect to it using Visual Studio Code (VS Code) from your computer - this is a free developer text editor you will be using to write your device code in Python. + +##### Task + +Install the required software and connect remotely to your Pi. + +1. Install VS Code on your computer by following the [VS Code documentation](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) + +1. Follow the instructions in the [VS Code Remote Development using SSH documentation](https://code.visualstudio.com/docs/remote/ssh?WT.mc_id=academic-17441-jabenn) to install the components needed + +1. Following the same instructions, connect VS Code to the Pi + +1. Once connected, follow the [managing extensions](https://code.visualstudio.com/docs/remote/ssh#_managing-extensions?WT.mc_id=academic-17441-jabenn) instructions to install the [Pylance extension](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) remotely onto the Pi + +## Hello world + +It is traditional when starting out with a new programming language or technology to create a 'Hello World' application - a small application that outputs something like the text `"Hello World"` to show that all the tools are correctly configured. + +The Hello World app for the Pi will ensure that you have Python and Visual Studio code installed correctly. + +This app will be in a folder called `nightlight`, and it will be re-used with different code in later parts of this assignment to build the nightlight application. + +### Task + +Create the Hello World app. + +1. Launch VS Code, either directly on the Pi, or on your computer and connected to the Pi using the Remote SSH extension + +1. Launch the VS Code Terminal by selecting *Terminal -> New Terminal, or pressing `` CTRL+` ``. It will open to the `pi` users home directory. + +1. Run the following commands to create a directory for your code, and create a Python file called `app.py` inside that directory: + + ```sh + mkdir nightlight + cd nightlight + touch app.py + ``` + +1. Open this folder in VS Code by selecting *File -> Open...* and selecting the *nightlight* folder, then select **OK** + + ![The VS Code open dialog showing the nightlight folder](../../../images/vscode-open-nightlight-remote.png) + +1. Open the `app.py` file from the VS Code explorer and add the following code: + + ```python + print('Hello World!') + ``` + + The `print` function prints whatever is passed to it to the console. + +1. From the VS Code Terminal, run the following to run your Python app: + + ```sh + python3 app.py + ``` + + > ๐Ÿ’ You need to explicitly call `python3` to run this code just in case you have Python 2 installed in addition to Python 3 (the latest version). If you have Python2 installed then calling `python` will use Python 2 instead of Python 3 + + You should see the following output: + + ```output + pi@raspberrypi:~/nightlight $ python3 app.py + Hello World! + ``` + +> ๐Ÿ’ You can find this code in the [code/pi](code/pi) folder. + +๐Ÿ˜€ Your 'Hello World' program was a success! diff --git a/1-getting-started/lessons/1-introduction-to-iot/virtual-device.md b/1-getting-started/lessons/1-introduction-to-iot/virtual-device.md new file mode 100644 index 00000000..dc49f29e --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/virtual-device.md @@ -0,0 +1,208 @@ +# Virtual single-board computer + +Instead of purchasing an IoT device, along with sensors and actuators, you can use your computer to simulate IoT hardware. The [CounterFit project](https://github.com/CounterFit-IoT/CounterFit) allows you to run an app locally that simulates IoT hardware such as sensors and actuators, and access the sensors and actuators from local Python code that is written in the same way as the code you would write on a Raspberry Pi using physical hardware. + +## Setup + +To use CounterFit, you will need to install some free software on your computer. + +### Task + +Install the required software. + +1. Install Python. Refer to the [Python downloads page](https://www.python.org/downloads/) for instructions on install the latest version of Python. + +1. Install Visual Studio Code (VS Code). This is the editor you will be using to write your virtual device code in Python. Refer to the [VS Code documentation](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) for instructions on installing VS Code. + + > ๐Ÿ’ You are free to use any Python IDE or editor for these lessons if you have a preferred tool, but the lessons will give instructions based off using VS Code. + +1. Install the VS Code Pylance extension. This is an extension for VS Code that provides Python language support. Refer to the [Pylance extension documentation](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) for instructions on installing this extension in VS Code. + +The instructions to install and configure the CounterFit app will be given at the relevant time in the assignment instructions as it is installed on a per-project basis. + +## Hello world + +It is traditional when starting out with a new programming language or technology to create a 'Hello World' application - a small application that outputs something like the text `"Hello World"` to show that all the tools are correctly configured. + +The Hello World app for the virtual IoT hardware will ensure that you have Python and Visual Studio code installed correctly. It will also connect to CounterFit for the virtual IoT sensors and actuators. It won't use any hardware, it will just connect to prove everything is working. + +This app will be in a folder called `nightlight`, and it will be re-used with different code in later parts of this assignment to build the nightlight application. + +### Configure a Python virtual environment + +One of the powerful features of Python is the ability to install [pip packages](https://pypi.org) - these are packages of code written by other people and published to the Internet. You can install a pip package onto your computer with one command, then use that package in your code. You'll be using pip to install a package to talk to CounterFit. + +By default when you install a package it is available everywhere on your computer, and this can lead to problems with package versions - such as one application depending on one version of a package that breaks when you install a new version for a different application. To work around this problem, you can use a [Python virtual environment](https://docs.python.org/3/library/venv.html), essentially a copy of Python in a dedicated folder, and when you install pip packages they get installed just to that folder. + +#### Task + +Configure a Python virtual environment and install the pip packages for CounterFit. + +1. From your terminal or command line, run the following at a location of your choice to create and navigate to a new directory: + + ```sh + mkdir nightlight + cd nightlight + ``` + +1. Now run the following to create a virtual environment in the `.venv` folder + + ```sh + python3 -m venv .venv + ``` + + > ๐Ÿ’ You need to explicitly call `python3` to create the virtual environment just in case you have Python 2 installed in addition to Python 3 (the latest version). If you have Python2 installed then calling `python` will use Python 2 instead of Python 3 + +1. Activate the virtual environment: + + * On Windows run: + + ```cmd + .venv\Scripts\activate.bat + ``` + + * On macOS or Linux, run: + + ```cmd + source ./.venv/bin/activate + ``` + +1. Once the virtual environment has been activated, the default `python` command will run the version of Python that was used to create the virtual environment. Run the following to see this: + + ```sh + python --version + ``` + + You should see the following: + + ```output + (.venv) โžœ nightlight python --version + Python 3.9.1 + ``` + + > ๐Ÿ’ Your Python version may be different - as long as it's version 3.6 or higher you are good. If not, delete this folder, install a newer version of Python and try again. + +1. Run the following commands to install the pip packages for CounterFit. These packages include the main CounterFit app as well as shims for Grove hardware. These shims allow you to write code as if you were programming using physical sensors and actuators from the Grove ecosystem, but connected to virtual IoT devices. + + ```sh + pip install CounterFit + pip install counterfit-connection + pip install counterfit-shims-grove + ``` + + These pip packages will only be installed in the virtual environment, and will not be available outside of this. + +### Write the code + +Once the Python virtual environment is ready, you can write the code for the 'Hello World' application + +#### Task + +Create a Python application to print `"Hello World"` to the console. + +1. From your terminal or command line, run the following inside the virtual environment to create a Python file called `app.py`: + + * From Windows run: + + ```cmd + type nul > app.py + ``` + + * On macOS or Linux, run: + + ```cmd + touch app.py + ``` + +1. Open the current folder in VS Code: + + ```sh + code . + ``` + +1. When VS Code launches, it will activate the Python virtual environment. You will see this in the bottom status bar: + + ![VS Code showing the selected virtual environment](../../../images/vscode-virtual-env.png) + +1. If the VS Code Terminal is already running when VS Code starts up, it won't have the virtual environment activated in it. The easiest thing to do is kill the terminal using the **Kill the active terminal instance** button: + + ![VS Code Kill the active terminal instance button](../../../images/vscode-kill-terminal.png) + + You can tell if the terminal has the virtual environment activated as the name of the virtual environment will be a prefix on the terminal prompt. For example, it might be: + + ```sh + (.venv) โžœ nightlight + ``` + + If you don't see `.venv` as a prefix on the prompt, the virtual environment is not active in the terminal. + +1. Launch a new VS Code Terminal by selecting *Terminal -> New Terminal, or pressing `` CTRL+` ``. The new terminal will load the virtual environment, and you will see the call to activate this in the terminal, as well as having the name of the virtual environment (`.venv`) in the prompt: + + ```output + โžœ nightlight source .venv/bin/activate + (.venv) โžœ nightlight + ``` + +1. Open the `app.py` file from the VS Code explorer and add the following code: + + ```python + print('Hello World!') + ``` + + The `print` function prints whatever is passed to it to the console. + +1. From the VS Code terminal, run the following to run your Python app: + + ```sh + python app.py + ``` + + You should see the following output: + + ```output + (.venv) โžœ nightlight python app.py + Hello World! + ``` + +๐Ÿ˜€ Your 'Hello World' program was a success! + +### Connect the 'hardware' + +As a second 'Hello World' step, you will run the CounterFit app and connect your code to it. This is the virtual equivalent of plugging in some IoT hardware to a dev kit. + +#### Task + +1. From the VS Code terminal, launch the CounterFit app with the following command: + + ```sh + CounterFit + ``` + + The app will start running and open in your web browser: + + ![The Counter Fit app running in a browser](../../../images/counterfit-first-run.png) + + You will see it marked as *Disconnected*, with the LED in the top-right corner turned off. + +1. Add the following code to the top of `app.py`: + + ```python + from counterfit_connection import CounterFitConnection + CounterFitConnection.init('127.0.0.1', 5000) + ``` + + This code imports the `CounterFitConnection` class from the `counterfit_connection` module, which comes from the `counterfit-connection` pip package you installed earlier. It then initializes a connection to the CounterFit app running on `127.0.0.1`, which is an IP address you can always use to access your local computer (often referred to as *localhost*), on port 5000. + + > ๐Ÿ’ If you have other apps running on port 5000, you can change this by updating the port in the code, and running CounterFit using `CounterFit --port `, replacing `` with the port you want to use. + +1. You will need to launch a new VS Code terminal by selecting the **Create a new integrated terminal** button. This is because the CounterFit app is running in the current terminal. + + ![VS Code Create a new integrated terminal button](../../../images/vscode-new-terminal.png) + +1. In this new terminal, run the `app.py` file as before. You will see the status of CounterFit change to **Connected** and the LED light up. + + ![Counter Fit showing as connected](../../../images/counterfit-connected.png) + +> ๐Ÿ’ You can find this code in the [code/virtual-device](code/virtual-device) folder. + +๐Ÿ˜€ Your connection to the hardware was a success! diff --git a/1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md b/1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md new file mode 100644 index 00000000..6692949b --- /dev/null +++ b/1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md @@ -0,0 +1,198 @@ +# Wio Terminal + +The [Wio Terminal from Seeed Studios](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) is an Arduino-compatible microcontroller, with WiFi and some sensors and actuators built in, as well as ports to add more sensors and actuators, using a hardware ecosystem called [Grove](https://www.seeedstudio.com/category/Grove-c-1003.html). + +![A Seeed studios Wio Terminal](../../../images/wio-terminal.png) + +## Setup + +To use your Wio Terminal, you will need to install some free software on your computer. You will also need to update the Wio Terminal firmware before you can connect it to WiFi. + +### Task + +Install the required software and update the firmware. + +1. Install Visual Studio Code (VS Code). This is the editor you will be using to write your device code in C/C++. Refer to the [VS Code documentation](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) for instructions on installing VS Code. + + > ๐Ÿ’ Another popular IDE for Arduino development is the [Arduino IDE](https://www.arduino.cc/en/software). If you are already familiar with this tool, then you can use it instead of VS Code and PlatformIO, but the lessons will give instructions based off using VS Code. + +1. Install the VS Code PlatformIO extension. This is an extension for VS Code that supports programming microcontrollers in C/C++. Refer to the [PlatformIO extension documentation](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide&WT.mc_id=academic-17441-jabenn) for instructions on installing this extension in VS Code. This extension depends on the Microsoft C/C++ extension to work with C and C++ code, and the C/C++ extension is installed automatically when you install PlatformIO. + +1. Connect your Wio Terminal to your computer. The Wio Terminal has a USB-C port on the bottom, and this needs to be connected to a USB port on your computer. The Wio Terminal comes with a USB-C to USB-A cable, but if your computer only has USB-C ports then you will either need a USB-C cable, or a USB-A to USB-C adapter. + +1. Follow the instructions in the [Wio Terminal Wiki WiFi Overview documentation](https://wiki.seeedstudio.com/Wio-Terminal-Network-Overview/) to set up your Wio Terminal and update the firmware. + +## Hello world + +It is traditional when starting out with a new programming language or technology to create a 'Hello World' application - a small application that outputs something like the text `"Hello World"` to show that all the tools are correctly configured. + +The Hello World app for the Wio Terminal will ensure that you have Visual Studio code installed correctly with PlatformIO and set up for microcontroller development. + +### Create a PlatformIO project + +The first step is to create a new project using PlatformIO configured for the Wio Terminal. + +#### Task + +Create the PlatformIO project. + +1. Connect the Wio Terminal to your computer + +1. Launch VS Code + +1. You should see the PlatformIO icon on the side menu bar: + + ![The Platform IO menu option](../../../images/vscode-platformio-menu.png) + + Select this menu item, then select *PIO Home -> Open* + + ![The Platform IO open option](../../../images/vscode-platformio-home-open.png) + +1. From the welcome screen, select the **+ New Project** button + + ![The new project button](../../../images/vscode-platformio-welcome-new-button.png) + +1. Configure the project in the *Project Wizard*: + + 1. Name your project `nightlight` + + 1. From the *Board* dropdown, type in `WIO` to filter the boards, and select *Seeeduino Wio Terminal* + + 1. Leave the *Framework* as *Arduino* + + 1. Either leave the *Use default location* checkbox checked, or uncheck it and select a location for your project + + 1. Select the **Finish** button + + ![The completed project wizard](../../../images/vscode-platformio-nightlight-project-wizard.png) + + PlatformIO will download the components it needs to compile code for the Wio Terminal and create your project. This may take a few minutes. + +### Investigate the PlatformIO project + +The VS Code explorer will show a number of files and folders created by the PlatformIO wizard. + +#### Folders + +* `.pio` - this folder contains temporary data needed by PlatformIO such as libraries or compiled code. It is recreated automatically if deleted, and you don't need to add this to source code control if you are sharing your project on sites such as GitHub. +* `.vscode` - this folder contains configuration used by PlatformIO and VS Code. It is recreated automatically if deleted, and you don't need to add this to source code control if you are sharing your project on sites such as GitHub. +* `include` - this folder is for external header files needed when adding additional libraries to your code. You won't be using this folder in any of these lessons. +* `lib` - this folder is for external libraries that you want to call from your code. You won't be using this folder in any of these lessons. +* `src` - this folder contains the main source code for your application. Initially it will contain a single file - `main.cpp`. +* `test` - this folder is where you would put any unit tests for your code + +#### Files + +* `main.cpp` - this file in the `src` folder contains the entry point for your application. If you open the file, you will see the following: + + ```cpp + #include + + void setup() { + // put your setup code here, to run once: + } + + void loop() { + // put your main code here, to run repeatedly: + } + ``` + + When the device starts up, the Arduino framework will run the `setup` function once, then run the `loop` function repeatedly until the device is turned off. + +* `.gitignore` - this file lists the files an directories to be ignored when adding your code to git source code control, such as uploading to a repository on GitHub. + +* `platformio.ini` - this file contains configuration for your device and app. If you open this file, you will see the following: + + ```ini + [env:seeed_wio_terminal] + platform = atmelsam + board = seeed_wio_terminal + framework = arduino + ``` + + The `[env:seeed_wio_terminal]` section has configuration for the Wio Terminal. You can have multiple `env` sections so your code can be compiled for multiple boards. + + The other values match the configuration from the project wizard: + + * `platform = atmelsam` defines the hardware that the Wio Terminal uses (an ATSAMD51-based microcontroller) + * `board = seeed_wio_terminal` defines the type of microcontroller board (the Wio Terminal) + * `framework = arduino` defines that this project is using the Arduino framework. + +### Write the Hello World app + +You're now ready to write the Hello World app. + +#### Task + +Write the Hello World app. + +1. Open the `main.cpp` file in VS Code + +1. Change the code to match the following: + + ```cpp + #include + + void setup() + { + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + } + + void loop() + { + Serial.println("Hello World"); + delay(5000); + } + ``` + + The `setup` function initializes a connection to the serial port - in this case the USB port that is used to connect the Wio Terminal to your computer. The parameter `9600` is the [baud rate](https://wikipedia.org/wiki/Symbol_rate) (also known as Symbol rate), or speed that data will be sent over the serial port in bits per second. This setting means 9,600 bits (0s and 1s) of data are sent each second. It then waits for the serial port to be ready. + + The `loop` function sends the line `Hello World!` to the serial port, so the characters of `Hello World!` along with a new line character. It then sleeps for 5,000 milliseconds, or 5 seconds. After the `loop` ends, it is run again, and again, and so on all the time the microcontroller is powered on. + +1. Build and upload the code to the Wio Terminal + + 1. Open the VS Code command palette + + 1. Type `PlatformIO Upload` to search for the upload option, and select *PlatformIO: Upload* + + ![The PlatformIO upload option in the command palette](../../../images/vscode-platformio-upload-command-palette.png) + + PlatformIO will automatically build the code if needed before uploading. + + 1. The code will be compiled, and uploaded to the Wio Terminal + + > ๐Ÿ’ If you are using macOS you will see a notification about a *DISK NOT EJECTED PROPERLY*. This is because the Wio Terminal gets mounted as a drive as part of the flashing process, and it is disconnected when the compiled code is written to the device. You can ignore this notification. + + โš ๏ธ If you get errors about the upload port being unavailable, first make sure you have the Wio Terminal connected to your computer, and switched on using the switch on the left hand side of the screen. The green light on the bottom should be on. If you still get the error, pull the on/off switch down twice in quick succession to force the Wio Terminal into bootloader mode and try the upload again. + +PlatformIO has a Serial Monitor that can monitor data sent over the USB cable from the Wio Terminal. This allows you to monitor the data sent by the `Serial.println("Hello World");` command. + +1. Open the VS Code command palette + +1. Type `PlatformIO Serial` to search for the Serial Monitor option, and select *PlatformIO: Serial Monitor* + + ![The PlatformIO Serial Monitor option in the command palette](../../../images/vscode-platformio-serial-monitor-command-palette.png) + + A new terminal will open, and the data sent over the serial port will be streamed into this terminal: + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem101 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Hello World + Hello World + ``` + + You will see `Hello World` appear every 5 seconds. + +> ๐Ÿ’ You can find this code in the [code/wio-terminal](code/wio-terminal) folder. + +๐Ÿ˜€ Your 'Hello World' program was a success! diff --git a/1-getting-started/lessons/2-deeper-dive/README.md b/1-getting-started/lessons/2-deeper-dive/README.md new file mode 100644 index 00000000..9a229974 --- /dev/null +++ b/1-getting-started/lessons/2-deeper-dive/README.md @@ -0,0 +1,269 @@ +# A deeper dive into IoT + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/3) + +## Introduction + +This lesson dives deeper into some of the concepts covered in the last lesson. + +In this lesson we'll cover: + +* [Components of an IoT application](#components-of-an-iot-application) +* [Deeper dive into microcontrollers](#deeper-dive-into-microcontrollers) +* [Deeper dive into single-board computers](#deeper-dive-into-single-board-computers) + +## Components of an IoT application + +The two components of an IoT application are the *Internet* and the *thing*. Lets look at these two components in a bit more detail. + +### The Thing + +![A Raspberry Pi 4](../../../images/raspberry-pi-4.jpg) + +***Raspberry Pi 4. Michael Henzler / [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page) / [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)*** + +The **Thing** part of IoT refers to a device that can interact with the physical world. These devices are usually small, low-priced computers, running at low speeds and using low power - for example simple microcontrollers with kilobytes of RAM (as opposed to gigabytes in a PC) running at only a few hundred megahertz (as opposed to gigahertz in a PC), but consuming sometimes so little power they can run for weeks, months or even years on batteries. + +These devices interact with the physical world, either by using sensors to gather data from their surroundings, or by controlling outputs or actuators to make physical changes. The typical example of this is a smart thermostat - a device that has a temperature sensor, a means to set a desired temperature such as a dial or touchscreen, and a connection to a heating or cooling system that can be turned on when the temperature detected is outside the desired range. The temperature sensor detects that the room is too cold and an actuator turns the heating on. + +![A diagram showing temperature and a dial as inputs to an IoT device, and control of a heater as an output](../../../images/basic-thermostat.png) + +***A simple thermostat. Temperature by Vectors Market / Microcontroller by Template / dial by Jamie Dickinson / heater by Pascal HeรŸ - all from the [Noun Project](https://thenounproject.com)*** + +There are a huge range of different things that can act as IoT devices, from dedicated hardware that senses one thing, to general purpose devices, even your smartphone! A smartphone can use sensors to detect the world around it, and actuators to interact with the world - for example using a GPS sensor to detect your location and a speaker to give you navigation instructions to a destination. + +โœ… Think of other systems you have around you that read data from a sensor and use that to make decisions. One example would be the thermostat on an oven. Can you find more? + +### The Internet + +The **Internet** side of an IoT application consists of applications that the IoT device can connect to to send and receive data, as well as other applications that can process the data from the IoT device and help make decisions on what requests to send to the IoT devices actuators. + +One typical setup would be having some kind of cloud service that the IoT device connects to, and this cloud service handles things like security, as well as receiving messages from the IoT device, and sending messages back to the device. This cloud service would then connect to other applications that can process or store sensor data, or use the sensor data with data from other systems to make decisions. + +Devices also don't always connect directly to the Internet themselves via WiFi or wired connections. Some devices use mesh networking to talk to each other over technologies such as bluetooth, connecting via a hub device that has the Internet connection. + +With the example of a smart thermostat, the thermostat would connect using home WiFi to a cloud service running in the cloud. It would send the temperature data to this cloud service, and from there it will be written to a database of some kind allowing the homeowner to check the current and past temperatures using a phone app. Another service in the cloud would know what temperature the homeowner wants, and send messages back to the IoT device via the cloud service to tell the heating system to turn on or off. + +![A diagram showing temperature and a dial as inputs to an IoT device, the IoT device with 2 way communication to the cloud, which in turn has 2 way communication to a phone, and control of a heater as an output from the IoT device](../../../images/mobile-controlled-thermostat.png) + +***An Internet connected thermostat with mobile app control. Temperature by Vectors Market / Microcontroller by Template / dial by Jamie Dickinson / heater by Pascal HeรŸ / mobile phone by Alice-vector / Cloud by Debi Alpa Nugraha - all from the [Noun Project](https://thenounproject.com)*** + +An even smarter version could use AI in the cloud with data from other sensors connected to other IoT devices such as occupancy sensors that detect what rooms are in use, as well as data such as weather and even your calendar, to make decisions on how to set the temperature in a smart fashion. For example it could turn your heating off if it reads from your calendar you are on vacation, or turn off the heating on a room by room basis depending on what rooms you use, learning from the data to be more and more accurate over time. + +![A diagram showing multiple temperature sensors and a dial as inputs to an IoT device, the IoT device with 2 way communication to the cloud, which in turn has 2 way communication to a phone, a calendar and a weather service, and control of a heater as an output from the IoT device](../../../images/smarter-thermostat.png) + +***An Internet connected thermostat using multiple room sensors, with mobile app control, as well as intelligence from weather and calendar data. Temperature by Vectors Market / Microcontroller by Template / dial by Jamie Dickinson / heater by Pascal HeรŸ / mobile phone and Calendar by Alice-vector / Cloud by Debi Alpa Nugraha / smart sensor by Andrei Yushchenko / weather by Adrien Coquet - all from the [Noun Project](https://thenounproject.com)*** + +โœ… What other data could help make an Internet connected thermostat smarter? + +### IoT on the Edge + +Although the I in IoT stands for Internet, these devices don't have to connect to the Internet. In some cases devices can connect to 'edge' devices - gateway devices that run on your local network meaning you can process data without making a call over the Internet. This can be faster when you have a lot of data or a slow Internet connection, it allows you to run offline where Internet connectivity is not possible such as on a ship or in a disaster area when responding to a humanitarian crisis, and allows you to keep data private. Some devices will contain processing code created using cloud tools, and run this locally to gather and respond to data without using an Internet connection to make a decision. + +One example of this is a smart home device such as an Apple HomePod, Amazon Alexa, or Google Home, which will listen to your voice using AI models trained in the cloud, and will 'wake up' when a certain word or phrase is spoken, and only then send your speech to the Internet for processing, keeping everything else you say private. + +โœ… Think of other scenarios where privacy is important so processing of data would be better done on the edge rather than in the cloud. As a hint - think about IoT devices with cameras or other imaging devices on them. + +### IoT Security + +With any Internet connection, security is an important consideration. There is an old joke that 'the S in IoT stands for Security' - there is no 'S' in IoT, implying it is not secure. + +IoT devices connect to a cloud service, and therefore are only as secure as that cloud service - if your cloud service allows any device to connect then malicious data can be sent, or virus attacks can take place. This can have very real world consequences as IoT devices interact and control other devices. For example, the [Stuxnet worm](https://wikipedia.org/wiki/Stuxnet) manipulated valves in centrifuges to damage them. Hackers have also taken advantage of [poor security to access baby monitors](https://www.npr.org/sections/thetwo-way/2018/06/05/617196788/s-c-mom-says-baby-monitor-was-hacked-experts-say-many-devices-are-vulnerable) and other home surveillance devices. + +> ๐Ÿ’ Sometimes IoT devices and the edge devices run on network completely isolated from the Internet to keep the data private and secure. This is know as [air-gapping](https://wikipedia.org/wiki/Air_gap_(networking)). + +## Deeper dive into microcontrollers + +In the last lesson we introduced microcontrollers. Lets now look deeper into them. + +### CPU + +The CPU is the 'brain' of the microcontroller. It is the processor that runs your code and can send data to and receive data from any connected devices. CPUs can contain one or more cores - essentially one or more CPUs that can work together to run your code. + +CPUs rely on a clock to tick many millions or billions of times a second. Each tick, or cycle, synchronizes the actions that the CPU can take. With each tick, the CPU can execute an instruction from a program, such as to retrieve data from an external device, or perform a mathematical calculation. This regular cycle allows for all actions to be completed before the next instructions is processed. + +The faster the clock cycle, the more instructions that can be processed each second, and therefore the faster the CPU. CPU speeds are measured in [Hertz (Hz)](https://wikipedia.org/wiki/Hertz), a standard unit where 1 Hz means one cycle or clock tick per second. + +> ๐ŸŽ“ CPU speeds are often given in MHz or GHz. 1MHz is 1 million Hz, 1GHz is 1 billion Hz. + +> ๐Ÿ’ CPUs execute programs using the [fetch-decode-execute cycle](https://wikipedia.org/wiki/Instruction_cycle). Every clock tick the CPU will fetch the next instruction from memory, decode it, then execute it such as using an arithmetic logic unit (ALU) to add 2 numbers. Some executions will take multiple ticks to run, so the next cycle will run at the next tick after the instruction has completed. + +![The fetch decode execute cycles showing the fetch taking an instruction from the program stored in RAM, then decoding and executing it on a CPU](../../../images/fetch-decode-execute.png) + +***CPU by Icon Lauk / ram by Atif Arshad - all from the [Noun Project](https://thenounproject.com)*** + +Microcontrollers have much lower clock speeds than desktop or laptop computers, or even most smartphones. The Wio Terminal for example has a CPU that runs at 120MHz, or 120,000,000 cycles per second. + +โœ… An average PC or Mac has a CPU with multiple cores running at multiple GigaHertz, meaning the clock ticks billions of times a second. Research the clock speed of your computer and see how many times faster it is than the Wio terminal. + +Each clock cycle draws power and generates heat. The faster the ticks, the more power consumed and more heat created. PC's have heat sinks and fans to remove heat, without which they would overheat and shut down within seconds. Microcontrollers often have neither as they run much cooler and therefore much slower. PC's run off mains power or large batteries for a few hours, microcontrollers can run for days, months, or even years off small batteries. Microcontrollers can also have cores that run at different speeds, switching to slower lower power cores when the demand on the CPU is low to reduce power consumption. + +> ๐Ÿ’ Some PCs and Macs are adopting the same mix of fast high power cores and slower low power cores, switching to save battery. For example the M1 chip in the latest Apple laptops can switch between 4 performance cores and 4 efficiency cores to optimize battery life or speed depending on the task being run. + +โœ… Do a little research: Read up on CPUs on the [Wikipedia CPU article](https://wikipedia.org/wiki/Central_processing_unit) + +#### Task + +Investigate the Wio Terminal. + +If you are using a Wio Terminal for these lessons, see if you can find the CPU. Find the *Hardware Overview* section of the [Wio Terminal product page](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) for a picture of the internals, and see if you can see the CPU through the clear plastic window on the back. + +### Memory + +Microcontrollers usually have two types of memory - program memory and random-access memory (RAM). + +Program memory is non-volatile, which means whatever is written to it stays when there is no power to the device. This is the memory that stores your program code. + +RAM is the memory used by the program to run, containing variables allocated by your program and data gathered from peripherals. RAM is volatile, when the power goes out the contents is lost, effectively resetting your program. + +> ๐ŸŽ“ Program memory stores your code and stays when there is no power. + +> ๐ŸŽ“ RAM is used to run your program, and is reset when there is no power + +Like with the CPU, the memory on a microcontroller is orders of magnitude smaller than a PC or Mac. A typical PC might have 8 Gigabytes (GB) of RAM, or 8,000,0000,000 bytes, with each byte enough space to store a single letter or a number from 0-255. A microcontroller would have only Kilobytes (KB) of RAM, with a kilobyte being 1,000 bytes. The Wio terminal mentioned above has 192KB of RAM, or 192,000 bytes - more than 40,000 times less than an average PC! + +The diagram below shows the relative size difference between 192KB and 8GB - the small dot in the center represents 192KB. + +![A comparison between 192KB and 8GB - more than 40,000 times larger](../../../images/ram-comparison.png) + +Program storage is also smaller than a PC. A typical PC might have a 500GB hard drive for program storage, whereas a microcontroller might have only kilobytes or maybe a few megabytes (MB) of storage (1MB is 1,000KB, or 1,000,000 bytes). The Wio terminal has 4MB of program storage. + +โœ… Do a little research: How much RAM and storage does the computer you are using to read this have? How does this compare to a microcontroller? + +### Input/Output + +Microcontrollers need input and output (I/O) connections to read data from sensors and send control signals to actuators. They usually contain a number of general-purpose input/output (GPIO) pins. These pins can be configured in software to be input (that is they receive a signal), or output (they send a signal). + +๐Ÿง โฌ…๏ธ Input pins are used to read values from sensors + +๐Ÿง โžก๏ธ Output pins send instructions to actuators + +โœ… You'll learn more about this in a subsequent lesson. + +#### Task + +Investigate the Wio Terminal. + +If you are using a Wio Terminal for these lessons, find the GPIO pins. Find the *Pinout diagram* section of the [Wio Terminal product page](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) to see which pins are which. The Wio Terminal comes with a sticker you can mount on the back with pin numbers, so add this now if you haven't already. + +### Physical size + +Microcontrollers are typically small in size, with the smallest, a [Freescale Kinetis KL03 MCU is small enough to fit in the dimple of a golf ball](https://www.edn.com/tiny-arm-cortex-m0-based-mcu-shrinks-package/). Just the CPU in a PC can measure 40mm x 40mm, and that's not including the heat sinks and fans needed to ensure the CPU can run for more than a few seconds without overheating, substantially larger than a complete microcontroller. The Wio terminal developer kit with a microcontroller, case, screen and a range of connections and components isn't much bigger than a bare Intel i9 CPU, and substantially smaller than the CPU with a heat sink and fan! + +| Device | Size | +| ------------------------------- | --------------------- | +| Freescale Kinetis KL03 | 1.6mm x 2mm x 1mm | +| Wio terminal | 72mm x 57mm x 12mm | +| Intel i9 CPU, Heat sink and fan | 136mm x 145mm x 103mm | + +### Frameworks and operating systems + +Due to their low speed and memory size, microcontrollers don't run an operating system (OS) in the desktop sense of the word. The operating system that makes your computer run (Windows, Linux or macOS) needs a lot of memory and processing power to run tasks that are completely unnecessary for a microcontroller. Remember that microcontrollers are usually programmed to perform one or more very specific tasks, unlike a general purpose computer like a PC or Mac that needs to support a user interface, play music or movies, provide tools to write documents or code, play games, or browse the Internet. + +To program a microcontroller without an OS you do need some tooling to allow you to build your code in a way that the microcontroller can run, using APIs that can talk to any peripherals. Each microcontroller is different, so manufacturers normally support standard frameworks which allow you to follow a standard 'recipe' to build your code and have it run on any microcontroller that supports that framework. + +You can program microcontrollers using an OS - often referred to as a real-time operating system (RTOS), as these are designed to handle sending data to and from peripherals in real time. These operating systems are very lightweight and provide features such as: + +* Multi-threading, allowing your code to run more than one block of code at the same time, either on multiple cores or by taking turns on one core +* Networking to allow communicating over the Internet securely +* Graphical user interface (GUI) components for building user interfaces (UI) on devices that have screens. + +โœ… Read up on some different RTOSes: [Azure RTOS](https://azure.microsoft.com/services/rtos/?WT.mc_id=academic-17441-jabenn), [FreeRTOS](https://www.freertos.org), [Zephyr](https://www.zephyrproject.org) + +#### Arduino + +![The Arduino logo](../../../images/arduino-logo.svg) + +[Arduino](https://www.arduino.cc) is probably the most popular microcontroller framework, especially among students, hobbyists and makers. Arduino is an open source electronics platform combining software and hardware. You can buy and Arduino compatible boards from Arduino themselves, or from other manufacturers, then code using the Arduino framework. + +Arduino boards are coded in C or C++. Using C/C++ allows your code to be compiled very small and run fast, something needed on a constrained device such as a microcontroller. The core of an Arduino application is referred to as a sketch, and is C/C++ code with 2 functions - `setup` and `loop`. When the board starts up, the Arduino framework code will run the `setup` function once, then it will run the `loop` function again and again, running it continuously until the power is powered off. + +You would write your setup code in the `setup` function, such as connecting to WiFi and cloud services or initializing pins for input and output. Your loop code would then contain processing code, such as reading from a sensor and sending the value to the cloud. You would normally include a delay in each loop, for example if you only want sensor data to be sent every 10 seconds you would add a delay of 10 seconds at the end of the loop so the microcontroller can sleep, saving power, then run the loop again when needed 10 seconds later. + +![An arduino sketch running setup first, then running loop repeatedly](../../../images/arduino-sketch.png) + +โœ… This program architecture is know as an *event loop* or *message loop*. Many applications use this under the hood, and is the standard for most desktop applications that run on OSes like Windows, macOS or Linux. The `loop` listens for messages from user interface components such as buttons, or devices like the keyboard, and responds to them. You can read more in this [article on the event loop](https://wikipedia.org/wiki/Event_loop). + +Arduino provides standard libraries for interacting with microcontrollers and the I/O pins, with different implementations under the hood to run on different microcontrollers. For example, the [`delay` function](https://www.arduino.cc/reference/en/language/functions/time/delay/) will pause the program for a given period of time, the [`digitalRead` function](https://www.arduino.cc/reference/en/language/functions/digital-io/digitalread/) will read a value of `HIGH` or `LOW` from the given pin, regardless of which board the code is run on. These standard libraries mean that Arduino code written for one board can be recompiled for any other Arduino board and will run, assuming that the pins are the same and the boards support the same features. + +There is a large ecosystem of third-party Arduino libraries that allow you to add extra features to your Arduino projects, such as using sensors and actuators, or connecting to cloud IoT services. + +##### Task + +Investigate the Wio Terminal. + +If you are using a Wio Terminal for these lessons, re-read the code you wrote in the last lesson. Find the `setup` and `loop` function. Monitor the serial output to see the loop function being called repeatedly. Try adding code to the `setup` function to write to the serial port and see this code is only called once each time you reboot. Try rebooting your device with the power switch on the side to see this called each time the device reboots. + +## Deeper dive into single-board computers + +In the last lesson we introduced single-board computers. Lets now look deeper into them. + +### Raspberry Pi + +![The Raspberry Pi logo](../../../images/raspberry-pi-logo.png) + +The [Raspberry Pi Foundation](https://www.raspberrypi.org) is a charity from the UK founded in 2009 to promote the study of computer science, especially at school level. As part of this mission they developed a single-board computer, called the Raspberry Pi. Raspberry Pis are currently available in 3 variants - a full size version, the smaller Pi Zero, and an compute module that can be built into your final IoT device. + +![A Raspberry Pi 4](../../../images/raspberry-pi-4.jpg) + +***Raspberry Pi 4. Michael Henzler / [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page) / [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)*** + +The latest iteration of the full size Raspberry Pi is the Raspberry Pi 4B. This has a quad-core (4 core) CPU running at 1.5GHz, 2, 4, or 8GB of RAM, gigabit ethernet, WiFi, 2 HDMI ports supporting 4k screens, an audio and composite video output port, USB ports (2 USB 2.0, 2 USB 3.0), 40 GPIO pins, a camera connector for a Raspberry Pi camera module, and an SD card slot. All this on a board that is 88mm x 58mm x 19.5mm and is powered by a 3A USB-C power supply. These start at US$35, much cheaper than a PC or Mac. + +> ๐Ÿ’ There is also a Pi400 all in one computer with a Pi4 built into a keyboard. + +![A Raspberry Pi Zero](../../../images/raspberry-pi-zero.jpg) + +The Pi Zero is much smaller, with lower power. It has a single core 1GHz CPU, 512MB of RAM, WiFi (in the Zero W model), a single HDMI port, a micro-USB port, 40 GPIO pins, a camera connector for a Raspberry Pi camera module, and an SD card slot. It measures 65mm x 30mm x 5mm, and draws very little power. The Zero is US$5, with the W version with WiFi US$10. + +> ๐ŸŽ“ The CPUs in both of these are ARM processors, as opposed to the Intel/AMD x86 or x64 processors you find in most PCs and Macs. These are similar to the CPUs you find in some microcontrollers, as well as nearly all mobile phones, the Microsoft Surface X, and the new Apple Silicon based Apple Macs. + +All variants of the Raspberry Pi run a version of Debian Linux called Raspberry Pi OS. This is available as a lite version with no desktop, which is perfect for 'headless' projects where you don't need a screen, or a full version with a full desktop environment, with web browser, office applications, coding tools and games. As the OS is a version of Debian Linux, you can install any application or tool that runs on Debian and is built for the ARM processor inside the Pi. + +#### Task + +Investigate the Raspberry Pi. + +If you are using a Raspberry Pi for these lessons, read up about the different hardware components on the board. + +* You can find details on the processors used on the [Raspberry Pi hardware documentation page](https://www.raspberrypi.org/documentation/hardware/raspberrypi/). Read up on the processor used in the Pi you are using. +* Locate the GPIO pins. Read more about them on the [Raspberry Pi GPIO documentation](https://www.raspberrypi.org/documentation/hardware/raspberrypi/gpio/README.md). Use the [GPIO Pin Usage guide](https://www.raspberrypi.org/documentation/usage/gpio/README.md) to identify the different pins on your Pi. + +### Programming single-board computers + +Single-board computers are full computers, running a full OS. This means there is a wide range of programming languages, frameworks and tools you can use to code them, unlike microcontrollers which rely on support for the board in frameworks like Arduino. Most programming languages have libraries that can access the GPIO pins to send and receive data from sensors and actuators. + +โœ… What programming languages are you familiar with? Are they supported on Linux? + +The most common programming language for building IoT applications on a Raspberry Pi is Python. There is a huge ecosystem of hardware designed for the Pi, and nearly all of these include the relevant code needed to use them as Python libraries. Some of these ecosystems are based off 'hats' - so called because they sit on top of the Pi like a hat and connect with a large socket to the 40 GPIO pins. These hats provide additional capabilities, such as screens, sensors, remote controlled cars, or adapters to allow you to plug in sensors with standardized cables + +### Use of single-board computers in professional IoT deployments + +Single-board computers are used for professional IoT deployments, not just as developer kits. They can provide a powerful way to control hardware and run complex tasks such as running machine learning models. For example, there is a [Raspberry Pi 4 compute module](https://www.raspberrypi.org/blog/raspberry-pi-compute-module-4/) that provides all the power of a Raspberry Pi 4 but in a compact and cheaper form factor without most of the ports, designed to be installed into custom hardware. + +--- + +## ๐Ÿš€ Challenge + +The challenge in the last lesson was to list as many IoT devices as you can that are in your home, school or workplace. For every device in this list, do you think they are built around microcontrollers or single-board computers, or even a mixture of both? + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/4) + +## Review & Self Study + +* Read the [Arduino getting started guide](https://www.arduino.cc/en/Guide/Introduction) to understand more about the Arduino platform. +* Read the [introduction to the Raspberry Pi 4](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) to learn more about Raspberry Pis. + +โœ… Use these guides, along with the costs shown by following the links in the [hardware guide](../../hardware.md) to decide on what hardware platform you want to use, or if you would rather use a virtual device. + +## Assignment + +[Compare and contrast microcontrollers and single-board computers](assignment.md) diff --git a/1-getting-started/lessons/2-deeper-dive/assignment.md b/1-getting-started/lessons/2-deeper-dive/assignment.md new file mode 100644 index 00000000..817499b0 --- /dev/null +++ b/1-getting-started/lessons/2-deeper-dive/assignment.md @@ -0,0 +1,12 @@ +# Compare and contrast microcontrollers and single-board computers + +## Instructions + +This lesson covered microcontrollers and single-board computers. Create a table comparing and contrasting them, and note at least 2 reasons why you would use a microcontroller over a single-board computer, and at least 2 reasons why you would use a single-board computer over a microcontroller. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Create a table comparing microcontrollers to single-board computers | Created a list with multiple items correctly comparing and contrasting | Created a list with only a couple of items | Was only able to come up with one item, or no items to compare and contrast | +| Reasons for using one over the other | Was able to provide 2 or more reasons for microcontrollers, and 2 or more for single-board computers | Was only able to provide 1-2 reasons for a microcontroller, and 1-2 reasons for a single-board computer | Was unable to provide 1 or more reasons for a microcontroller or for a single-board computer | diff --git a/1-getting-started/lessons/3-sensors-and-actuators/README.md b/1-getting-started/lessons/3-sensors-and-actuators/README.md new file mode 100644 index 00000000..c1851abb --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/README.md @@ -0,0 +1,224 @@ +# Interact with the physical world with sensors and actuators + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/5) + +## Introduction + +This lesson introduces two of the important concepts for your IoT device - sensors and actuators. You will also get hands on with them both, adding a light sensor to your IoT project, then adding an LED controlled by light levels, effectively building a nightlight. + +In this lesson we'll cover: + +* [What are sensors?](#what-are-sensors) +* [Use a sensor](#use-a-sensor) +* [Sensor types](#sensor-types) +* [What are actuators?](#what-are-actuators) +* [Use an actuator](#use-an-actuator) +* [Actuator types](#actuator-types) + +## What are sensors? + +Sensors are hardware devices that sense the physical world - that is they measure one or more properties around them and send the information to an IoT device. Sensors cover a huge range of devices as there are so many things that can be measured, from natural properties such as air temperature to physical interactions such as movement. + +Some common sensors include: + +* Temperature sensors - these sense the air temperature, or the temperature of what they are immersed in. For hobbyists and developer, these are often combined with air pressure and humidity in a single sensor. +* Buttons - they sense when they have been pressed +* Light sensors - these detect light levels, and can be for specific colors, UV light, IR light, or general visible light +* Cameras - these sense a visual representation of the world by taking a photograph or streaming video +* Accelerometers - these sense movement in multiple directions +* Microphones - these sense sound, either general sound levels, or directional sound + +โœ… Do some research. What sensors does your phone have? + +All sensors have one thing in common - the convert whatever they sense into an electrical signal that can be interpreted by an IoT device. How this electrical signal is interpreted depends on the sensor, as well as the communication protocol used to communicate with the IoT device. + +## Use a sensor + +Follow the relevant guide below to add a sensor to your IoT device: + +* [Arduino - Wio Terminal](wio-terminal-sensor.md) +* [Single-board computer - Raspberry Pi](pi-sensor.md) +* [Single-board computer - Virtual device](virtual-device-sensor.md) + +## Sensor types + +Sensors are either analog or digital. + +### Analog sensors + +Some of the most basic sensors are analog sensors. These sensors receive a voltage from the IoT device, the sensor components adjust this voltage, and the voltage that is returned from the sensor is measured to give the sensor value. + +> ๐ŸŽ“ Voltage is a measure of how mush push there is to move electricity from one place to another, such as from a positive terminal of a battery to the negative terminal. For example, a standard AA battery is 1.5V (V is the symbol for volts), and can push electricity with the force of 1.5V from it's positive terminal to its negative terminal. Different electrical hardware requires different voltages to work, for example an LED can light with between 2-3V, but a 100W filament lightbulb would need 240V. You can read more about voltage on the [Voltage page on Wikipedia](https://wikipedia.org/wiki/Voltage). + +One example of this is a potentiometer. This is a dial that you can rotate between two positions and the sensor measures the rotation. + +![A potentiometer set to a mid point being sent 5 volts returning 3.8 volts](../../../images/potentiometer.png) + +***A potentiometer. Microcontroller by Template / dial by Jamie Dickinson - all from the [Noun Project](https://thenounproject.com)*** + +The IoT device will send an electrical signal to the potentiometer at a voltage, such as 5 volts (5V). As the potentiometer is adjusted it changes the voltage that comes out the other side. Imagine you have a potentiometer labelled as a dial that goes from 0 to [11](https://wikipedia.org/wiki/Up_to_eleven), such as a volume knob on an amplifier. When the potentiometer is in the full off position (0) then 0v (0 volts) will come out. When it is in the full on position (11), 5V (5 volts) will come out. + +> ๐ŸŽ“ This is an oversimplification, and you can read more on potentiometers and variable resistors on the [potentiometer Wikipedia page](https://wikipedia.org/wiki/Potentiometer). + +The voltage that comes out the sensor is then read by the IoT device, and the device can respond to it. Depending on the sensor, this voltage can be an arbitrary value, or can map to a standard unit. For example an analog temperature sensor based on a [thermistor](https://wikipedia.org/wiki/Thermistor) changes it's resistance depending on the temperature. The output voltage can then be converted to a temperature in Kelvin, and correspondingly into ยฐC or ยฐF, by calculations in code. + +โœ… What do you think happens if the sensor returns a higher voltage than was sent (for example coming from an external power supply)? โ›”๏ธ DO NOT test this out. + +#### Analog to digital conversion + +IoT devices are digital - they can't work with analog values, they only work with 0s and 1s. This means that analog sensor values need to be converted to a digital signal before they can be processed. Many IoT devices have analog-to-digital converters (ADCs) to convert analog inputs to digital representations of their value. Sensors can also work with ADCs via a connector board. For example, in the Seeed Grove ecosystem with a Raspberry Pi, analog sensors connect to specific ports on a 'hat' that sits on the Pi connected to the Pis GPIO pins, and this hat has an ADC to convert the voltage into a digital signal that can be sent of the Pi's GPIO pins. + +Imagine you have an analog light sensor connected to an IoT device that uses 3.3V, and is returning a value of 1v. This 1v doesn't mean anything in the digital world, so needs to be converted. The voltage will be converted to an analog value using a scale depending on the device and sensor. One example is the Seeed Grove light sensor which outputs values from 0 to 1,023. For this sensor running at 3.3V, a 1v output would be a value of 300. An IoT device can't handle 300 as an analog value, so the value would be converted to `0000000100101100`, the binary representation of 300 by the Grove hat. This would then be processed by the IoT device. + +โœ… If you don't know binary then do a small amount of research to learn how numbers are represented by 0s and 1s. The [BBC Bitesize introduction to binary lesson](https://www.bbc.co.uk/bitesize/guides/zwsbwmn/revision/1) is a great place to start. + +From a coding perspective, all this is usually handled by libraries that come with the sensors, so you don't need to worry about this conversion yourself. For the Grove light sensor you would use the Python library and call the `light` property, or use the Arduino library and call `analogRead` to get a value of 300. + +### Digital sensors + +Digital sensors, like analog sensors, detect the world around them using changes in electrical voltage. The difference is they output a digital signal, either by only measuring two states, or by using a built-in ADC. Digital sensors are becoming more and more common to avoid the need to use an ADC either in a connector board or on the IoT device itself. + +The simplest digital sensor is a button or switch. This is a sensor with two states, on or off. + +![A button is sent 5 volts. When not pressed it returns 0 volts, when pressed it returns 5 volts](../../../images/button.png) + +***A button. Microcontroller by Template / Button by Dan Hetteix - all from the [Noun Project](https://thenounproject.com)*** + +Pins on IoT devices such as GPIO pins can measure this signal directly as a 0 or 1. If the voltage sent is the same as the voltage returned, the value read is 1, otherwise the value read is 0. There is no need to convert the signal, it can only be 1 or 0. + +> ๐Ÿ’ Voltages are never exact especially as the components in a sensor will have some resistance, so there is usually a tolerance. For example the GPIO pins on a Raspberry Pi work on 3.3V, and read a return signal above 1.8v as a 1, below 1.8v as 0. + +* 3.3V goes into the button. The button is off so 0v comes out, giving a value of 0 +* 3.3V goes into the button. The button is on so 3.3V comes out, giving a value of 1 + +More advanced digital sensors read analog values, then convert them using on-board ADCs to digital signals. For example a digital temperature sensor will still use a thermocouple in the same way as an analog sensor, and will still measure the change in voltage caused by the resistance of the thermocouple at the current temperature. Instead of returning an analog value and relying on the device or connector board to convert to a digital signal, an ADC built into the sensor will convert the value and send it as a series of 0s and 1s to the IoT device. These 0s and 1s are sent in the same way as the digital signal for a button with 1 being full voltage and 0 being 0v. + +![A digital temperature sensor converting an analog reading to binary data with 0 as 0 volts and 1 as 5 volts before sending it to an IoT device](../../../images/temperature-as-digital.png) + +***A digital temperature sensor. Temperature by Vectors Market / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +Sending digital data allows sensors to become more complex and send more detailed data, even encrypted data for secure sensors. One example is a camera. This is a sensor that captures an image and sends it as digital data containing that image, usually in a compressed format such as JPEG, to be read by the IoT device. It can even stream video by capturing images and sending either the complete image frame by frame, or a compressed video stream. + +## What are actuators? + +Actuators are the opposite of sensors - they convert an electrical signal from your IoT device into an interaction with the physical world such as emitting light or sound, or moving a motor. + +Some common actuators include: + +* LED - these emit light when turned on +* Speaker - these emil sound based on the signal sent to them, from a basic buzzer to an audio speaker that can play music +* Stepper motor - these convert a signal into a defined amount of rotation, such as turning a dial 90ยฐ +* Relay - these are switches that can be turned on or off by an electrical signal. They allow a small voltage from an IoT device to turn on larger voltages. +* Screens - these are more complex actuators and show information on a multi-segment display. Screens vary from simple LED displays to high-resolution video monitors. + +โœ… Do some research. What actuators does your phone have? + +## Use an actuator + +Follow the relevant guide below to add an actuator to your IoT device, controlled by the sensor, to build an IoT nightlight. It will gather light levels from the light sensor, and use an actuator in the form of an LED to emit light when the detected light level is too low. + +![A flow chart of the assignment showing light levels being read and checked, and the LED begin controlled](../../../images/assignment-1-flow.png) + +***A flow chart of the assignment showing light levels being read and checked, and the LED begin controlled. ldr by Eucalyp / LED by abderraouf omara - all from the [Noun Project](https://thenounproject.com)*** + +* [Arduino - Wio Terminal](wio-terminal-actuator.md) +* [Single-board computer - Raspberry Pi](pi-actuator.md) +* [Single-board computer - Virtual device](virtual-device-actuator.md) + +## Actuator types + +Like sensors, actuators are either analog or digital. + +### Analog actuators + +Analog actuators take an analog signal and convert it into some kind of interaction, where the interaction changes based off the voltage supplied. + +One example is a dimmable light, such as the ones you might have in your house. The amount of voltage supplied to the light determines how bright it is. + +![A light dimmed at a low voltage and brighter at a higher voltage](../../../images/dimmable-light.png) + +***A light controlled by the voltage output by an IoT device. Idea by Pause08 / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +Like with sensors, the actual IoT device works on digital signals, not analog. This means to send an analog signal, the IoT device needs a digital to analog converter (DAC), either on the IoT device directly, or on a connector board. This will convert the 0s and 1s from the IoT device to an analog voltage that the actuator can use. + +โœ… What do you think happens if the IoT device sends a higher voltage than the actuator can handle? โ›”๏ธ DO NOT test this out. + +#### Pulse-Width Modulation + +Another option for converting digital signals from an IoT device to an analog signal is pulse-width modulation. This involves sending lots of short digital pulses that act as if it was an analog signal. + +For example, you can use PWM to control the speed of a motor. + +imagine you are controlling a motor with a 5V supply. You send a short pulse to your motor, switching the voltage to high (5V) for two hundredths of a second (0.02s). In that time your motor can rotate one tenth of a rotation, or 36ยฐ. The signal then pauses for two hundredths of a second (0.02s), sending a low signal (0v). Each cycle of on then off lasts 0.04s. The cycle then repeats. + +![Pule width modulation rotation of a motor at 150 RPM](../../../images/pwm-motor-150rpm.png) + +***PWM rotation of a motor at 150RPM. motor by Bakunetsu Kaito / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +This means in one second you have 25 5V pulses of 0.02s that rotate the motor, each followed by 0.02s pause of 0v not rotating the motor. Each pulse rotates the motor one tenth of a rotation, meaning the motor completes 2.5 rotations per second. You've used a digital signal to rotate the motor at 2.5 rotations per second, or 150 ([revolutions per minute](https://wikipedia.org/wiki/Revolutions_per_minute), a non-standard measure of rotational velocity). + +```output +25 pulses per second x 0.1 rotations per pulse = 2.5 rotations per second +2.5 rotations per second x 60 seconds in a minute = 150rpm +``` + +> ๐ŸŽ“ When a PWM signal is on for half the time, and off for half it is referred to as a [50% duty cycle](https://wikipedia.org/wiki/Duty_cycle). Duty cycles are measured as the percentage time the signal is in the on state compared to the off state. + +![Pule width modulation rotation of a motor at 75 RPM](../../../images/pwm-motor-75rpm.png) + +***PWM rotation of a motor at 75RPM. motor by Bakunetsu Kaito / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +You can change the motor speed by changing the size of the pulses. For example, with the same motor you can keep the same cycle time of 0.04s, with the on pulse halved to 0.01s, and the off pulse increasing to 0.03s. You have the same number of pulses per second (25), but each on pulse is half the length. A half length pulse only turns the motor one twentieth of a rotation, and at 25 pulses a second will complete 1.25 rotations per second, or 75rpm. By changing the pulse speed of a digital signal you've halved the speed of an analog motor. + +```output +25 pulses per second x 0.05 rotations per pulse = 1.25 rotations per second +1.25 rotations per second x 60 seconds in a minute = 75rpm +``` + +โœ… How would you keep the motor rotation smooth, especially at low speeds? Would you use a small number long pulses with long pauses, or lots of very short pulses with very short pauses? + +> ๐Ÿ’ Some sensors also use PWM to convert analog signals to digital signals. + +> ๐ŸŽ“ You can read more on pulse-width modulation on the [pulse-width modulation page on Wikipedia](https://wikipedia.org/wiki/Pulse-width_modulation). + +### Digital actuators + +Digital actuators, like digital sensors, either have two states controlled by a high or low voltage, or have a DAC built in so can convert a digital signal to an analog one. + +One simple digital actuator is an LED. When a device sends a digital signal of 1, a high voltage is sent that lights the LED. When a digital signal of 0 is sent, the voltage drops to 0v and the LED turns off. + +![A LED is off at 0 volts and on at 5V](../../../images/led.png) + +***An LED turning on and off depending on voltage. LED by abderraouf omara / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +โœ… What other simple 2-state actuators can you think of? One example is a solenoid, which is an electromagnet that can be activated to do things like move a door bolt locking/unlocking a door. + +More advanced digital actuators, such as screens require the digital data to be sent in certain formats. They usually come with libraries that make it easier to send the correct data to control them. + +--- + +## ๐Ÿš€ Challenge + +The challenge in the last two lessons was to list as many IoT devices as you can that are in your home, school or workplace and decide if they are built around microcontrollers or single-board computers, or even a mixture of both. + +For every device you listed, what sensors and actuators are they connected to? What is the purpose of each sensor and actuator connected to these devices. + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/6) + +## Review & Self Study + +* Read up on electricity and circuits on [ThingLearn](http://www.thinglearn.com/essentials/). +* Read about the different types of temperature sensors on the [Seeed Studios Temperature Sensors guide](https://www.seeedstudio.com/blog/2019/10/14/temperature-sensors-for-arduino-projects/) +* Read about LEDs on the [Wikipedia LED page](https://wikipedia.org/wiki/Light-emitting_diode) + +## Assignment + +[Research sensors and actuators](assignment.md) diff --git a/1-getting-started/lessons/3-sensors-and-actuators/assignment.md b/1-getting-started/lessons/3-sensors-and-actuators/assignment.md new file mode 100644 index 00000000..3aacf6cf --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/assignment.md @@ -0,0 +1,17 @@ +# Research sensors and actuators + +## Instructions + +This lesson covered sensors and actuators. Research and describe one sensor and one actuator that can be used with an IoT dev kit, including: + +* What it does +* The electronics/hardware used inside +* Is it analog or digital +* What the units and range of inputs or measurements is + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Describe a sensor | Described a sensor including with details for all 4 sections listed above. | Described a sensor, but was only able to provide 2-3 of the sections above | Described a sensor, but was only able to provide 1 of the sections above | +| Describe an actuator | Described an actuator including with details for all 4 sections listed above. | Described an actuator, but was only able to provide 2-3 of the sections above | Described an actuator, but was only able to provide 1 of the sections above | diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/pi/nightlight/app.py b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/pi/nightlight/app.py new file mode 100644 index 00000000..beb55666 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/pi/nightlight/app.py @@ -0,0 +1,17 @@ +import time +from grove.grove_light_sensor_v1_2 import GroveLightSensor +from grove.grove_led import GroveLed + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +while True: + light = light_sensor.light + print('Light level:', light) + + if light < 200: + led.on() + else: + led.off() + + time.sleep(1) \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/virtual-device/nightlight/app.py b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/virtual-device/nightlight/app.py new file mode 100644 index 00000000..b7bf75b6 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/virtual-device/nightlight/app.py @@ -0,0 +1,20 @@ +import time +from counterfit_connection import CounterFitConnection +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor +from counterfit_shims_grove.grove_led import GroveLed + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +while True: + light = light_sensor.light + print('Light level:', light) + + if light < 200: + led.on() + else: + led.off() + + time.sleep(1) \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/.gitignore b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/.vscode/extensions.json b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/app.py b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/app.py new file mode 100644 index 00000000..f33ca45e --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/app.py @@ -0,0 +1,20 @@ +from counterfit_shims_grove.counterfit_connection import CounterFitConnection +import time +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor +from counterfit_shims_grove.grove_led import GroveLed + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +while True: + light = light_sensor.light + print('Light level:', light) + + if light < 200: + led.on() + else: + led.off() + + time.sleep(1) \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/include/README b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/lib/README b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/platformio.ini b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/platformio.ini new file mode 100644 index 00000000..4e03e890 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/src/main.cpp b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/src/main.cpp new file mode 100644 index 00000000..ae2299f0 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/src/main.cpp @@ -0,0 +1,32 @@ +#include + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + pinMode(WIO_LIGHT, INPUT); + pinMode(D0, OUTPUT); +} + +void loop() +{ + int light = analogRead(WIO_LIGHT); + Serial.print("Light value: "); + Serial.println(light); + + if (light < 200) + { + digitalWrite(D0, HIGH); + } + else + { + digitalWrite(D0, LOW); + } + + delay(1000); +} \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/test/README b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal/nightlight/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/pi/nightlight/app.py b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/pi/nightlight/app.py new file mode 100644 index 00000000..54f58874 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/pi/nightlight/app.py @@ -0,0 +1,10 @@ +import time +from grove.grove_light_sensor_v1_2 import GroveLightSensor + +light_sensor = GroveLightSensor(0) + +while True: + light = light_sensor.light + print('Light level:', light) + + time.sleep(1) \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/virtual-device/nightlight/app.py b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/virtual-device/nightlight/app.py new file mode 100644 index 00000000..f26b3097 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/virtual-device/nightlight/app.py @@ -0,0 +1,13 @@ +import time +from counterfit_connection import CounterFitConnection +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) + +while True: + light = light_sensor.light + print('Light level:', light) + + time.sleep(1) \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/.gitignore b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/.vscode/extensions.json b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/app.py b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/app.py new file mode 100644 index 00000000..f33ca45e --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/app.py @@ -0,0 +1,20 @@ +from counterfit_shims_grove.counterfit_connection import CounterFitConnection +import time +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor +from counterfit_shims_grove.grove_led import GroveLed + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +while True: + light = light_sensor.light + print('Light level:', light) + + if light < 200: + led.on() + else: + led.off() + + time.sleep(1) \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/include/README b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/lib/README b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/platformio.ini b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/platformio.ini new file mode 100644 index 00000000..4e03e890 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/src/main.cpp b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/src/main.cpp new file mode 100644 index 00000000..95701473 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/src/main.cpp @@ -0,0 +1,22 @@ +#include + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + pinMode(WIO_LIGHT, INPUT); +} + +void loop() +{ + int light = analogRead(WIO_LIGHT); + Serial.print("Light value: "); + Serial.println(light); + + delay(1000); +} \ No newline at end of file diff --git a/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/test/README b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal/nightlight/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md b/1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md new file mode 100644 index 00000000..962aa010 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md @@ -0,0 +1,114 @@ +# Build a nightlight - Raspberry Pi + +In this part of the lesson, you will add an LED to your Raspberry Pi and use it to create a nightlight. + +## Hardware + +The nightlight now needs an actuator. + +The actuator is an **LED**, a [light-emitting diode](https://wikipedia.org/wiki/Light-emitting_diode) that emits light when current flows through it. This is a digital actuator that has 2 states, on and off. Sending a value of 1 turns the LED on, and 0 turns it off. The LED is an external Grove actuator and needs to be connected to the Grove Base hat on the Raspberry Pi. + +The nightlight logic in pseudo-code is: + +```output +Check the light level. +If the light is less than 200 + Turn the LED on +Otherwise + Turn the LED off +``` + +### Connect the LED + +The Grove LED comes as a module with a selection of LEDs, allowing you to chose the color. + +#### Task + +Connect the LED. + +![A grove LED](../../../images/grove-led.png) + +1. Pick your favorite LED and insert the legs into the two holes on the LED module. + + LEDs are light-emitting diodes, and diodes are electronic devices that can only carry current one way. This means the LED needs to be connected the right way round, otherwise it won't work. + + One of the legs of the LED is the positive pin, the other is the negative pin. The LED is not perfectly round, and is slightly flatter on one side. The side that is slightly flatter is the negative pin. When you connect the LED to the module, make sure the pin by the rounded side is connected to the socket marked **+** on the outside of the module, and the flatter side is connected to the socket closer to the middle of the module. + +1. The LED module has a spin button that allows you to control the brightness. Turn this all the way up to start with by rotating it anti-clockwise as far as it will go using a small Phillips head screwdriver. + +1. Insert one end of a Grove cable into the socket on the LED module. It will only go in one way round. + +1. With the Raspberry Pi powered off, connect the other end of the Grove cable to the digital socket marked **D5** on the Grove Base hat attached to the Pi. This socket is the second from the left, on the row of sockets next to the GPIO pins. + +![The grove LED connected to socket D5](../../../images/pi-led.png) + +## Program the nightlight + +The nightlight can now be programmed using the Grove light sensor and the Grove LED. + +### Task + +Program the nightlight. + +1. Power up the Pi and wait for it to boot + +1. Open the nightlight project in VS Code that you created in the previous part of this assignment, either running directly on the Pi or connected using the Remote SSH extension. + +1. Add the following code to the `app.py` file to connect to import a required library. This should be added to the top, below the other `import` lines. + + ```python + from grove.grove_led import GroveLed + ``` + + The `from grove.grove_led import GroveLed` statement imports the `GroveLed` from the Grove Python libraries. This library has code to interact with a Grove LED. + +1. Add the following code after the `light_sensor` declaration to create an instance of the class that manages the LED: + + ```python + led = GroveLed(5) + ``` + + The line `led = GroveLed(5)` creates an instance of the `GroveLed` class connecting to pin **D5** - the digital Grove pin that the LED is connected to. + +1. Add a check inside the `while` loop, and before the `time.sleep` to check the light levels and turn the LED on or off: + + ```python + if light < 200: + led.on() + else: + led.off() + ``` + + This code checks the `light` value. If this is less than 200 it calls the `on` method of the `GroveLed` class which sends a digital value of 1 to the LED, turning it on. If the light value is greater than or equal to 200 it calls the `off` method, sending a digital value of 0 to the LED, turning it off. + + > ๐Ÿ’ This code should be indented to the same level as the `print('Light level:', light)` line to be inside the while loop! + + > ๐Ÿ’ When sending digital values to actuators, a 0 value is 0v, and a 1 value is the max voltage for the device. For the Raspberry Pi with Grove sensors and actuators, the 1 voltage is 3.3V. + +1. From the VS Code Terminal, run the following to run your Python app: + + ```sh + python3 app.py + ``` + + You should see light values being output to the console. + + ```output + pi@raspberrypi:~/nightlight $ python3 app.py + Light level: 634 + Light level: 634 + Light level: 634 + Light level: 230 + Light level: 104 + Light level: 290 + ``` + +1. Cover and uncover the light sensor. Notice how the LED will light up if the light level is 200 or less, and turn off when the light level is greater than 200. + + > ๐Ÿ’ If the LED doesn't turn on, make sure it is connected the right way round, and the spin button is set to full on. + +![The LED connected to the Pi turning on and off as the light level changes](../../../images/pi-running-assignment-1-1.gif) + +> ๐Ÿ’ You can find this code in the [code-actuator/pi](code-actuator/pi) folder. + +๐Ÿ˜€ Your nightlight program was a success! diff --git a/1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md b/1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md new file mode 100644 index 00000000..1d35126a --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md @@ -0,0 +1,98 @@ +# Build a nightlight - Raspberry Pi + +In this part of the lesson, you will add a light sensor to your Raspberry Pi. + +## Hardware + +The sensor for this lesson is a **light sensor** that uses a [photodiode](https://wikipedia.org/wiki/Photodiode) to convert light to an electrical signal. This is an analog sensor that sends an integer value from 0 to 1,000 indicating a relative amount of light that doesn't map to any standard unit of measurement such as [lux](https://wikipedia.org/wiki/Lux). + +The light sensor is an eternal Grove sensor and needs to be connected to the Grove Base hat on the Raspberry Pi. + +### Connect the light sensor + +The Grove light sensor that is used to detect the light levels needs to be connected to the Raspberry Pi. + +#### Task + +Connect the light sensor + +![A grove light sensor](../../../images/grove-light-sensor.png) + +1. Insert one end of a Grove cable into the socket on the light sensor module. It will only go in one way round. + +1. With the Raspberry Pi powered off, connect the other end of the Grove cable to the analog socket marked **A0** on the Grove Base hat attached to the Pi. This socket is the second from the right, on the row of sockets next to the GPIO pins. + +![The grove light sensor connected to socket A0](../../../images/pi-light-sensor.png) + +## Program the light sensor + +The device can now be programmed using the Grove light sensor. + +### Task + +Program the device. + +1. Power up the Pi and wait for it to boot + +1. Open the nightlight project in VS Code that you created in the previous part of this assignment, either running directly on the Pi or connected using the Remote SSH extension. + +1. Open the `app.py` file and remove all code from it + +1. Add the following code to the `app.py` file to import some required libraries: + + ```python + import time + from grove.grove_light_sensor_v1_2 import GroveLightSensor + ``` + + The `import time` statement imports the `time` module that will be used later in this assignment. + + The `from grove.grove_light_sensor_v1_2 import GroveLightSensor` statement imports the `GroveLightSensor` from the Grove Python libraries. This library has code to interact with a Grove light sensor, and was installed globally during the Pi setup. + +1. Add the following code after the code above to create an instance of the class that manages the light sensor: + + ```python + light_sensor = GroveLightSensor(0) + ``` + + The line `light_sensor = GroveLightSensor(0)` creates an instance of the `GroveLightSensor` class connecting to pin **A0** - the analog Grove pin that the light sensor is connected to. + + > ๐Ÿ’ All the sockets have unique pin numbers. Pins 0, 2, 4, and 6 are analog pins, pins 5, 16, 18, 22, 24, and 26 are digital pins. + +1. Add an infinite loop after the code above to poll the light sensor value and print it to the console: + + ```python + while True: + light = light_sensor.light + print('Light level:', light) + ``` + + This will read the current light level on a scale of 0-1,023 using the `light` property of the `GroveLightSensor` class. This property reads the analog value from the pin. This value is then printed to the console. + +1. Add a small sleep of one second at the end of the `loop` as the light levels don't need to be checked continuously. A sleep reduces the power consumption of the device. + + ```python + time.sleep(1) + ``` + +1. From the VS Code Terminal, run the following to run your Python app: + + ```sh + python3 app.py + ``` + + You should see light values being output to the console. Cover and uncover the light sensor to see the values change: + + ```output + pi@raspberrypi:~/nightlight $ python3 app.py + Light level: 634 + Light level: 634 + Light level: 634 + Light level: 230 + Light level: 104 + Light level: 290 + ``` + +> ๐Ÿ’ You can find this code in the [code-sensor/pi](code-sensor/pi) folder. + +๐Ÿ˜€ Adding a sensor to your nightlight program was a success! diff --git a/1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md b/1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md new file mode 100644 index 00000000..403655f8 --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md @@ -0,0 +1,110 @@ +# Build a nightlight - Virtual IoT Hardware + +In this part of the lesson, you will add an LED to your virtual IoT device and use it to create a nightlight. + +## Virtual Hardware + +The nightlight needs one actuator, created in the CounterFit app. + +The actuator is an **LED**. In a physical IoT device, it would be a [light-emitting diode](https://wikipedia.org/wiki/Light-emitting_diode) that emits light when current flows through it. This is a digital actuator that has 2 states, on and off. Sending a value of 1 turns the LED on, and 0 turns it off. + +The nightlight logic in pseudo-code is: + +```output +Check the light level. +If the light is less than 200 + Turn the LED on +Otherwise + Turn the LED off +``` + +### Add the sensors to CounterFit + +To use a virtual LED, you need to add it to the CounterFit app + +#### Task + +Add the LED to the CounterFit app. + +1. Make sure the CounterFit web app is running from the previous part of this assignment. If not, start it and re-add the light sensor. + +1. Create an LED: + + 1. In the *Create actuator* box in the *Actuator* pane, drop down the *Actuator type* box and select *LED*. + + 1. Set the *Pin* to *5* + + 1. Select the **Add** button to create the LED on Pin 5 + + ![The LED settings](../../../images/counterfit-create-led.png) + + The LED will be created and appear in the actuators list. + + ![The LED created](../../../images/counterfit-led.png) + + Once the LED has been created, you can change the color using the *Color* picker. Select the **Set** button to change the color after you have selected it. + +### Program the nightlight + +The nightlight can now be programmed using the CounterFit light sensor and LED. + +#### Task + +Program the nightlight. + +1. Open the nightlight project in VS Code that you created in the previous part of this assignment. Kill and re-create the terminal to ensure it is running using the virtual environment if necessary. + +1. Open the `app.py` file + +1. Add the following code to the `app.py` file to connect to import a required library. This should be added to the top, below the other `import` lines. + + ```python + from counterfit_shims_grove.grove_led import GroveLed + ``` + + The `from counterfit_shims_grove.grove_led import GroveLed` statement imports the `GroveLed` from the CounterFit Grove shim Python libraries. This library has code to interact with an LED created in the CounterFit app. + +1. Add the following code after the `light_sensor` declaration to create an instance of the class that manages the LED: + + ```python + led = GroveLed(5) + ``` + + The line `led = GroveLed(5)` creates an instance of the `GroveLed` class connecting to pin **5** - the CounterFit Grove pin that the LED is connected to. + +1. Add a check inside the `while` loop, and before the `time.sleep` to check the light levels and turn the LED on or off: + + ```python + if light < 200: + led.on() + else: + led.off() + ``` + + This code checks the `light` value. If this is less than 200 it calls the `on` method of the `GroveLed` class which sends a digital value of 1 to the LED, turning it on. If the light value is greater than or equal to 200 it calls the `off` method, sending a digital value of 0 to the LED, turning it off. + + > ๐Ÿ’ This code should be indented to the same level as the `print('Light level:', light)` line to be inside the while loop! + +1. From the VS Code Terminal, run the following to run your Python app: + + ```sh + python3 app.py + ``` + + You should see light values being output to the console. + + ```output + (.venv) โžœ GroveTest python3 app.py + Light level: 143 + Light level: 244 + Light level: 246 + Light level: 253 + ``` + +1. Change the *Value* or the *Random* settings to vary the light level above and below 200. You will see the LED turn on and off. + +![The LED in the CounterFit app turning on and off as the light level changes](../../../images/virtual-device-running-assignment-1-1.gif) + +> ๐Ÿ’ You can find this code in the [code-actuator/virtual-device](code-actuator/virtual-device) folder. + +๐Ÿ˜€ Your nightlight program was a success! diff --git a/1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md b/1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md new file mode 100644 index 00000000..2f740bfe --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md @@ -0,0 +1,111 @@ +# Build a nightlight - Virtual IoT Hardware + +In this part of the lesson, you will add a light sensor to your virtual IoT device. + +## Virtual Hardware + +The nightlight needs one sensor, created in the CounterFit app. + +The sensor is a **light sensor**. In a physical IoT device, it would be a [photodiode](https://wikipedia.org/wiki/Photodiode) that converts light to an electrical signal. Light sensors are analog sensors that sends an integer value indicating a relative amount of light, that doesn't map to any standard unit of measurement such as [lux](https://wikipedia.org/wiki/Lux). + +### Add the sensors to CounterFit + +To use a virtual light sensor, you need to add it to the CounterFit app + +#### Task + +Add the light sensor to the CounterFit app. + +1. Make sure the CounterFit web app is running from the previous part of this assignment. If not, start it. + +1. Create a light sensor: + + 1. In the *Create sensor* box in the *Sensors* pane, drop down the *Sensor type* box and select *Light*. + + 1. Leave the *Units* set to *NoUnits* + + 1. Ensure the *Pin* is set to *0* + + 1. Select the **Add** button to create the light sensor on Pin 0 + + ![The light sensor settings](../../../images/counterfit-create-light-sensor.png) + + The light sensor will be created and appear in the sensors list. + + ![The light sensor created](../../../images/counterfit-light-sensor.png) + +## Program the light sensor + +The device can now be programmed to use the built in light sensor. + +### Task + +Program the device. + + +1. Open the nightlight project in VS Code that you created in the previous part of this assignment. Kill and re-create the terminal to ensure it is running using the virtual environment if necessary. + +1. Open the `app.py` file + +1. Add the following code to the top of `app.py` file with the rest of the `import` statements to connect to import some required libraries: + + ```python + import time + from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor + ``` + + The `import time` statement imports the Python `time` module that will be used later in this assignment. + + The `from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor` statement imports the `GroveLightSensor` from the CounterFit Grove shim Python libraries. This library has code to interact with a light sensor created in the CounterFit app. + +1. Add the following code to the bottom of the file to create instances of classes that manage the light sensor: + + ```python + light_sensor = GroveLightSensor(0) + ``` + + The line `light_sensor = GroveLightSensor(0)` creates an instance of the `GroveLightSensor` class connecting to pin **0** - the CounterFit Grove pin that the light sensor is connected to. + +1. Add an infinite loop after the code above to poll the light sensor value and print it to the console: + + ```python + while True: + light = light_sensor.light + print('Light level:', light) + ``` + + This will read the current light level using the `light` property of the `GroveLightSensor` class. This property reads the analog value from the pin. This value is then printed to the console. + +1. Add a small sleep of one second at the end of the `while` loop as the light levels don't need to be checked continuously. A sleep reduces the power consumption of the device. + + ```python + time.sleep(1) + ``` + +1. From the VS Code Terminal, run the following to run your Python app: + + ```sh + python3 app.py + ``` + + You should see light values being output to the console. Initially this value will be 0. + +1. From the CounterFit app, change the value of the light sensor that will be read by the app. You can do this in one of two ways: + + * Enter a number in the *Value* box for the light sensor, then select the **Set** button. The number you enter will be the value returned by the sensor. + + * Check the *Random* checkbox, and enter a *Min* and *Max* value, then select the **Set** button. Every time the sensor reads a value, it will read a random number between *Min* and *Max*. + + You should see the values you set appearing in the console. Change the *Value* or the *Random* settings to see the value change. + + ```output + (.venv) โžœ GroveTest python3 app.py + Light level: 143 + Light level: 244 + Light level: 246 + Light level: 253 + ``` + +> ๐Ÿ’ You can find this code in the [code-sensor/virtual-device](code-sensor/virtual-device) folder. + +๐Ÿ˜€ Your nightlight program was a success! diff --git a/1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md b/1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md new file mode 100644 index 00000000..e99b8aab --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md @@ -0,0 +1,110 @@ +# Build a nightlight - Wio Terminal + +In this part of the lesson, you will add an LED to your Wio Terminal and use it to create a nightlight. + +## Hardware + +The nightlight now needs an actuator. + +The actuator is an **LED**, a [light-emitting diode](https://wikipedia.org/wiki/Light-emitting_diode) that emits light when current flows through it. This is a digital actuator that has 2 states, on and off. Sending a value of 1 turns the LED on, and 0 turns it off. This is an external Grove actuator and needs to be connected to the Wio Terminal. + +The nightlight logic in pseudo-code is: + +```output +Check the light level. +If the light is less than 200 + Turn the LED on +Otherwise + Turn the LED off +``` + +### Connect the LED + +The Grove LED comes as a module with a selection of LEDs, allowing you to chose the color. + +#### Task + +Connect the LED. + +![A grove LED](../../../images/grove-led.png) + +1. Pick your favorite LED and insert the legs into the two holes on the LED module. + + LEDs are light-emitting diodes, and diodes are electronic devices that can only carry current one way. This means the LED needs to be connected the right way round, otherwise it won't work. + + One of the legs of the LED is the positive pin, the other is the negative pin. The LED is not perfectly round, and is slightly flatter on one side. The side that is slightly flatter is the negative pin. When you connect the LED to the module, make sure the pin by the rounded side is connected to the socket marked **+** on the outside of the module, and the flatter side is connected to the socket closer to the middle of the module. + +1. The LED module has a spin button that allows you to control the brightness. Turn this all the way up to start with byt rotating it anti-clockwise as far as it will go using a small Phillips head screwdriver. + +1. Insert one end of a Grove cable into the socket on the LED module. It will only go in one way round. + +1. With the Wio Terminal disconnected from your computer or other power supply, connect the other end of the Grove cable to the right-hand side Grove socket on the Wio Terminal as you look at the screen. This is the socket farthest away from the power button. + + > ๐Ÿ’ The right-hand Grove socket can be used with analog or digital sensors and actuators. The left-hand socket is for I2C and digital sensors and actuators only. I2C will be covered in a later lesson. + +![The grove LED connected to the right hand socket](../../../images/wio-led.png) + +## Program the nightlight + +The nightlight can now be programmed using the built in light sensor and the Grove LED. + +### Task + +Program the nightlight. + +1. Open the nightlight project in VS Code that you created in the previous part of this assignment + +1. Add the following line to the bottom of the `setup` function: + + ```cpp + pinMode(D0, OUTPUT); + ``` + + This line configures the pin used to communicate with the LED via the Grove port. + + The `D0` pin is the digital pin for the right-hand Grove socket. This pin is set to `OUTPUT`, meaning it connects to an actuator and data will be written to the pin. + +1. Add the following code immediately before the `delay` in the loop function: + + ```cpp + if (light < 200) + { + digitalWrite(D0, HIGH); + } + else + { + digitalWrite(D0, LOW); + } + ``` + + This code checks the `light` value. If this is less than 200 it sends a `HIGH` value to the `D0` digital pin. This `HIGH` is a value of 1, turning on the LED. If the light is greater than or equal to 200, a `LOW` value of 0 is sent to the pin, turning the LED off. + + > ๐Ÿ’ When sending digital values to actuators, a LOW value is 0v, and a HIGH value is the max voltage for the device. For the Wio Terminal, the HIGH voltage is 3.3V. + +1. Reconnect the Wio Terminal to your computer, and upload the new code as you did before. + +1. Connect the Serial Monitor. You should see light values being output to the terminal. + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem101 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Light value: 4 + Light value: 5 + Light value: 4 + Light value: 158 + Light value: 343 + Light value: 348 + Light value: 344 + ``` + +1. Cover and uncover the light sensor. Notice how the LED will light up if the light level is 200 or less, and turn off when the light level is greater than 200. + +![The LED connected to the WIO turning on and off as the light level changes](../../../images/wio-running-assignment-1-1.gif) + +> ๐Ÿ’ You can find this code in the [code-actuator/wio-terminal](code-actuator/wio-terminal) folder. + +๐Ÿ˜€ Your nightlight program was a success! diff --git a/1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md b/1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md new file mode 100644 index 00000000..e89cbefc --- /dev/null +++ b/1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md @@ -0,0 +1,73 @@ +# Add a sensor - Wio Terminal + +In this part of the lesson, you will use the light sensor on your Wio Terminal. + +## Hardware + +The sensor for this lesson is a **light sensor** that uses a [photodiode](https://wikipedia.org/wiki/Photodiode) to convert light to an electrical signal. This is an analog sensor that sends an integer value from 0 to 1,023 indicating a relative amount of light that doesn't map to any standard unit of measurement such as [lux](https://wikipedia.org/wiki/Lux). + +The light sensor is built into the Wio Terminal and is visible through the clear plastic window on the back. + +![The light sensor on the back of the Wio Terminal](../../../images/wio-light-sensor.png) + +## Program the light sensor + +The device can now be programmed to use the built in light sensor. + +### Task + +Program the device. + +1. Open the nightlight project in VS Code that you created in the previous part of this assignment + +1. Add the following line to the bottom of the `setup` function: + + ```cpp + pinMode(WIO_LIGHT, INPUT); + ``` + + This line configures the pins used to communicate with the sensor hardware. + + The `WIO_LIGHT` pin is the number of the GPIO pin connected to the on-board light sensor. This pin is set to `INPUT`, meaning it connects to a sensor and data will be read from the pin. + +1. Delete the contents of the `loop` function. + +1. Add the following code to the now empty `loop` function. + + ```cpp + int light = analogRead(WIO_LIGHT); + Serial.print("Light value: "); + Serial.println(light); + ``` + + This code reads an analog value from the `WIO_LIGHT` pin. This reads a value from 0-1,023 from the on-board light sensor. This value is then sent to the serial port so you can see it in the Serial Monitor when this code is running. `Serial.print` writes the text without a new line on the end, so each line will start with `Light value:` and end with the actual light value. + +1. Add a small delay of one second (1,000ms) at the end of the `loop` as the light levels don't need to be checked continuously. A delay reduces the power consumption of the device. + + ```cpp + delay(1000); + ``` + +1. Reconnect the Wio Terminal to your computer, and upload the new code as you did before. + +1. Connect the Serial Monitor. You should see light values being output to the terminal. Cover and uncover the light sensor on the back of the Wio Terminal to see the values change. + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem101 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Light value: 4 + Light value: 5 + Light value: 4 + Light value: 158 + Light value: 343 + Light value: 348 + Light value: 344 + ``` + +> ๐Ÿ’ You can find this code in the [code-sensor/wio-terminal](code-sensor/wio-terminal) folder. + +๐Ÿ˜€ Adding a sensor to your nightlight program was a success! diff --git a/1-getting-started/lessons/4-connect-internet/README.md b/1-getting-started/lessons/4-connect-internet/README.md new file mode 100644 index 00000000..e5e61322 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/README.md @@ -0,0 +1,444 @@ +# Connect your device to the Internet + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/7) + +## Introduction + +The **I** in IoT stands for **Internet** - the cloud connectivity and services that enable a lot of the features of IoT devices, from gathering measurements from the sensors connected to the device, to sending messages to control the actuators. IoT devices typically connect to a single cloud IoT service using a standard communication protocol, and that service is connected to the rest of your IoT application, from AI services to make smart decisions around your data, to web apps for control or reporting. + +> ๐ŸŽ“ Data gathered from sensors and sent to the cloud is called telemetry. + +IoT devices can receive messages from the cloud. Often the messages contain commands - that is instructions to perform an action either internally (such as reboot or update firmware), or using an actuator (such as turning on a light). + +This lesson introduces some of the communication protocols IoT devices can use to connect to the cloud, and the types of data they might send or receive. You will also get hands on with them both, adding Internet control to your nightlight, moving the LED control logic to 'server' code running locally. + +In this lesson we'll cover: + +* [Communication protocols](#communication-protocols) +* [Message Queueing Telemetry Transport (MQTT)](#message-queueing-telemetry-transport-mqtt) +* [Telemetry](#telemetry) +* [Commands](#commands) + +## Communication protocols + +There are a number of popular communication protocols used by IoT devices to communicate with the Internet. The most popular are based around publish/subscribe messaging via some kind of broker. The IoT devices connect to the broker and publish telemetry and subscribe to commands, the cloud services also connect to the broker and subscribe to all the telemetry messages and publishes commands either to specific devices, or to groups of devices. + +![IoT devices connect to a broker and publish telemetry and subscribe to commands. Cloud services connect to the broker and subscribe to all telemetry and send commands to specific devices.](../../../images/pub-sub.png) + +***IoT devices connect to a broker and publish telemetry and subscribe to commands. Cloud services connect to the broker and subscribe to all telemetry and send commands to specific devices. broadcast by RomStu / Microcontroller by Template / Cloud by Debi Alpa Nugraha - all from the [Noun Project](https://thenounproject.com)*** + +MQTT is the most popular, and is covered in this lesson. Others include AMQP and HTTP/HTTPS. + +## Message Queueing Telemetry Transport (MQTT) + +[MQTT](http://mqtt.org) is a lightweight, open standard messaging protocol that can send messages between devices. It was designed in 1999 to monitor oil pipelines, before being released as an open standard 15 years later by IBM. + +MQTT has a single broker and multiple clients. All clients connect to the broker, and the broker routes messages to the relevant clients. Messages are routed using named topics, rather than being sent direct to an individual client. A client can publish to a topic, and any clients that subscribe to that topic will receive the message. + +![IoT device publishing telemetry on the /telemetry topic, and the cloud service subscribing to that topic](../../../images/mqtt.png) + +***IoT device publishing telemetry on the /telemetry topic, and the cloud service subscribing to that topic. Microcontroller by Template / Cloud by Debi Alpa Nugraha - all from the [Noun Project](https://thenounproject.com)*** + +โœ… Do some research. If you have a lot of IoT devices, how can you ensure your MQTT broker can handle all the messages? + +### Connect your IoT device to MQTT + +The first part in adding Internet control to your nightlight is connecting it to an MQTT broker. + +#### Task + +Connect your device to an MQTT broker. + +In this part of the lesson, you will connect your IoT nightlight to the Internet to allow it to be remotely controlled. Later in this lesson your IoT device will send a telemetry message over MQTT to a public MQTT broker with the light level, where it will be picked up by some server code that you will write. This code will check the light level and send a command message back to the device telling it to turn the LED on or off. + +The real-world use case for such a setup could be to gather data from multiple light sensors before deciding to turn on lights, in a location that has a lot of lights, such as a stadium. This could stop the lights being turned on if only one sensor was covered by cloud or a bird, but the other sensors detected enough light. + +โœ… What other situations would require data from multiple sensors to be evaluated before sending commands? + +Rather than dealing with the complexities of setting up an MQTT broker as part of this assignment, you can use a public test server that runs [Eclipse Mosquitto](https://www.mosquitto.org), an open-source MQTT broker. This test broker is publicly available at [test.mosquitto.org](https://test.mosquitto.org), and doesn't require an accounts to be set up, making it a great tool for testing MQTT clients and servers. + +> ๐Ÿ’ This test broker is public and not secure. Anyone could be listening to what you publish, so should not be used with any data that needs to be kept private + +![A flow chart of the assignment showing light levels being read and checked, and the LED begin controlled](../../../images/assignment-1-internet-flow.png) + +***A flow chart of the assignment showing light levels being read and checked, and the LED begin controlled. ldr by Eucalyp / LED by abderraouf omara - all from the [Noun Project](https://thenounproject.com)*** + +Follow the relevant step below to connect your device to the MQTT broker: + +* [Arduino - Wio Terminal](wio-terminal-mqtt.md) +* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-mqtt.md) + +### A deeper dive into MQTT + +Topics can have a hierarchy, and clients can subscribe to different levels of the hierarchy using wildcards. For example you can send temperature telemetry messages to `/telemetry/temperature` and humidity messages to `/telemetry/humidity`, then in your cloud app subscribe to `/telemetry/*` to receive both the temperature and humidity telemetry messages. + +Messages can be sent with a quality of service (QoS), which determines the guarantees of the message being received. + +* At most once - the message is sent only once and the client and broker take no additional steps to acknowledge delivery (fire and forget). +* At least once - the message is re-tried by the sender multiple times until acknowledgement is received (acknowledged delivery). +* Exactly once - the sender and receiver engage in a two-level handshake to ensure only one copy of the message is received (assured delivery). + +โœ… What situations might require an assured delivery message over a fire and forget message? + +Although the name is Message Queueing, it doesn't actually support message queues. This means that if a client is disconnected, then reconnects it won't receive messages sent during the disconnect except for those messages that it had already started to process using the QoS process. Messages can have a retained flag set on them. If this is set, the MQTT broker will store the last message sent on a topic with this flag, and send this to any clients who later subscribe to the topic. This way the clients will always get the latest message. + +MQTT also supports a keep alive function that checks to see if the connection is still alive during long gaps between messages. + +> ๐ŸฆŸ [Mosquitto from the Eclipse Foundation](https://mosquitto.org) has a free MQTT broker you can run yourself to experiment with MQTT, along with a public MQTT broker you can use to test your code hosted at [test.mosquitto.org](https://test.mosquitto.org). + +MQTT connections can be public and open, or encrypted and secured using usernames and passwords, or certificates. + +> ๐Ÿ’ MQTT communicates over TCP/IP, the same underlying network protocol as HTTP, but on a different port. You can also use MQTT over websockets to communicate with web apps running in a browser, or in situations where firewalls or other networking rules block standard MQTT connections. + +## Telemetry + +The word telemetry is derived from Greek roots meaning to measure remotely. Telemetry is the act of gathering data from sensors and sending it to the cloud. + +> ๐Ÿ’ One of the earliest telemetry devices was invented in France in 1874 and sent real-time weather and snow depths from Mont Blanc to Paris. It used physical wires as wireless technologies were not available at the time. + +Lets look back at the example of the smart thermostat from Lesson 1. + +![An Internet connected thermostat using multiple room sensors](../../../images/telemetry.png) + +***An Internet connected thermostat using multiple room sensors. Temperature by Vectors Market / Microcontroller by Template / dial by Jamie Dickinson / heater by Pascal HeรŸ / mobile phone and Calendar by Alice-vector / Cloud by Debi Alpa Nugraha / smart sensor by Andrei Yushchenko / weather by Adrien Coquet - all from the [Noun Project](https://thenounproject.com)*** + +The thermostat has temperature sensors to gather telemetry. It would most likely have one temperature sensor built in, and it might connect to multiple external temperature sensors over a wireless protocol such as BLE. + +An example of the telemetry data it would send could be: + +| Name | Value | Description | +| ---- | ----- | ----------- | +| `thermostat_temperature` | 18ยฐC | The temperature measured by the thermostat's built in temperature sensor | +| `livingroom_temperature` | 19ยฐC | The temperature measured by a remote temperature sensor that has been named `livingroom` to identify the room it is in | +| `bedroom_temperature` | 21ยฐC | The temperature measured by a remote temperature sensor that has been named `bedroom` to identify the room it is in | + +The cloud service can then use this telemetry data to make decisions around what commands to send to control the heating. + +### Send telemetry from your IoT device + +The next part in adding Internet control to your nightlight is sending the light level telemetry to the MQTT broker on a telemetry topic. + +#### Task + +Send light level telemetry to the MQTT broker. + +Data is sent encoded as JSON - short for JavaScript Object Notation, a standard for encoding data in text using key/value pairs. + +โœ… If you've not come across JSON before, you can learn more about it on the [JSON.org documentation](https://www.json.org/). + +Follow the relevant step below to send telemetry from your device to the MQTT broker: + +* [Arduino - Wio Terminal](wio-terminal-telemetry.md) +* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-telemetry.md) + +### Receive telemetry from the MQTT broker + +There's no point in sending telemetry if there's nothing on the other end to listen for it. The light level telemetry needs something listening to it to process the data. This 'server' code is the kind of code you wold deploy to a cloud service as part of a larger IoT application, but here you are going to run this code locally on your computer (on on you Pi if you are coding directly on there). The server code consists of a Python app that listens to telemetry messages over MQTT with light levels. Later in this less you will make it reply with a command message with instructions to turn the LED on or off. + +โœ… Do some research: What happens to MQTT messages if there is no listener? + +#### Install Python and VS Code + +If you don't have Python and VS Code installed locally, you will need to install them both to code the server. If you are using a virtual device, or are working on your Raspberry Pi you can skip this step. + +##### Task + +Install Python and VS Code. + +1. Install Python. Refer to the [Python downloads page](https://www.python.org/downloads/) for instructions on install the latest version of Python. + +1. Install Visual Studio Code (VS Code). This is the editor you will be using to write your virtual device code in Python. Refer to the [VS Code documentation](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) for instructions on installing VS Code. + + > ๐Ÿ’ You are free to use any Python IDE or editor for these lessons if you have a preferred tool, but the lessons will give instructions based off using VS Code. + +1. Install the VS Code Pylance extension. This is an extension for VS Code that provides Python language support. Refer to the [Pylance extension documentation](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) for instructions on installing this extension in VS Code. + +#### Configure a Python virtual environment + +One of the powerful features of Python is the ability to install [pip packages](https://pypi.org) - these are packages of code written by other people and published to the Internet. You can install a pip package onto your computer with one command, then use that package in your code. You'll be using pip to install a package to communicate over MQTT. + +By default when you install a package it is available everywhere on your computer, and this can lead to problems with package versions - such as one application depending on one version of a package that breaks when you install a new version for a different application. To work around this problem, you can use a [Python virtual environment](https://docs.python.org/3/library/venv.html), essentially a copy of Python in a dedicated folder, and when you install pip packages they get installed just to that folder. + +##### Task + +Configure a Python virtual environment and install the MQTT pip packages. + +1. From your terminal or command line, run the following at a location of your choice to create and navigate to a new directory: + + ```sh + mkdir nightlight-server + cd nightlight-server + ``` + +1. Now run the following to create a virtual environment in the `.venv` folder + + ```sh + python3 -m venv .venv + ``` + + > ๐Ÿ’ You need to explicitly call `python3` to create the virtual environment just in case you have Python 2 installed in addition to Python 3 (the latest version). If you have Python2 installed then calling `python` will use Python 2 instead of Python 3 + +1. Activate the virtual environment: + + * On Windows run: + + ```cmd + .venv\Scripts\activate.bat + ``` + + * On macOS or Linux, run: + + ```cmd + source ./.venv/bin/activate + ``` + +1. Once the virtual environment has been activated, the default `python` command will run the version of Python that was used to create the virtual environment. Run the following to see this: + + ```sh + python --version + ``` + + You should see the following: + + ```output + (.venv) โžœ nightlight-server python --version + Python 3.9.1 + ``` + + > ๐Ÿ’ Your Python version may be different - as long as it's version 3.6 or higher you are good. If not, delete this folder, install a newer version of Python and try again. + +1. Run the following commands to install the pip package for [Paho-MQTT](https://pypi.org/project/paho-mqtt/), a popular MQTT library. + + ```sh + pip install paho-mqtt + ``` + + This pip package will only be installed in the virtual environment, and will not be available outside of this. + +#### Write the server code + +The server code can now be written in Python. + +##### Task + +Write the server code. + +1. From your terminal or command line, run the following inside the virtual environment to create a Python file called `app.py`: + + * From Windows run: + + ```cmd + type nul > app.py + ``` + + * On macOS or Linux, run: + + ```cmd + touch app.py + ``` + +1. Open the current folder in VS Code: + + ```sh + code . + ``` + +1. When VS Code launches, it will activate the Python virtual environment. You will see this in the bottom status bar: + + ![VS Code showing the selected virtual environment](../../../images/vscode-virtual-env.png) + +1. If the VS Code Terminal is already running when VS Code starts up, it won't have the virtual environment activated in it. The easiest thing to do is kill the terminal using the **Kill the active terminal instance** button: + + ![VS Code Kill the active terminal instance button](../../../images/vscode-kill-terminal.png) + +1. Launch a new VS Code Terminal by selecting *Terminal -> New Terminal, or pressing `` CTRL+` ``. The new terminal will load the virtual environment, and you will see the call to activate this in the terminal, as well as having the name of the virtual environment (`.venv`) in the prompt: + + ```output + โžœ nightlight source .venv/bin/activate + (.venv) โžœ nightlight + ``` + +1. Open the `app.py` file from the VS Code explorer and add the following code: + + ```python + import json + import time + + import paho.mqtt.client as mqtt + + id = '' + + client_telemetry_topic = id + '/telemetry' + client_name = id + 'nightlight_server' + + mqtt_client = mqtt.Client(client_name) + mqtt_client.connect('test.mosquitto.org') + + mqtt_client.loop_start() + + def handle_telemetry(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + mqtt_client.subscribe(client_telemetry_topic) + mqtt_client.on_message = handle_telemetry + + while True: + time.sleep(2) + ``` + + Replace `` on line 6 with the unique ID you used when creating your device code. + + โš ๏ธ This **must** be the same ID that you used on your device, or the server code won't subscribe or publish to the right topic. + + This code creates an MQTT client with a unique name, and connects to the *test.mosquitto.org* broker. It then starts a processing loop that runs in on a background thread listening for messages on any subscribed topics. + + The client then subscribes to messages on the telemetry topic, and defines a function that is called when a message is received. When a telemetry message is received, the `handle_telemetry` function is called, printing the message received to the console. + + Finally an infinite loop keeps the application running. The MQTT client is listening to messages on a background thread and runs all the time the main application is running. + +1. From the VS Code terminal, run the following to run your Python app: + + ```sh + python app.py + ``` + + The app will start listening to messages from the IoT device. + +1. Make sure your device is running and sending telemetry messages. Adjust the light levels detected by your physical or virtual device. You will see messages being received in the terminal. + + ```output + (.venv) โžœ nightlight-server python app.py + Message received: {'light': 0} + Message received: {'light': 400} + ``` + +> ๐Ÿ’ You can find this code in the [code-server/server](code-server/server) folder. + +### How often should telemetry be sent? + +One important consideration with telemetry is how often to measure and send the data? The answer is - it depends. If you measure often you can respond faster to changes in measurements, but you use more power, more bandwidth, generate more data and need more cloud resources to process. You need to measure often enough, but not too often. + +For a thermostat, measuring every few minutes is probably more than enough as temperatures don't change that often. If you only measure once a day then you could end up heating your house for nighttime temperatures in the middle of a sunny day, whereas if you measure every second you will have thousands of unnecessary duplicated temperature measurements that will eat into the users Internet speed and bandwidth (a problem for people with limited bandwidth plans), use more power which can be a problem for battery powered devices like remote sensors, and increase the cost of the providers cloud computing resources processing and storing them. + +If you are monitoring a data around a piece of machinery in a factory that if it fails could cause catastrophic damage and millions of dollars in lost revenue, thn measuring multiple times a second might be necessary. It's better to waste bandwidth than miss telemetry that indicates that a machine needs to be stopped and fixed before it breaks. + +> ๐Ÿ’ In this situation, you might consider having an edge device to process the telemetry first to reduce reliance on the Internet. + +### Loss of connectivity + +Internet connections can be unreliable, with outages common. What should an IoT device do under these circumstances - should it lose the data, or should it store it until connectivity is restored? Again, the answer is it depends. + +For a thermostat the data can probably be lost as soon as a new temperature measurement has been taken. The heating system doesn't care that 20 minutes ago it was 20.5ยฐC if the temperature is now 19ยฐC, it's the temperature now that determines if the heating should be on or off. + +For machinery you might want to keep the data, especially if it is used to look for trends. There are machine learning models that can detect anomalies in streams of data by looking over data from defined period of time (such as the last hour) and spotting anomalous data. This is often used for predictive maintenance, looking for indications that something might break soon so you can repair or replace before it happens. You might want every bit of telemetry for a machine sent so it can be processed for anomaly detection, so once the IoT device can reconnect it will send all the telemetry generated during the Internet outage. + +IoT device designers should also consider if the IoT device can be used during an Internet outage or loss of signal caused by location. A smart thermostat should be able to make some limited decisions to control heating if it can't send telemetry to the cloud due to an outage. + +[![This ferrari got bricked because someone tried to upgrade it underground where there's no cell reception](../../../images/bricked-car.png)](https://twitter.com/internetofshit/status/1315736960082808832) + +For MQTT to handle a loss of connectivity, the device and server code will need to be responsible for ensuring message delivery if it is needed, for example by requiring that all messages sent are replied to by additional messages on a reply topic, and if not they are queued manually to be replayed later. + +## Commands + +Commands are messages sent by the cloud to a device, instructing it to do something. Most of the time this involves giving some kind of output via an actuator, but it can be an instruction for the device itself, such as to reboot, or gather extra telemetry and return it as a response to the command. + +![An Internet connected thermostat receiving a command to turn on the heating](../../../images/commands.png) + +***An Internet connected thermostat receiving a command to turn on the heating. Temperature by Vectors Market / Microcontroller by Template / dial by Jamie Dickinson / heater by Pascal HeรŸ / mobile phone and Calendar by Alice-vector / Cloud by Debi Alpa Nugraha / smart sensor by Andrei Yushchenko / weather by Adrien Coquet - all from the [Noun Project](https://thenounproject.com)*** + +A thermostat could receive a command from the cloud to turn the heating on. Based on the telemetry data from all the sensors, if the cloud service has decided that the heating should be on, so it sends the relevant command. + +### Send commands to the MQTT broker + +The next step for our Internet controlled nightlight is for the server code to send a command back to the IoT device to control the light based off the light levels it senses. + +1. Open the server code in VS Code + +1. Add the following line after the declaration of the `client_telemetry_topic` to define which topic to send commands to: + + ```python + server_command_topic = id + '/commands' + ``` + +1. Add the following code to the end of the `handle_telemetry` function: + + ```python + command = { 'led_on' : payload['light'] < 200 } + print("Sending message:", command) + + client.publish(server_command_topic, json.dumps(command)) + ``` + + This sends a JSON message to the command topic with the value of `led_on` set to true or false depending on if the light is less than 200 or not. If the light is less than 200, true is sent to instruct the device to turn the LED on. + +1. Run the code as before + +1. Adjust the light levels detected by your physical or virtual device. You will see messages being received and commands being sent in the terminal: + + ```output + (.venv) โžœ nightlight-server python app.py + Message received: {'light': 0} + Sending message: {'led_on': True} + Message received: {'light': 400} + Sending message: {'led_on': False} + ``` + +> ๐Ÿ’ The telemetry and commands are being sent on a single topic each. This means telemetry from multiple devices will appear on the same telemetry topic, and commands to multiple devices will appear on the same commands topic. If you wanted to send a command to a specific device, you could use multiple topics, named with a unique device id, such as `/commands/device1`, `/commands/device2`. That way a device can listen on messages just meant for that one device. + +### Handle commands on the IoT device + +Now that commands are being sent from the server, you cna now add code to the IoT device to handle them and control the LED. + +Follow the relevant step below to listen to commands from the MQTT broker: + +* [Arduino - Wio Terminal](wio-terminal-commands.md) +* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-commands.md) + +Once this code is written and running, experiment with changing light levels. Watch the output from the server and device, and watch the LED as you change light levels. + +### Loss of connectivity + +What should a cloud service do if it needs to send a command to an IoT device that is offline? Again, the answer is it depends. + +If the latest command overrides an earlier one then the earlier ones can probably be ignored. If a cloud service sends a command to turn the heating on, then sends a command to turn it off, then the on command can be ignored and not resent. + +If the commands need to be processed in sequence, such as move a robot arm up, then close a grabber then they need to be sent in order once connectivity is restored. + +โœ… How could the device or server code ensure commands are always sent and handled in order over MQTT if needed? + +--- + +## ๐Ÿš€ Challenge + +The challenge in the last three lessons was to list as many IoT devices as you can that are in your home, school or workplace and decide if they are built around microcontrollers or single-board computers, or even a mixture of both, and thing about what sensors and actuators they are using. + +For these devices, think about what messages they might be sending or receiving. What telemetry do they send? What messages or commands might they receive? Do you think they are secure? + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/8) + +## Review & Self Study + +Read more on MQTT on the [MQTT Wikipedia page](https://wikipedia.org/wiki/MQTT). + +Try running an MQTT broker yourself using [Mosquitto](https://www.mosquitto.org) and connect to it from your IoT device and server code. + +> ๐Ÿ’ Tip - by default Mosquitto doesn't allow anonymous connections (that is connecting without a username and password), and doesn't allow connections from outside of the computer it's running on. +> You can fix this with a [`mosquitto.conf` config file](https://www.mosquitto.org/man/mosquitto-conf-5.html) with the following: +> +> ```sh +> listener 1883 0.0.0.0 +> allow_anonymous true +> ``` + +## Assignment + +[Compare and contrast MQTT with other communication protocols](assignment.md) diff --git a/1-getting-started/lessons/4-connect-internet/assignment.md b/1-getting-started/lessons/4-connect-internet/assignment.md new file mode 100644 index 00000000..1676bf76 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/assignment.md @@ -0,0 +1,14 @@ +# Compare and contrast MQTT with other communication protocols + +## Instructions + +This lesson covered MQTT as a communication protocols. There are others, including AMQP and HTTP/HTTPS. + +Research these both and compare/contract them with MQTT. Think about power usage, security, and message persistence if connections are lost. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Compare AMQP to MQTT | Is able to compare and contrast AMQP to MQTT and covers power, security, and message persistance. | Is partly able to compare and contrast AMQP to MQTT and covers two of power, security, and message persistance. | Is partly able to compare and contrast AMQP to MQTT and covers one of power, security, and message persistance. | +| Compare HTTP/HTTPS to MQTT | Is able to compare and contrast HTTP/HTTPS to MQTT and covers power, security, and message persistance. | Is partly able to compare and contrast HTTP/HTTPS to MQTT and covers two of power, security, and message persistance. | Is partly able to compare and contrast HTTP/HTTPS to MQTT and covers one of power, security, and message persistance. | diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/pi/nightlight/app.py b/1-getting-started/lessons/4-connect-internet/code-commands/pi/nightlight/app.py new file mode 100644 index 00000000..dbce36e7 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/pi/nightlight/app.py @@ -0,0 +1,39 @@ +import time +from grove.grove_light_sensor_v1_2 import GroveLightSensor +from grove.grove_led import GroveLed +import json +import paho.mqtt.client as mqtt + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'nightlight_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +def handle_command(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + if payload['led_on']: + led.on() + else: + led.off() + +mqtt_client.subscribe(server_command_topic) +mqtt_client.on_message = handle_command + +while True: + light = light_sensor.light + print('Light level:', light) + + mqtt_client.publish(client_telemetry_topic, json.dumps({'light' : light})) + + time.sleep(5) \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/server/app.py b/1-getting-started/lessons/4-connect-internet/code-commands/server/app.py new file mode 100644 index 00000000..e6b46379 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/server/app.py @@ -0,0 +1,24 @@ +import json +import time + +import paho.mqtt.client as mqtt + +id = '' + +client_telemetry_topic = id + '/telemetry' +client_name = id + '_nightlight_server' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +def handle_telemetry(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + +mqtt_client.subscribe(client_telemetry_topic) +mqtt_client.on_message = handle_telemetry + +while True: + time.sleep(2) \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/virtual-device/nightlight/app.py b/1-getting-started/lessons/4-connect-internet/code-commands/virtual-device/nightlight/app.py new file mode 100644 index 00000000..4857aa61 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/virtual-device/nightlight/app.py @@ -0,0 +1,40 @@ +import time +from counterfit_connection import CounterFitConnection +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor +from counterfit_shims_grove.grove_led import GroveLed +import json +import paho.mqtt.client as mqtt + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'nightlight_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +def handle_command(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + if payload['led_on']: + led.on() + else: + led.off() + +mqtt_client.subscribe(server_command_topic) +mqtt_client.on_message = handle_command + +while True: + light = light_sensor.light + print('Light level:', light) + + mqtt_client.publish(client_telemetry_topic, json.dumps({'light' : light})) diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/.gitignore b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/.vscode/extensions.json b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/include/README b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/lib/README b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/platformio.ini b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/platformio.ini new file mode 100644 index 00000000..2d16f6d5 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + knolleary/PubSubClient @ 2.8 + bblanchon/ArduinoJson @ 6.17.3 + seeed-studio/Seeed Arduino rpcWiFi @ 1.0.3 + seeed-studio/Seeed Arduino FS @ 2.0.2 + seeed-studio/Seeed Arduino SFUD @ 2.0.1 + seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 + seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/src/config.h b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/src/config.h new file mode 100644 index 00000000..8d5620ee --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/src/config.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +using namespace std; + +// WiFi credentials +const char *SSID = ""; +const char *PASSWORD = ""; + +// MQTT settings +const string ID = ""; + +const string BROKER = "test.mosquitto.org"; +const string CLIENT_NAME = ID + "nightlight_client"; + +const string CLIENT_TELEMETRY_TOPIC = ID + "/telemetry"; +const string SERVER_COMMAND_TOPIC = ID + "/commands"; \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/src/main.cpp b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/src/main.cpp new file mode 100644 index 00000000..e8c1ac21 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/src/main.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include + +#include "config.h" + +void connectWiFi() +{ + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("Connecting to WiFi.."); + WiFi.begin(SSID, PASSWORD); + delay(500); + } + + Serial.println("Connected!"); +} + +WiFiClient wioClient; +PubSubClient client(wioClient); + +void clientCallback(char *topic, uint8_t *payload, unsigned int length) +{ + char buff[length + 1]; + for (int i = 0; i < length; i++) + { + buff[i] = (char)payload[i]; + } + buff[length] = '\0'; + + Serial.print("Message received:"); + Serial.println(buff); + + DynamicJsonDocument doc(1024); + deserializeJson(doc, buff); + JsonObject obj = doc.as(); + + bool led_on = obj["led_on"]; + + if (led_on) + digitalWrite(D0, HIGH); + else + digitalWrite(D0, LOW); +} + +void reconnectMQTTClient() +{ + while (!client.connected()) + { + Serial.print("Attempting MQTT connection..."); + + if (client.connect(CLIENT_NAME.c_str())) + { + Serial.println("connected"); + client.subscribe(SERVER_COMMAND_TOPIC.c_str()); + } + else + { + Serial.print("Retying in 5 seconds - failed, rc="); + Serial.println(client.state()); + + delay(5000); + } + } +} + +void createMQTTClient() +{ + client.setServer(BROKER.c_str(), 1883); + client.setCallback(clientCallback); + reconnectMQTTClient(); +} + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + pinMode(WIO_LIGHT, INPUT); + pinMode(D0, OUTPUT); + + connectWiFi(); + createMQTTClient(); +} + +void loop() +{ + reconnectMQTTClient(); + client.loop(); + + int light = analogRead(WIO_LIGHT); + + DynamicJsonDocument doc(1024); + doc["light"] = light; + + string telemetry; + JsonObject obj = doc.as(); + serializeJson(obj, telemetry); + + Serial.print("Sending telemetry "); + Serial.println(telemetry.c_str()); + + client.publish(CLIENT_TELEMETRY_TOPIC.c_str(), telemetry.c_str()); + + delay(2000); +} \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/test/README b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal/nightlight/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/pi/nightlight/app.py b/1-getting-started/lessons/4-connect-internet/code-mqtt/pi/nightlight/app.py new file mode 100644 index 00000000..5186de17 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/pi/nightlight/app.py @@ -0,0 +1,29 @@ +import time +from grove.grove_light_sensor_v1_2 import GroveLightSensor +from grove.grove_led import GroveLed +import paho.mqtt.client as mqtt + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +id = '' + +client_name = id + 'nightlight_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +print("MQTT connected!") + +while True: + light = light_sensor.light + print('Light level:', light) + + if light < 200: + led.on() + else: + led.off() + + time.sleep(1) diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/virtual-device/nightlight/app.py b/1-getting-started/lessons/4-connect-internet/code-mqtt/virtual-device/nightlight/app.py new file mode 100644 index 00000000..6c68b5de --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/virtual-device/nightlight/app.py @@ -0,0 +1,32 @@ +import time +from counterfit_connection import CounterFitConnection +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor +from counterfit_shims_grove.grove_led import GroveLed +import paho.mqtt.client as mqtt + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +id = '' + +client_name = id + 'nightlight_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +print("MQTT connected!") + +while True: + light = light_sensor.light + print('Light level:', light) + + if light < 200: + led.on() + else: + led.off() + + time.sleep(1) diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.gitignore b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.vscode/extensions.json b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/include/README b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/lib/README b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/platformio.ini b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/platformio.ini new file mode 100644 index 00000000..3b2294a4 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/platformio.ini @@ -0,0 +1,21 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + knolleary/PubSubClient @ 2.8 + seeed-studio/Seeed Arduino rpcWiFi @ 1.0.3 + seeed-studio/Seeed Arduino FS @ 2.0.2 + seeed-studio/Seeed Arduino SFUD @ 2.0.1 + seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 + seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/src/config.h b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/src/config.h new file mode 100644 index 00000000..e98eba81 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/src/config.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +using namespace std; + +// WiFi credentials +const char *SSID = ""; +const char *PASSWORD = ""; + +// MQTT settings +const string ID = ""; + +const string BROKER = "test.mosquitto.org"; +const string CLIENT_NAME = ID + "nightlight_client"; \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/src/main.cpp b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/src/main.cpp new file mode 100644 index 00000000..a0122583 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/src/main.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +#include "config.h" + +void connectWiFi() +{ + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("Connecting to WiFi.."); + WiFi.begin(SSID, PASSWORD); + delay(500); + } + + Serial.println("Connected!"); +} + +WiFiClient wioClient; +PubSubClient client(wioClient); + +void reconnectMQTTClient() +{ + while (!client.connected()) + { + Serial.print("Attempting MQTT connection..."); + + if (client.connect(CLIENT_NAME.c_str())) + { + Serial.println("connected"); + } + else + { + Serial.print("Retying in 5 seconds - failed, rc="); + Serial.println(client.state()); + + delay(5000); + } + } +} + +void createMQTTClient() +{ + client.setServer(BROKER.c_str(), 1883); + reconnectMQTTClient(); +} + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + pinMode(WIO_LIGHT, INPUT); + pinMode(D0, OUTPUT); + + connectWiFi(); + createMQTTClient(); +} + +void loop() +{ + reconnectMQTTClient(); + client.loop(); + + delay(2000); +} \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/test/README b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/1-getting-started/lessons/4-connect-internet/code-server/server/app.py b/1-getting-started/lessons/4-connect-internet/code-server/server/app.py new file mode 100644 index 00000000..01b6a0f2 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-server/server/app.py @@ -0,0 +1,30 @@ +import json +import time + +import paho.mqtt.client as mqtt + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'nightlight_server' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +def handle_telemetry(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + command = { 'led_on' : payload['light'] < 200 } + print("Sending message:", command) + + client.publish(server_command_topic, json.dumps(command)) + +mqtt_client.subscribe(client_telemetry_topic) +mqtt_client.on_message = handle_telemetry + +while True: + time.sleep(2) \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/pi/nightlight/app.py b/1-getting-started/lessons/4-connect-internet/code-telemetry/pi/nightlight/app.py new file mode 100644 index 00000000..a34b93bc --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/pi/nightlight/app.py @@ -0,0 +1,30 @@ +import time +from grove.grove_light_sensor_v1_2 import GroveLightSensor +from grove.grove_led import GroveLed +import paho.mqtt.client as mqtt +import json + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +client_name = id + 'nightlight_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +print("MQTT connected!") + +while True: + light = light_sensor.light + telemetry = json.dumps({'light' : light}) + + print("Sending telemetry ", telemetry) + + mqtt_client.publish(client_telemetry_topic, telemetry) + + time.sleep(5) diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/virtual-device/nightlight/app.py b/1-getting-started/lessons/4-connect-internet/code-telemetry/virtual-device/nightlight/app.py new file mode 100644 index 00000000..194b7cec --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/virtual-device/nightlight/app.py @@ -0,0 +1,33 @@ +import time +from counterfit_connection import CounterFitConnection +from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor +from counterfit_shims_grove.grove_led import GroveLed +import paho.mqtt.client as mqtt +import json + +CounterFitConnection.init('127.0.0.1', 5000) + +light_sensor = GroveLightSensor(0) +led = GroveLed(5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +client_name = id + 'nightlight_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +print("MQTT connected!") + +while True: + light = light_sensor.light + telemetry = json.dumps({'light' : light}) + + print("Sending telemetry ", telemetry) + + mqtt_client.publish(client_telemetry_topic, telemetry) + + time.sleep(5) diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/.gitignore b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/.vscode/extensions.json b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/include/README b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/lib/README b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/platformio.ini b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/platformio.ini new file mode 100644 index 00000000..aa415c00 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + knolleary/PubSubClient @ 2.8 + seeed-studio/Seeed Arduino rpcWiFi @ 1.0.3 + seeed-studio/Seeed Arduino FS @ 2.0.2 + seeed-studio/Seeed Arduino SFUD @ 2.0.1 + seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 + seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 + bblanchon/ArduinoJson @ 6.17.3 diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/src/config.h b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/src/config.h new file mode 100644 index 00000000..0daa8305 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/src/config.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +using namespace std; + +// WiFi credentials +const char *SSID = ""; +const char *PASSWORD = ""; + +// MQTT settings +const string ID = ""; + +const string BROKER = "test.mosquitto.org"; +const string CLIENT_NAME = ID + "nightlight_client"; + +const string CLIENT_TELEMETRY_TOPIC = ID + "/telemetry"; \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/src/main.cpp b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/src/main.cpp new file mode 100644 index 00000000..5bf64b3e --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/src/main.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include + +#include "config.h" + +void connectWiFi() +{ + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("Connecting to WiFi.."); + WiFi.begin(SSID, PASSWORD); + delay(500); + } + + Serial.println("Connected!"); +} + +WiFiClient wioClient; +PubSubClient client(wioClient); + +void reconnectMQTTClient() +{ + while (!client.connected()) + { + Serial.print("Attempting MQTT connection..."); + + if (client.connect(CLIENT_NAME.c_str())) + { + Serial.println("connected"); + } + else + { + Serial.print("Retying in 5 seconds - failed, rc="); + Serial.println(client.state()); + + delay(5000); + } + } +} + +void createMQTTClient() +{ + client.setServer(BROKER.c_str(), 1883); + reconnectMQTTClient(); +} + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + pinMode(WIO_LIGHT, INPUT); + pinMode(D0, OUTPUT); + + connectWiFi(); + createMQTTClient(); +} + +void loop() +{ + reconnectMQTTClient(); + client.loop(); + + int light = analogRead(WIO_LIGHT); + + DynamicJsonDocument doc(1024); + doc["light"] = light; + + string telemetry; + JsonObject obj = doc.as(); + serializeJson(obj, telemetry); + + Serial.print("Sending telemetry "); + Serial.println(telemetry.c_str()); + + client.publish(CLIENT_TELEMETRY_TOPIC.c_str(), telemetry.c_str()); + + delay(2000); +} \ No newline at end of file diff --git a/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/test/README b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal/nightlight/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md b/1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md new file mode 100644 index 00000000..c14a1864 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md @@ -0,0 +1,53 @@ +# Control your nightlight over the Internet - Virtual IoT Hardware and Raspberry Pi + +In this part of the lesson, you will subscribe to commands sent from an MQTT broker to your Raspberry Pi or virtual IoT device. + +## Subscribe to commands + +The next step is to subscribe to the commands sent from the MQTT broker, and respond to them. + +### Task + +Subscribe to commands. + +1. Open the nightlight project in VS Code. + +1. If you are using a virtual IoT device, ensure the terminal is running the virtual environment + +1. Add the following code after the definitions of the `client_telemetry_topic`: + + ```python + server_command_topic = id + '/commands' + ``` + + The `server_command_topic` is the MQTT topic the device will subscribe to to receive LED commands. + +1. Add the following code just above the main loop, after the `mqtt_client.loop_start()` line: + + ```python + def handle_command(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + if payload['led_on']: + led.on() + else: + led.off() + + mqtt_client.subscribe(server_command_topic) + mqtt_client.on_message = handle_command + ``` + + This code defines a function, `handle_command`, that reads a message as a JSON document and looks for the value of the `led_on` property. If it is set to `True` the LED is turned on, otherwise it is turned off. + + The MQTT client subscribes on the topic that the server will send messages on, and sets the `handle_command` function to be called when a message is received. + + > ๐Ÿ’ The `on_message` handler is called for all topics subscribed to. If you later write code that listens to multiple topics, you can get the topic that the message was sent to from the `message` object passed to the handler function. + +1. Run the code in the same way as you ran the code from the previous part of the assignment. If you are using a virtual IoT device, then make sure the CounterFit app is running and the light sensor and LED have been created on the correct pins. + +1. Adjust the light levels detected by your physical or virtual device. You will see messages being received and commands being sent in the terminal. You will also see the LED being turned on and off depending on the light level. + +> ๐Ÿ’ You can find this code in the [code-commands/virtual-device](code-commands/virtual-device) folder or the [code-commands/pi](code-commands/pi) folder. + +๐Ÿ˜€ You have successfully coded your device to respond to commands from an MQTT broker. diff --git a/1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md b/1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md new file mode 100644 index 00000000..f153a39b --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md @@ -0,0 +1,79 @@ +# Control your nightlight over the Internet - Virtual IoT Hardware and Raspberry Pi + +The IoT device needs to be coded to communicate with *test.mosquitto.org* using MQTT to send telemetry values with the light sensor reading, and receive commands to control the LED. + +In this part of the lesson, you will connect your Raspberry Pi or virtual IoT device to an MQTT broker. + +## Install the MQTT client package + +To communicate with the MQTT broker, you need to install an MQTT library pip package either on your Pi, or in your virtual environment if you are using a virtual device. + +### Task + +Install the pip package + +1. Open the nightlight project in VS Code. + +1. If you are using a virtual IoT device, ensure the terminal is running the virtual environment. + +1. Run the following command to install the MQTT pip package: + + ```sh + pip3 install paho-mqtt + ``` + +## Code the device + +The device is ready to code. + +### Task + +Write the device code. + +1. Add the following import to the top of the `app.py` file: + + ```python + import paho.mqtt.client as mqtt + ``` + + The `paho.mqtt.client` library allows your app to communicate over MQTT. + +1. Add the following code after the definitions of the light sensor and LED: + + ```python + id = '' + + client_name = id + 'nightlight_client' + ``` + + Replace `` with a unique ID that will be used the name of this device client, and later for the topics that this device publishes and subscribes to. The *test.mosquitto.org* broker is public and used by many people, including other students working through this assignment. Having a unique MQTT client name and topic names ensures your code won't clash with anyone elses. You will also need this ID when you are creating the server code later in this assignment. + + > ๐Ÿ’ You can use a website like [GUIDGen](https://www.guidgen.com) to generate a unique ID. + + The `client_name` is a unique name for this MQTT client on the broker. + +1. Add the following code below this new code to create an MQTT client object and connect to the MQTT broker: + + ```python + mqtt_client = mqtt.Client(client_name) + mqtt_client.connect('test.mosquitto.org') + + mqtt_client.loop_start() + + print("MQTT connected!") + ``` + + This code creates the client object, connects to the public MQTT broker, and starts a processing loop that runs in a background thread listening for messages on any subscribed topics. + +1. Run the code in the same way as you ran the code from the previous part of the assignment. If you are using a virtual IoT device, then make sure the CounterFit app is running and the light sensor and LED have been created on the correct pins. + + ```output + (.venv) โžœ nightlight python app.py + MQTT connected! + Light level: 0 + Light level: 0 + ``` + +> ๐Ÿ’ You can find this code in the [code-mqtt/virtual-device](code/virtual-device) folder or the [code-mqtt/pi](code/pi) folder. + +๐Ÿ˜€ You have successfully connected your device to an MQTT broker. diff --git a/1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md b/1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md new file mode 100644 index 00000000..08bf6019 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md @@ -0,0 +1,60 @@ +# Control your nightlight over the Internet - Virtual IoT Hardware and Raspberry Pi + +In this part of the lesson, you will send telemetry with light levels from your Raspberry Pi or virtual IoT device to an MQTT broker. + +## Publish telemetry + +The next step is to create a JSON document with telemetry and send it to the MQTT broker. + +### Task + +Publish telemetry to the MQTT broker. + +1. Open the nightlight project in VS Code. + +1. If you are using a virtual IoT device, ensure the terminal is running the virtual environment + +1. Add the following import to the top of the `app.py` file: + + ```python + import json + ``` + + The `json` library is used to encode the telemetry as a JSON document. + +1. Add the following after the `client_name` declaration: + + ```python + client_telemetry_topic = id + '/telemetry' + ``` + + The `client_telemetry_topic` is the MQTT topic the device will publish light levels to. + +1. Replace the contents of the `while True:` loop at the end of the file with the following: + + ```python + while True: + light = light_sensor.light + telemetry = json.dumps({'light' : light}) + + print("Sending telemetry ", telemetry) + + mqtt_client.publish(client_telemetry_topic, telemetry) + + time.sleep(5) + ``` + + This code packages the light level into a JSON document and publishes it to the MQTT broker. It then sleeps to reduce the frequency that messages are sent. + +1. Run the code in the same way as you ran the code from the previous part of the assignment. If you are using a virtual IoT device, then make sure the CounterFit app is running and the light sensor and LED have been created on the correct pins. + + ```output + (.venv) โžœ nightlight python app.py + MQTT connected! + Sending telemetry {"light": 0} + Sending telemetry {"light": 0} + ``` + +> ๐Ÿ’ You can find this code in the [code-telemetry/virtual-device](code-telemetry/virtual-device) folder or the [code-telemetry/pi](code-telemetry/pi) folder. + +๐Ÿ˜€ You have successfully sent telemetry from your device. diff --git a/1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md b/1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md new file mode 100644 index 00000000..966df6af --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md @@ -0,0 +1,79 @@ +# Control your nightlight over the Internet - Wio Terminal + +In this part of the lesson, you will subscribe to commands sent from an MQTT broker to your Wio Terminal. + +## Subscribe to commands + +The next step is to subscribe to the commands sent from the MQTT broker, and respond to them. + +### Task + +Subscribe to commands. + +1. Open the nightlight project in VS Code. + +1. Add the following code to the bottom of the `config.h` file to define the topic name for the commands: + + ```cpp + const string SERVER_COMMAND_TOPIC = ID + "/commands"; + ``` + + The `SERVER_COMMAND_TOPIC` is the topic the device will subscribe to to receive LED commands. + +1. Add the following line to the end of the `reconnectMQTTClient` function to subscribe to the command topic when the MQTT client is reconnected: + + ```cpp + client.subscribe(SERVER_COMMAND_TOPIC.c_str()); + ``` + +1. Add the following code below the `reconnectMQTTClient` function. + + ```cpp + void clientCallback(char *topic, uint8_t *payload, unsigned int length) + { + char buff[length + 1]; + for (int i = 0; i < length; i++) + { + buff[i] = (char)payload[i]; + } + buff[length] = '\0'; + + Serial.print("Message received:"); + Serial.println(buff); + + DynamicJsonDocument doc(1024); + deserializeJson(doc, buff); + JsonObject obj = doc.as(); + + bool led_on = obj["led_on"]; + + if (led_on) + digitalWrite(D0, HIGH); + else + digitalWrite(D0, LOW); + } + ``` + + This function will be the callback that the MQTT client will call when it receives a message from the server. + + The message is received as an array of unsigned 8-bit integers, so needs to be converted to a character array to be treated as text. + + The message contains a JSON document, and is decoded using the ArduinoJson library. The `led_on` property of the JSON document is read, and depending on the value the LED is turned on or off. + +1. Add the following code to the `createMQTTClient` function: + + ```cpp + client.setCallback(clientCallback); + ``` + + This code sets the `clientCallback` as the callback to be called when a message is received from the MQTT broker. + + > ๐Ÿ’ The `clientCallback` handler is called for all topics subscribed to. If you later write code that listens to multiple topics, you can get the topic that the message was sent to from the `topic` parameter passed to the callback function. + +1. Upload the code to your Wio Terminal, and use the Serial Monitor to see the light levels being sent to the MQTT broker. + +1. Adjust the light levels detected by your physical or virtual device. You will see messages being received and commands being sent in the terminal. You will also see the LED being turned on and off depending on the light level. + +> ๐Ÿ’ You can find this code in the [code-commands/wio-terminal](code-commands/wio-terminal) folder. + +๐Ÿ˜€ You have successfully coded your device to respond to commands from an MQTT broker. diff --git a/1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md b/1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md new file mode 100644 index 00000000..a6e53550 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md @@ -0,0 +1,237 @@ +# Control your nightlight over the Internet - Wio Terminal + +The IoT device needs to be coded to communicate with *test.mosquitto.org* using MQTT to send telemetry values with the light sensor reading, and receive commands to control the LED. + +In this part of the lesson, you will connect your Wio Terminal to an MQTT broker. + +## Install the WiFi and MQTT Arduino libraries + +To communicate with the MQTT broker, you need to install some Arduino libraries to use the WiFi chip in the Wio Terminal, and communicate with MQTT. When developing for Arduino devices, you can use a wide range of libraries that contain open-source code and implement a huge range of capabilities. Seeed publish libraries for the Wio Terminal that allow it to communicate over WiFi. Other developers have published libraries to communicate with MQTT brokers, and you will be using these with your device. + +These libraries are provided as source code that can be imported automatically into PlatformIO, and compiled for your device. This way Arduino libraries will work on any device that supports the Arduino framework, assuming that the device has any specific hardware needed by that library. Some libraries, such as the Seeed WiFi libraries, are specific to certain hardware. + +Libraries can be installed globally and compiled in if needed, or into a specific project. For this assignment, the libraries will be installed into the project. + +โœ… You can learn more about library management and how to find and install libraries in the [PlatformIO library documentation](https://docs.platformio.org/en/latest/librarymanager/index.html). + +### Task + +Install the Arduino libraries. + +1. Open the nightlight project in VS Code. + +1. Add the following to the end of the `platformio.ini` file: + + ```ini + lib_deps = + seeed-studio/Seeed Arduino rpcWiFi @ 1.0.3 + seeed-studio/Seeed Arduino FS @ 2.0.2 + seeed-studio/Seeed Arduino SFUD @ 2.0.1 + seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 + seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 + ``` + + This imports the Seeed WiFi libraries. The `@ ` syntax refers to a specific version number of the library. + + > ๐Ÿ’ You can remove the `@ ` to always use the latest version of the libraries, but there's no guarantees the later versions will work with the code below. The code here has been tested with this version of the libraries. + + This is all you need to do to add the libraries. Next time PlatformIO builds the project it will download the source code for these libraries and compile it into your project. + +1. Add the following to the `lib_deps`: + + ```ini + knolleary/PubSubClient @ 2.8 + ``` + + This imports [PubSubClient](https://github.com/knolleary/pubsubclient), an Arduino MQTT client + +## Connect to WiFi + +The Wio Terminal can now be connected to WiFi. + +### Task + +Connect the Wio Terminal to WiFi. + +1. Create a new file in the `src` folder called `config.h`. You can do this by selecting the `src` folder, or the `main.cpp` file inside, and selecting the **New file** button from the explorer. This button only appears when your cursor is over the explorer. + + ![The new file button](../../../images/vscode-new-file-button.png) + +1. Add the following code to this file to define constants for your WiFi credentials: + + ```cpp + #pragma once + + #include + + using namespace std; + + // WiFi credentials + const char *SSID = ""; + const char *PASSWORD = ""; + ``` + + Replace `` with the SSID of your WiFi. Replace `` with your WiFi password. + +1. Open the `main.cpp` file + +1. Add the following `#include` directives to the top of the file: + + ```cpp + #include + #include + #include + + #include "config.h" + ``` + + This includes header files for the libraries you added earlier, as well as the config header file. + +1. Add the following code above the `setup` function: + + ```cpp + void connectWiFi() + { + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("Connecting to WiFi.."); + WiFi.begin(SSID, PASSWORD); + delay(500); + } + + Serial.println("Connected!"); + } + ``` + + This code loops whilst the device is not connected to WiFi, and tries the connecting using the SSID and password from the config header file. + +1. Add a call to this function at the bottom of the `setup` function, after the pins have been configured. + + ```cpp + connectWiFi(); + ``` + +1. Upload this code to your device to check the WiFi connection is working. You should see this in the serial monitor. + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem1101 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Connecting to WiFi.. + Connected! + ``` + +## Connect to MQTT + +Once the Wio Terminal is connected to WiFi, it can connect to the MQTT broker. + +### Task + +Connect to the MQTT broker. + +1. Add the following code to the bottom of the `config.h` file to define the connection details for the MQTT broker: + + ```cpp + // MQTT settings + const string ID = ""; + + const string BROKER = "test.mosquitto.org"; + const string CLIENT_NAME = ID + "nightlight_client"; + ``` + + Replace `` with a unique ID that will be used the name of this device client, and later for the topics that this device publishes and subscribes to. The *test.mosquitto.org* broker is public and used by many people, including other students working through this assignment. Having a unique MQTT client name and topic names ensures your code won't clash with anyone elses. You will also need this ID when you are creating the server code later in this assignment. + + > ๐Ÿ’ You can use a website like [GUIDGen](https://www.guidgen.com) to generate a unique ID. + + The `BROKER` is the URL of the MQTT broker. + + The `CLIENT_NAME` is a unique name for this MQTT client on the broker. + +1. Open the `main.cpp` file, and add the following code below the `connectWiFi` function and above the `setup` function: + + ```cpp + WiFiClient wioClient; + PubSubClient client(wioClient); + ``` + + This code creates a WiFi client using the Wio Terminal WiFI libraries, and uses it to create an MQTT client. + +1. Below this code, add the following: + + ```cpp + void reconnectMQTTClient() + { + while (!client.connected()) + { + Serial.print("Attempting MQTT connection..."); + + if (client.connect(CLIENT_NAME.c_str())) + { + Serial.println("connected"); + } + else + { + Serial.print("Retying in 5 seconds - failed, rc="); + Serial.println(client.state()); + + delay(5000); + } + } + } + ``` + + This function tests the connection to the MQTT broker and reconnects if it is not connected. It loops all the time it is not connected, and attempts to connect using the unique client name defined in the config header file. + + If the connection fails, it retries after 5 seconds. + +1. Add the following code below the `reconnectMQTTClient` function: + + ```cpp + void createMQTTClient() + { + client.setServer(BROKER.c_str(), 1883); + reconnectMQTTClient(); + } + ``` + + This code sets the MQTT broker for the client, as well as setting up the callback when a message is received. It then attempts to connect to the broker. + +1. Call the `createMQTTClient` function in the `setup` function after the WiFi is connected. + +1. Replace the entire `loop` function with the following: + + ```cpp + void loop() + { + reconnectMQTTClient(); + client.loop(); + + delay(2000); + } + ``` + + This code starts by reconnecting to the MQTT broker. These connections can be broken easily, so it's worth regularly checking and reconnecting if necessary. It then calls the `loop` method on the MQTT client to process any messages that are coming in on the topic subscribed to. This app is single-threaded, so messages cannot be received on a background thread, therefore time on the main thread needs to be allocated to processing any messages that are waiting on the network connection. + + Finally a delay of 2 seconds ensures the light levels are not sent too often and reduces the power consumption of the device. + +1. Upload the code to your Wio Terminal, and use the Serial Monitor to see the device connecting to WiFi and MQTT. + + ```output + > Executing task: platformio device monitor < + + source /Users/jimbennett/GitHub/IoT-For-Beginners/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.venv/bin/activate + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Connecting to WiFi.. + Connected! + Attempting MQTT connection...connected + ``` + +> ๐Ÿ’ You can find this code in the [code-mqtt/wio-terminal](code-mqtt/wio-terminal) folder. + +๐Ÿ˜€ You have successfully connected your device to an MQTT broker. diff --git a/1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md b/1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md new file mode 100644 index 00000000..eb728a21 --- /dev/null +++ b/1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md @@ -0,0 +1,80 @@ +# Control your nightlight over the Internet - Wio Terminal + +In this part of the lesson, you will send telemetry with light levels from your Wio Terminal to the MQTT broker. + +## Install the JSON Arduino libraries + +A popular way to send messages over MQTT is using JSON. There is an Arduino library for JSON that makes reading and writing JSON documents easier. + +### Task + +Install the Arduino JSON library. + +1. Open the nightlight project in VS Code. + +1. Add the following as an additional line to the `lib_deps` list in the `platformio.ini` file: + + ```ini + bblanchon/ArduinoJson @ 6.17.3 + ``` + + This imports [ArduinoJson](https://arduinojson.org), an Arduino JSON library. + +## Publish telemetry + +The next step is to create a JSON document with telemetry and send it to the MQTT broker. + +### Task + +Publish telemetry to the MQTT broker. + +1. Add the following code to the bottom of the `config.h` file to define the telemetry topic name for the MQTT broker: + + ```cpp + const string CLIENT_TELEMETRY_TOPIC = ID + "/telemetry"; + ``` + + The `CLIENT_TELEMETRY_TOPIC` is the topic the device will publish light levels to. + +1. Open the `main.cpp` file + +1. Add the following `#include` directive to the top of the file: + + ```cpp + #include + ``` + +1. Add the following code inside the `loop` function, just before the `delay`: + + ```cpp + int light = analogRead(WIO_LIGHT); + + DynamicJsonDocument doc(1024); + doc["light"] = light; + + string telemetry; + JsonObject obj = doc.as(); + serializeJson(obj, telemetry); + + Serial.print("Sending telemetry "); + Serial.println(telemetry.c_str()); + + client.publish(CLIENT_TELEMETRY_TOPIC.c_str(), telemetry.c_str()); + ``` + + This code reads the light level, and creates a JSON document using ArduinoJson containing this level. This is then serialized to a string and published on the telemetry MQTT topic by the MQTT client. + +1. Upload the code to your Wio Terminal, and use the Serial Monitor to see the light levels being sent to the MQTT broker. + + ```output + Connecting to WiFi.. + Connected! + Attempting MQTT connection...connected + Sending telemetry {"light":652} + Sending telemetry {"light":612} + Sending telemetry {"light":583} + ``` + +> ๐Ÿ’ You can find this code in the [code-telemetry/wio-terminal](code-telemetry/wio-terminal) folder. + +๐Ÿ˜€ You have successfully sent telemetry from your device. diff --git a/2-farm/README.md b/2-farm/README.md new file mode 100644 index 00000000..e974f528 --- /dev/null +++ b/2-farm/README.md @@ -0,0 +1,22 @@ +# Farming with IoT + +As the population grows, so does the demand on agriculture. The amount of land available doesn't change, but the climate does - giving even more challenges to farmers, especially the 2 billion [subsistence farmers](https://wikipedia.org/wiki/Subsistence_agriculture) who rely on what they grow to be able to eat and feed their families. IoT can help farmers make smarter decisions on what to grow and when to harvest, increase yields, reduce the amount of manual labor, and detect and deal with pests. + +In these 6 lessons you'll learn how to apply the Internet of Things to improve and automate farming. + +> ๐Ÿ’ These lessons will use some cloud resources. If you don't complete all the lessons in this project, make sure you follow the [Clean up your project](lessons/6-keep-your-plant-secure/README.md#clean-up-your-project) step in [lesson 6](lessons/6-keep-your-plant-secure/README.md). + +**Add video of automated plant** + +## Topics + +1. [Predict plant growth with IoT](lessons/1-predict-plant-growth/README.md) +1. [Detect soil moisture](lessons/2-detect-soil-moisture/README.md) +1. [Automated plant watering](lessons/3-automated-plant-watering/README.md) +1. [Migrate your plant to the cloud](lessons/4-migrate-your-plant-to-the-cloud/README.md) +1. [Migrate your application logic to the cloud](lessons/5-migrate-application-to-the-cloud/README.md) +1. [Keep your plant secure](lessons/6-keep-your-plant-secure/README.md) + +## Credits + +All the lessons were written with โ™ฅ๏ธ by [Jim Bennett](https://GitHub.com/JimBobBennett) diff --git a/2-farm/lessons/1-predict-plant-growth/README.md b/2-farm/lessons/1-predict-plant-growth/README.md new file mode 100644 index 00000000..1a6ffe21 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/README.md @@ -0,0 +1,269 @@ +# Predict plant growth with IoT + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/9) + +## Introduction + +Plants need certain things to grow - water, carbon dioxide, nutrients, light, and heat. In this lesson, you'll learn how to calculate the growth and maturity rates of plants by measuring the air temperature. + +In this lesson we'll cover: + +* [Digital agriculture](#digital-agriculture) +* [Why is temperature important when farming?](#why-is-temperature-important-when-farming) +* [Measure ambient temperature](#measure-ambient-temperature) +* [Growing degree days (GDD)](#growing-degree-days) +* [Calculate GDD using temperature sensor data](#calculate-gdd-using-temperature-sensor-data) + +## Digital agriculture + +Digital Agriculture is transforming how we farm, using tools to collect, store and analyze data from farming. We are currently in a period described as the 'Fourth Industrial Revolution' by the World Economic Forum, and the rise of digital agriculture has been labelled as the 'Fourth Agricultural Revolution', or 'Agriculture 4.0'. + +> ๐ŸŽ“ The term Digital Agriculture also includes the whole 'agriculture value chain', that is the entire journey from farm to table. It includes tracking produce quality as food is shipped and processed, warehouse and e-commerce systems, even tractor rental apps! + +These changes allow farmers to increase yields, use less fertilizers and pesticides, and water more efficiently. Although primarily used in richer nations, sensors and other devices are slowly reducing in price, making them more accessible in developing nations. + +Some techniques enabled by digital agriculture are: + +* Temperature measurement - measuring temperature allows farmers to predict plant growth and maturity. +* Automated watering - measuring soil moisture and turning on irrigation systems when the soil is too dry, rather than timed watering. Timed watering can lead to crops being under-watered during a hot, dry spell, or over-watered during rain. By watering only when the soil needs it farmers can optimize their water use. +* Pest control - farmers can use cameras on automated robots or drones to check for pests, then apply pesticides only where needed, reducing the amount of pesticides used and reducing pesticide run-off into local water supplies. + +โœ… Do some research. What other techniques are used to improve farming yields? + +> ๐ŸŽ“ The term 'Precision Agriculture' is used to define observing, measuring and responding to crops on a per-field basis, or even on parts of a field. This includes measuring water, nutrient and pest levels and responding accurately, such as watering only a small part of a field. + +## Why is temperature important when farming? + +When learning about plants, most students are taught about the necessity of water, light, carbon dioxide (CO2) and nutrients. Plants also need warmth to grow - this is why plants bloom in spring as the temperature rises, why snowdrops or daffodils can sprout early due to a short warm spell, and why hothouses and greenhouses are so good at making plants grow. + +> ๐ŸŽ“ Hothouses and greenhouses do a similar job, but with an important difference. Hothouses are heated artificially and allow farmers to control temperatures more accurately, greenhouses rely on the sun for warmth and usually the only control is windows or other openings to let heat out. + +Plants have a base or minimum temperature, optimal temperature, and maximum temperature, all based on daily average temperatures. + +* Base temperature - this is the minimum daily average temperature needed for a plant to grow. +* Optimum temperature - this is the best daily average temperature to get the most growth +* Maximum temperature - The is the maximum temperature a plant can withstand. Above this the plant will shut down it's growth in an attempt to conserve water and stay alive. + +> ๐Ÿ’ These are average temperatures, averaged over the daily and nightly temperatures. Plants also need different temperatures day and night to help them photosynthesize more efficiently and save energy at night. + +Each species of plant has different values for their base, optimal and maximum. This is why some plants thrive in hot countries, and others in colder countries. + +โœ… Do some research. For any plants you have in your garden, school, or local park see if you can find the base temperature. + +![A graph showing growth rate rising as temperature rises, then dropping as the temperature goes too high](../../../images/plant-growth-temp-graph.png) + +The graph above shows an example growth rate to temperature graph. Up to the base temperature there is no growth. The growth rate increases up to the optimum temperature, then falls after reaching this peak. At the maximum temperature growth stops. + +The shape of this graph varies from plant species to plant species. Some have sharper drop offs above the optimum, some have slower increases from tbe base to the optimum. + +> ๐Ÿ’ For a farmer to get the best growth, they will need to know the three temperature values and understand the shape of the graphs for the plants they are growing. + +If a farmer has control of temperature, for example in a commercial hothouse, then they can optimise for their plants. A commercial hothouse growing tomatoes for example will have the temperature set to around 25ยฐC during the day and 20ยฐC at night to get the fastest growth. + +> ๐Ÿ… Combining these temperatures with artificial lights, fertilizers and controlled CO2 levels means commercial growers can grow and harvest all year round. + +## Measure ambient temperature + +Temperature sensors can be used with IoT devices to measure ambient temperature. + +### Task - measure temperature + +Work through the relevant guide to monitor temperatures using your IoT device: + +* [Arduino - Wio Terminal](wio-terminal-temp.md) +* [Single-board computer - Raspberry Pi](pi-temp.md) +* [Single-board computer - Virtual device](virtual-device-temp.md) + +## Growing degree days + +Growing degree days (also know as growing degree units) are a way of measuring the growth of plants based off the temperature. Assuming a plant has enough water, nutrients and CO2, the temperature determines the growth rate. + +Growing degree days, or GDD are calculated per day as the average temperature in Celsius for a day above the plants base temperature. Each plant needs a certain number of GDD to grow, flower or produce and mature a crop. The more GDD each day, the faster the plant will grow. + +> ๐Ÿ‡บ๐Ÿ‡ธ For Americans, growing degree days can also be calculated using Fahrenheit. 5 GDDC (growing degree days in Celsius) is the equivalent of 9 GDDF (growing degree days in Fahrenheit). + +The full formula for GDD is a little complicated, but there is a simplified equation that is often used as a good approximation: + +![GDD = T max + T min divided by 2, all minus T base](../../../images/gdd-calculation.png) + +* **GDD** - this is the number of growing degree days +* **Tmax** - this is the daily maximum temperature in degrees Celsius +* **Tmin** - this is the daily minimum temperature in degrees Celsius +* **Tbase** - this is the plants base temperature in degrees Celsius + +> ๐Ÿ’ There are variations that deal with Tmax above 30ยฐC or Tmin below Tbase, but we'll ignore these for now. + +### Example - Corn/Maize ๐ŸŒฝ + +Depending on the variety, corn (or maize) needs between 800 and 2,700 GDD to mature, with a base temperature of 10ยฐC. + +On the first day above the base temperature, the following temperatures were measured: + +| Measurement | Temp ยฐC | +| :---------- | :-----: | +| Maximum | 16 | +| Minimum | 12 | + +Plugging these numbers in to our calculation: + +* Tmax = 16 +* Tmin = 12 +* Tbase = 10 + +This gives a calculation of: + +![GDD = 16 + 12 divided by 2, all minus 10, giving an answer of 4](../../../images/gdd-calculation-corn.png) + +The corn received 4 GDD on that day. Assuming a corn variety that needs 800 GDD days to mature, it will need another 796 GDD to reach maturity. + +โœ… Do some research. For any plants you have in your garden, school, or local park see if you can find the number of GDD required to reach maturity or produce crops. + +## Calculate GDD using temperature sensor data + +Plants don't grow on fixed dates - for example you can't plant a seed and know that the plant will bear fruit exactly 100 days later. Instead as a farmer you can have a rough idea how long a plant takes to grow, then you would check daily to see when the crops were ready. + +This has a huge labour impact on a large farm, and risks the farmer missing crops that are ready unexpectedly early. By measuring temperatures, the farmer can calculate the GDD a plant has received, allowing them to only check close to the expected maturity. + +By gathering temperature data using an IoT device, a farmer can automatically be notified when plants are close to maturity. A typical architecture for this is to have the IoT devices measure temperature, then publish this telemetry data over the Internet using something like MQTT. Server code then listens to this data and saves it somewhere, such as to a database. This means the data can then be analyzed later, such as a nightly job to calculate the GDD for the day, total up the GDD for each crop so far and alert if a plant is close to maturity. + +![Telemetry data is sent to a server and then saved to a database](../../../images/save-telemetry-database.png) + +***Telemetry data is sent to a server and then saved to a database. database by Icons Bazaar - from the [Noun Project](https://thenounproject.com)*** + +The server code can also augment the data by adding extra information. For example, the IoT device can publish an identifier to indicate which device it is, and the sever code can use this to look up the location of the device, and what crops it is monitoring. It can also add basic data like the current time as some IoT devices don't have the necessary hardware to keep track of an accurate time, or require additional code to read the current time over the Internet. + +โœ… Why do you think different fields might have different temperatures? + +### Task - publish temperature information + +Work through the relevant guide to publish temperature data over MQTT using your IoT device so it can be analyzed later: + +* [Arduino - Wio Terminal](wio-terminal-temp-publish.md) +* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-temp-publish.md) + +### Task - capture and store the temperature information + +Once the IoT device is publishing telemetry, the server code can be written to subscribe to this data and store it. Rather than save it to a database, the server code will save it to a Comma Separated Values (CSV) file. CSV files store data as rows of values as text with each value separated by a comma, and each record on a new line. They are a convenient, human-readable and well supported way to save data as a file. + +The CSV file will have two columns - *date* and *temperature*. The *date* column is set as the current date and time that the message was received by the server, the *temperature* comes from the telemetry message. + +1. Repeat the steps in lesson 4 to create server code to subscribe to telemetry. You don't need to add code to publish commands. + + The steps for this are: + + * Configure and activate a Python Virtual Environment + + * Install the paho-mqtt pip package + + * Write the code to listen for MQTT messages published on the telemetry topic + + > โš ๏ธ You can refer to [the instructions in lesson 4 for creating a Python app to receive telemetry if needed](../../../1-getting-started/lessons/4-connect-internet/README.md#receive-telemetry-from-the-mqtt-broker). + + Name the folder for this project `temperature-sensor-server`. + +1. Make sure the `client_name` reflects this project: + + ```cpp + client_name = id + 'temperature_sensor_server' + ``` + +1. Add the following imports to the top of the file, below the existing imports: + + ```python + from os import path + import csv + from datetime import datetime + ``` + + This imports a library for reading files, a library to interact with CSV files, and a library to help with dates and times. + +1. Add the following code before the `handle_telemetry` function: + + ```python + temperature_file_name = 'temperature.csv' + fieldnames = ['date', 'temperature'] + + if not path.exists(temperature_file_name): + with open(temperature_file_name, mode='w') as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=fieldnames) + writer.writeheader() + ``` + + This code declares some constants for the name of the file to write to, and the name of the column headers for the CSV file. The first row of a CSV file traditionally contains column headers separated by commas. + + The code then checks to see if the CSV file already exists. If it doesn't exist, it is created with the column headers on the first row. + +1. Add the following code to the end of the `handle_telemetry` function: + + ```python + with open(temperature_file_name, mode='a') as temperature_file: + temperature_writer = csv.DictWriter(temperature_file, fieldnames=fieldnames) + temperature_writer.writerow({'date' : datetime.now().astimezone().replace(microsecond=0).isoformat(), 'temperature' : payload['temperature']}) + ``` + + This code opens the CSV file, then appends a new row on the end. The row has the current data and time formatted into a human-readable format, followed by the temperature received from the IoT device. The data is stored in [ISO 8601 format](https://wikipedia.org/wiki/ISO_8601) with the timezone, but without microseconds. + +1. Run this code in the same way as before, making sure your IoT device is sending data. A CSV file called `temperature.csv` will be created in the same folder. If you view it you will see date/times and temperature measurements: + + ```output + date,temperature + 2021-04-19T17:21:36-07:00,25 + 2021-04-19T17:31:36-07:00,24 + 2021-04-19T17:41:36-07:00,25 + ``` + +1. Run this code for a while to capture data. Ideally you should run this for a an entire day to gather enough data for GDD calculations. + + > ๐Ÿ’ If you want to run this for an entire day, then you need to make sure the computer your server code is running on won't go to sleep, either by changing your power settings, or running something like [this keep system active Python script](https://github.com/jaqsparow/keep-system-active). + +> ๐Ÿ’ You can find this code in the [code-server/server](code-server/server) folder. + +### Task - calculate GDD using the stored data + +Once the server has captured temperature data, the GDD for a plant can be calculated. + +The steps to do this manually are: + +1. Find the base temperature for the plant. For example, for strawberries the base temperature is 10ยฐC. + +1. From the `temperature.csv`, find the highest and lowest temperatures for the day + +1. Use the GDD calculation given earlier to calculate GDD + +For example, if the highest temperature for the day is 25ยฐC, and the lowest is 12ยฐC: + +![GDD = 25 + 12 divided by 2, then subtract 10 from the result giving 8.5](../../../images/gdd-calculation-strawberries.png) + +* 25 + 12 = 37 +* 37 / 2 = 18.5 +* 18.5 - 10 = 8.5 + +Therefore the strawberries have received **8.5** GDD. Strawberries need about 250 GDD to bear fruit, so still a while to go. + +--- + +## ๐Ÿš€ Challenge + +Plants need more than heat to grow. What other things are needed? + +For these, find if there are sensors that can measure them. What about actuators to control these levels? How would you put together one or more IoT devices to optimize plant growth? + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/10) + +## Review & Self Study + +Read more on digital agriculture on the [Digital Agriculture Wikipedia page](https://wikipedia.org/wiki/Digital_agriculture). Also read more about precision agriculture the [Precision Agriculture Wikipedia page](https://wikipedia.org/wiki/Precision_agriculture). + +The full growing degree days calculation is more complicated than the simplified one given here. Read more about the more complicated equation and how to deal with temperatures below the baseline on the [Growing Degree Day Wikipedia page](https://wikipedia.org/wiki/Growing_degree-day). + +## Assignment + +[Visualize GDD data using a Jupyter Notebook](assignment.md) diff --git a/2-farm/lessons/1-predict-plant-growth/assignment.md b/2-farm/lessons/1-predict-plant-growth/assignment.md new file mode 100644 index 00000000..b4881b13 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/assignment.md @@ -0,0 +1,43 @@ +# Visualize GDD data using a Jupyter Notebook + +## Instructions + +In this lesson you gathered GDD data using an IoT sensor. To get good GDD data, you need to gather data for multiple days. To help visualize temperature data and calculate GDD you can use tools like [Jupyter Notebooks](https://jupyter.org) to analyze the data. + +Start by gathering data for a few days. You will need to ensure your server code is running all the time your IoT device is running, either by adjusting your power management settings or running something like [this keep system active Python script](https://github.com/jaqsparow/keep-system-active). + +Once you have temperature data, you can use a Jupyter Notebook in this repo to visualize it and calculate GDD. Jupyter notebooks mix code and instructions in blocks called *cells*, often code in Python. You can read the instructions, then run each block of code block by block. You can also edit the code. In this notebook for example, you can edit the base temperature used to calculate the GDD for your plant. + +1. Create a folder called `gdd-calculation` + +1. Download the [gdd.ipynb](./code-notebook/gdd.ipynb) file and copy it into the `gdd-calculation` folder. + +1. Copy the `temperature.csv` file created by the MQTT server + +1. Create a new Python virtual environment in the `gdd-calculation` folder. + +1. Install some pip packages for Jupyter notebooks, along with libraries needed to manage and plot the data: + + ```sh + pip install -U pip + pip install pandas + pip install matplotlib + pip install jupyter + ``` + +1. Run the notebook in Jupyter: + + ```sh + jupyter notebook gdd.ipynb + ``` + + Jupyter will start up and open the notebook in your browser. Work through the instructions in the notebook to visualize the temperatures measured, and calculate the growing degree days. + + ![The jupyter notebook](../../../images/gdd-jupyter-notebook.png) + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Capture data | Capture at least 2 complete days of data | Capture at least 1 complete day of data | Capture some data | +| Calculate GDD | Successfully run the notebook and calculate GDD | Successfully run the notebook | Not able to run the notebook | diff --git a/2-farm/lessons/1-predict-plant-growth/code-notebook/gdd.ipynb b/2-farm/lessons/1-predict-plant-growth/code-notebook/gdd.ipynb new file mode 100644 index 00000000..c8d50169 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-notebook/gdd.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Growing Degree Days\n", + "\n", + "This notebook loads temperature data saved in a CSV file, and analyzes it. It plots the temperatures, shows the highest and lowest value for each day, and calculates the GDD.\n", + "\n", + "To use this notebook:\n", + "\n", + "* Copy the `temperature.csv` file into the same folder as this notebook\n", + "* Run all the cells using the **โ–ถ๏ธŽ Run** button above. This will run the selected cell, then move to the next one." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the cell below, set `base_temperature` to the base temperature of the plant." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "base_temperature = 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The CSV file now needs to be loaded, using pandas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Read the temperature CSV file\n", + "df = pd.read_csv('temperature.csv')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The temperature can now be plotted on a graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure(figsize=(20, 10))\n", + "plt.plot(df['date'], df['temperature'])\n", + "plt.xticks(rotation='vertical');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the data has been read it can be grouped by the `date` column, and the minimum and maximum temperatures extracted for each date." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert datetimes to pure dates so we can group by the date\n", + "df['date'] = pd.to_datetime(df['date']).dt.date\n", + "\n", + "# Group the data by date so it can be analyzed by date\n", + "data_by_date = df.groupby('date')\n", + "\n", + "# Get the minimum and maximum temperatures for each date\n", + "min_by_date = data_by_date.min()\n", + "max_by_date = data_by_date.max()\n", + "\n", + "# Join the min and max temperatures into one dataframe and flatten it\n", + "min_max_by_date = min_by_date.join(max_by_date, on='date', lsuffix='_min', rsuffix='_max')\n", + "min_max_by_date = min_max_by_date.reset_index()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The GDD can be calculated using the standard GDD equation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_gdd(row):\n", + " return ((row['temperature_max'] + row['temperature_min']) / 2) - base_temperature\n", + "\n", + "# Calculate the GDD for each row\n", + "min_max_by_date['gdd'] = min_max_by_date.apply (lambda row: calculate_gdd(row), axis=1)\n", + "\n", + "# Print the results\n", + "print(min_max_by_date[['date', 'gdd']].to_string(index=False))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.1" + }, + "metadata": { + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/pi/temperature-sensor/app.py b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/pi/temperature-sensor/app.py new file mode 100644 index 00000000..255565fc --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/pi/temperature-sensor/app.py @@ -0,0 +1,28 @@ +import time +from seeed_dht import DHT +import paho.mqtt.client as mqtt +import json + +sensor = DHT("11", 5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +client_name = id + 'temperature_sensor_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +print("MQTT connected!") + +while True: + _, temp = sensor.read() + telemetry = json.dumps({'temperature' : temp}) + + print("Sending telemetry ", telemetry) + + mqtt_client.publish(client_telemetry_topic, telemetry) + + time.sleep(10 * 60) diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/virtual-device/temperature-sensor/app.py b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/virtual-device/temperature-sensor/app.py new file mode 100644 index 00000000..ec2eb45c --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/virtual-device/temperature-sensor/app.py @@ -0,0 +1,31 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +from counterfit_shims_seeed_python_dht import DHT +import paho.mqtt.client as mqtt +import json + +sensor = DHT("11", 5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +client_name = id + 'temperature_sensor_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +print("MQTT connected!") + +while True: + _, temp = sensor.read() + telemetry = json.dumps({'temperature' : temp}) + + print("Sending telemetry ", telemetry) + + mqtt_client.publish(client_telemetry_topic, telemetry) + + time.sleep(10 * 60) diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/.gitignore b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/.vscode/extensions.json b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/include/README b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/lib/README b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/platformio.ini b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/platformio.ini new file mode 100644 index 00000000..05b21598 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/platformio.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + seeed-studio/Grove Temperature And Humidity Sensor @ 1.0.1 + knolleary/PubSubClient @ 2.8 + bblanchon/ArduinoJson @ 6.17.3 + seeed-studio/Seeed Arduino rpcWiFi @ 1.0.3 + seeed-studio/Seeed Arduino FS @ 2.0.2 + seeed-studio/Seeed Arduino SFUD @ 2.0.1 + seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 + seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/src/config.h b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/src/config.h new file mode 100644 index 00000000..54489ec0 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/src/config.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +using namespace std; + +// WiFi credentials +const char *SSID = ""; +const char *PASSWORD = ""; + +// MQTT settings +const string ID = ""; + +const string BROKER = "test.mosquitto.org"; +const string CLIENT_NAME = ID + "temperature_sensor_client"; + +const string CLIENT_TELEMETRY_TOPIC = ID + "/telemetry"; \ No newline at end of file diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/src/main.cpp b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/src/main.cpp new file mode 100644 index 00000000..c76c516b --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/src/main.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include + +#include "config.h" + +DHT dht(D0, DHT11); + +void connectWiFi() +{ + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("Connecting to WiFi.."); + WiFi.begin(SSID, PASSWORD); + delay(500); + } + + Serial.println("Connected!"); +} + +WiFiClient wioClient; +PubSubClient client(wioClient); + +void reconnectMQTTClient() +{ + while (!client.connected()) + { + Serial.print("Attempting MQTT connection..."); + + if (client.connect(CLIENT_NAME.c_str())) + { + Serial.println("connected"); + } + else + { + Serial.print("Retying in 5 seconds - failed, rc="); + Serial.println(client.state()); + + delay(5000); + } + } +} + +void createMQTTClient() +{ + client.setServer(BROKER.c_str(), 1883); + reconnectMQTTClient(); +} + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + dht.begin(); + + connectWiFi(); + createMQTTClient(); +} + +void loop() +{ + reconnectMQTTClient(); + client.loop(); + + float temp_hum_val[2] = {0}; + dht.readTempAndHumidity(temp_hum_val); + + DynamicJsonDocument doc(1024); + doc["temperature"] = temp_hum_val[1]; + + string telemetry; + JsonObject obj = doc.as(); + serializeJson(obj, telemetry); + + Serial.print("Sending telemetry "); + Serial.println(telemetry.c_str()); + + client.publish(CLIENT_TELEMETRY_TOPIC.c_str(), telemetry.c_str()); + + delay(10 * 60 * 1000); +} diff --git a/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/test/README b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal/temperature-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/2-farm/lessons/1-predict-plant-growth/code-server/temperature-sensor-server/app.py b/2-farm/lessons/1-predict-plant-growth/code-server/temperature-sensor-server/app.py new file mode 100644 index 00000000..bdf0bb8e --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-server/temperature-sensor-server/app.py @@ -0,0 +1,41 @@ +import json +import time + +import paho.mqtt.client as mqtt + +from os import path +import csv +import datetime + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'temperature_sensor_server' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +temperature_file_name = 'temperature.csv' +fieldnames = ['date', 'temperature'] + +if not path.exists(temperature_file_name): + with open(temperature_file_name, mode='w') as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=fieldnames) + writer.writeheader() + +def handle_telemetry(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + with open(temperature_file_name, mode='a') as temperature_file: + temperature_writer = csv.DictWriter(temperature_file, fieldnames=fieldnames) + temperature_writer.writerow({'date' : datetime.date.today(), 'temperature' : payload['temperature']}) + +mqtt_client.subscribe(client_telemetry_topic) +mqtt_client.on_message = handle_telemetry + +while True: + time.sleep(2) \ No newline at end of file diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/pi/temperature-sensor/app.py b/2-farm/lessons/1-predict-plant-growth/code-temperature/pi/temperature-sensor/app.py new file mode 100644 index 00000000..2bd080d2 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/pi/temperature-sensor/app.py @@ -0,0 +1,10 @@ +import time +from seeed_dht import DHT + +sensor = DHT("11", 5) + +while True: + _, temp = sensor.read() + print(f'Temperature {temp}ยฐC') + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/virtual-device/temperature-sensor/app.py b/2-farm/lessons/1-predict-plant-growth/code-temperature/virtual-device/temperature-sensor/app.py new file mode 100644 index 00000000..d6160794 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/virtual-device/temperature-sensor/app.py @@ -0,0 +1,13 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +from counterfit_shims_seeed_python_dht import DHT + +sensor = DHT("11", 5) + +while True: + _, temp = sensor.read() + print(f'Temperature {temp}ยฐC') + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/.gitignore b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/.vscode/extensions.json b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/include/README b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/lib/README b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/platformio.ini b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/platformio.ini new file mode 100644 index 00000000..7fa173cd --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/platformio.ini @@ -0,0 +1,16 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + seeed-studio/Grove Temperature And Humidity Sensor @ 1.0.1 diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/src/main.cpp b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/src/main.cpp new file mode 100644 index 00000000..f32d2fd7 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/src/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "config.h" + +DHT dht(D0, DHT11); + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + dht.begin(); +} + +void loop() +{ + float temp_hum_val[2] = {0}; + dht.readTempAndHumidity(temp_hum_val); + Serial.print("Temperature: "); + Serial.print(temp_hum_val[1]); + Serial.println ("ยฐC"); + + delay(10000); +} \ No newline at end of file diff --git a/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/test/README b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal/temperature-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/2-farm/lessons/1-predict-plant-growth/pi-temp.md b/2-farm/lessons/1-predict-plant-growth/pi-temp.md new file mode 100644 index 00000000..1eb15e4e --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/pi-temp.md @@ -0,0 +1,111 @@ +# Measure temperature - Raspberry Pi + +In this part of the lesson, you will add a temperature sensor to your Raspberry Pi. + +## Hardware + +The sensor you'll use is a [DHT11 humidity and temperature sensor](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html), combining 2 sensors in one package. This is fairly popular, with a number of commercially available sensors combining temperature, humidity and sometimes atmospheric pressure. The temperature sensor component is a negative temperature coefficient (NTC) thermistor, a thermistor where the resistance decreases as the temperature increases. + +This is a digital sensor, so has an onboard ADC to create a digital signal containing the temperature and humidity data that the microcontroller can read. + +### Connect the temperature sensor + +The Grove temperature sensor can be connected to the Raspberry Pi. + +#### Task + +Connect the temperature sensor + +![A grove temperature sensor](../../../images/grove-dht11.png) + +1. Insert one end of a Grove cable into the socket on the humidity and temperature sensor. It will only go in one way round. + +1. With the Raspberry Pi powered off, connect the other end of the Grove cable to the digital socket marked **D5** on the Grove Base hat attached to the Pi. This socket is the second from the left, on the row of sockets next to the GPIO pins. + +![The grove temperature sensor connected to socket A0](../../../images/pi-temperature-sensor.png) + +## Program the temperature sensor + +The device can now be programmed to use the attached temperature sensor. + +### Task + +Program the device. + +1. Power up the Pi and wait for it to boot + +1. Launch VS Code, either directly on the Pi, or connect via the Remote SSH extension. + + > โš ๏ธ You can refer to [the instructions for setting up and launch VS Code in lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/pi.md). + +1. From the terminal, create a new folder in the `pi` users home directory called `temperature-sensor`. Create a file in this folder called `app.py`: + + ```sh + mkdir temperature-sensor + cd temperature-sensor + touch app.py + ``` + +1. Open this folder in VS Code + +1. To use the temperature and humidity sensor, an additional Pip package needs to be installed. From the Terminal in VS Code, run the following command to install this Pip package on the Pi: + + ```sh + pip3 install seeed-python-dht + ``` + +1. Add the following code to the `app.py` file to import the required libraries: + + ```python + import time + from seeed_dht import DHT + ``` + + The `from seeed_dht import DHT` statement imports the `DHT` sensor class to interact with a Grove temperature sensor from the `seeed_dht` module. + +1. Add the following code after the code above to create an instance of the class that manages the temperature sensor: + + ```python + sensor = DHT("11", 5) + ``` + + This declares an instance of the `DHT` class that manages the **D**igital **H**umidity and **T**emperature sensor. The first parameter tells the code the sensor being used is the *DHT11* sensor - the library you are using supports other variants of this sensor. The second parameter tells the code the sensor is connected to digital port `D5` on the Grove base hat. + + > โœ… Remember, all the sockets have unique pin numbers. Pins 0, 2, 4, and 6 are analog pins, pins 5, 16, 18, 22, 24, and 26 are digital pins. + +1. Add an infinite loop after the code above to poll the temperature sensor value and print it to the console: + + ```python + while True: + _, temp = sensor.read() + print(f'Temperature {temp}ยฐC') + ``` + + The call to `sensor.read()` returns a tuple of humidity and temperature. You only need the temperature value, so the humidity is ignored. The temperature value is then printed to the console. + +1. Add a small sleep of ten seconds at the end of the `loop` as the temperature levels don't need to be checked continuously. A sleep reduces the power consumption of the device. + + ```python + time.sleep(10) + ``` + +1. From the VS Code Terminal, run the following to run your Python app: + + ```sh + python3 app.py + ``` + + You should see temperature values being output to the console. Use something to warm the sensor, such as pressing your thumb on it, or using a fan to see the values change: + + ```output + pi@raspberrypi:~/temperature-sensor $ python3 app.py + Temperature 26ยฐC + Temperature 26ยฐC + Temperature 28ยฐC + Temperature 30ยฐC + Temperature 32ยฐC + ``` + +> ๐Ÿ’ You can find this code in the [code-temperature/pi](code-temperature/pi) folder. + +๐Ÿ˜€ Your temperature sensor program was a success! diff --git a/2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md b/2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md new file mode 100644 index 00000000..d81f4a11 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md @@ -0,0 +1,57 @@ +# Publish temperature - Virtual IoT Hardware and Raspberry Pi + +In this part of the lesson, you will publish the temperature values detected by the Raspberry Pi or Virtual IoT Device over MQTT so they can be used later to calculate GDD. + +## Publish the temperature + +Once the temperature has been read, it can be published over MQTT to some 'server' code that will read the values, and store them ready to be used for a GDD calculation. + +### Task - publish the temperature + +Program the device to publish the temperature data. + +1. Open the `temperature-sensor` app project if it's not already open + +1. Repeat the steps you did in lesson 4 to connect to MQTT and send telemetry, You will be using the same public Mosquitto broker. + + The steps for this are: + + - Add the MQTT pip package + - Add the code to connect to the MQTT broker + - Add the code to publish telemetry + + > โš ๏ธ Refer to the [instructions for connecting to MQTT](../../../1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md) and the [instructions for sending telemetry](../../../1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md) from lesson 4 if needed. + +1. Make sure the `client_name` reflects this projects name: + + ```python + client_name = id + 'temperature_sensor_client' + ``` + +1. For the telemetry, instead of sending a light value, send the temperature value read from the DHT sensor in a property on the JSON document called `temperature`: + + ```python + _, temp = sensor.read() + telemetry = json.dumps({'temperature' : temp}) + ``` + +1. The temperature value doesn't need to be read very often - it won't change much in a short space of time, so set the `time.sleep` to 10 minutes: + + ```cpp + time.sleep(10 * 60); + ``` + + > ๐Ÿ’ The `sleep` function takes the time in seconds, so to make it easier to read the value is passed as the result of a calculation. 60s in a minute, so 10 x (60s in a minute) gives a 10 minute delay. + +1. Run the code in the same way as you ran the code from the previous part of the assignment. If you are using a virtual IoT device, then make sure the CounterFit app is running and the humidity and temperature sensors have been created on the correct pins. + + ```output + pi@raspberrypi:~/temperature-sensor $ python3 app.py + MQTT connected! + Sending telemetry {"temperature": 25} + Sending telemetry {"temperature": 25} + ``` + +> ๐Ÿ’ You can find this code in the [code-publish-temperature/virtual-device](code-publish-temperature/virtual-device) folder or the [code-publish-temperature/pi](code-publish-temperature/pi) folder. + +๐Ÿ˜€ You have successfully published the temperature as telemetry from your device. diff --git a/2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md b/2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md new file mode 100644 index 00000000..9e4fa256 --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md @@ -0,0 +1,140 @@ +# Measure temperature - Virtual IoT Hardware + +In this part of the lesson, you will add a temperature sensor to your virtual IoT device. + +## Virtual Hardware + +The virtual IoT device will use a simulated Grove Digital Humidity and Temperature sensor. This keeps this lab the same as using a Raspberry Pi with a physical Grove DHT11 sensor. + +The sensor combines a **temperature sensor** with a **humidity sensor**, but in this lab you are only interested in the temperature sensor component. In a physical IoT device, the temperature sensor would be a [thermistor](https://wikipedia.org/wiki/Thermistor) that measures temperature by sensing a change in resistance as temperature changes. Temperature sensors are usually digital sensors the internally convert the resistance measured into a temperature in degrees Celsius (or Kelvin, or Fahrenheit). + +### Add the sensors to CounterFit + +To use a virtual humidity and temperature sensor, you need to add the two sensors to the CounterFit app + +#### Task - add the sensors to CounterFit + +Add the humidity and temperature sensors to the CounterFit app. + +1. Create a new Python app on your computer in a folder called `temperature-sensor` with a single file called `app.py` and a Python virtual environment, and add the CounterFit pip packages. + + > โš ๏ธ You can refer to [the instructions for creating and setting up a CounterFit Python project in lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/virtual-device.md). + +1. Install an additional Pip package to install a CounterFit shim for the DHT11 sensor. Make sure you are installing this from a terminal with the virtual environment activated. + + ```sh + pip install counterfit-shims-seeed-python-dht + ``` + +1. Make sure the CounterFit web app is running + +1. Create a humidity sensor: + + 1. In the *Create sensor* box in the *Sensors* pane, drop down the *Sensor type* box and select *Humidity*. + + 1. Leave the *Units* set to *Percentage* + + 1. Ensure the *Pin* is set to *5* + + 1. Select the **Add** button to create the humidity sensor on Pin 5 + + ![The humidity sensor settings](../../../images/counterfit-create-humidity-sensor.png) + + The humidity sensor will be created and appear in the sensors list. + + ![The humidity sensor created](../../../images/counterfit-humidity-sensor.png) + +1. Create a temperature sensor: + + 1. In the *Create sensor* box in the *Sensors* pane, drop down the *Sensor type* box and select *Temperature*. + + 1. Leave the *Units* set to *Celsius* + + 1. Ensure the *Pin* is set to *6* + + 1. Select the **Add** button to create the temperature sensor on Pin 6 + + ![The temperature sensor settings](../../../images/counterfit-create-temperature-sensor.png) + + The temperature sensor will be created and appear in the sensors list. + + ![The temperature sensor created](../../../images/counterfit-temperature-sensor.png) + +## Program the temperature sensor app + +The temperature sensor app can now be programmed using the CounterFit sensors. + +### Task - program the temperature sensor app + +Program the temperature sensor app. + +1. Make sure the `temperature-sensor` app is open in VS Code + +1. Open the `app.py` file + +1. Add the following code to the top of `app.py` to connect the app to CounterFit: + + ```python + from counterfit_connection import CounterFitConnection + CounterFitConnection.init('127.0.0.1', 5000) + ``` + +1. Add the following code to the `app.py` file to import the required libraries: + + ```python + import time + from counterfit_shims_seeed_python_dht import DHT + ``` + + The `from seeed_dht import DHT` statement imports the `DHT` sensor class to interact with a virtual Grove temperature sensor using a shim from the `counterfit_shims_seeed_python_dht` module. + +1. Add the following code after the code above to create an instance of the class that manages the virtual humidity and temperature sensor: + + ```python + sensor = DHT("11", 5) + ``` + + This declares an instance of the `DHT` class that manages the virtual **D**igital **H**umidity and **T**emperature sensor. The first parameter tells the code the sensor being used is a virtual *DHT11* sensor. The second parameter tells the code the sensor is connected to port `5`. + + > ๐Ÿ’ CounterFit simulates this combined humidity and temperature sensor by connecting to 2 sensors, a humidity sensor on the pin given when the `DHT` class is created, and a temperature sensor that runs on the next pin. If the humidity sensor is on pin 5, the shim expects the temperatures sensor to be on pin 6. + +1. Add an infinite loop after the code above to poll the temperature sensor value and print it to the console: + + ```python + while True: + _, temp = sensor.read() + print(f'Temperature {temp}ยฐC') + ``` + + The call to `sensor.read()` returns a tuple of humidity and temperature. You only need the temperature value, so the humidity is ignored. The temperature value is then printed to the console. + +1. Add a small sleep of ten seconds at the end of the `loop` as the temperature levels don't need to be checked continuously. A sleep reduces the power consumption of the device. + + ```python + time.sleep(10) + ``` + +1. From the VS Code Terminal with an activated virtual environment, run the following to run your Python app: + + ```sh + python app.py + ``` + +1. From the CounterFit app, change the value of the temperature sensor that will be read by the app. You can do this in one of two ways: + + * Enter a number in the *Value* box for the temperature sensor, then select the **Set** button. The number you enter will be the value returned by the sensor. + + * Check the *Random* checkbox, and enter a *Min* and *Max* value, then select the **Set** button. Every time the sensor reads a value, it will read a random number between *Min* and *Max*. + + You should see the values you set appearing in the console. Change the *Value* or the *Random* settings to see the value change. + + ```output + (.venv) โžœ temperature-sensor python app.py + Temperature 28.25ยฐC + Temperature 30.71ยฐC + Temperature 25.17ยฐC + ``` + +> ๐Ÿ’ You can find this code in the [code-temperature/virtual-device](code-temperature/virtual-device) folder. + +๐Ÿ˜€ Your temperature sensor program was a success! diff --git a/2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md b/2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md new file mode 100644 index 00000000..77a1cffa --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md @@ -0,0 +1,68 @@ +# Publish temperature - Wio Terminal + +In this part of the lesson, you will publish the temperature values detected by the Wio Terminal over MQTT so they can be used later to calculate GDD. + +## Publish the temperature + +Once the temperature has been read, it can be published over MQTT to some 'server' code that will read the values, and store them ready to be used for a GDD calculation. Microcontrollers don't read the time from the Internet and track the time with a real-time clock out of the box, the device needs to be programmed to do this, assuming it has the necessary hardware. + +To simplify things for this lesson, the time won't be sent with the sensor data, instead it can be added by the server code later when it receives the messages. + +### Task + +Program the device to publish the temperature data. + +1. Open the `temperature-sensor` Wio Terminal project + +1. Repeat the steps you did in lesson 4 to connect to MQTT and send telemetry, You will be using the same public Mosquitto broker. + + The steps for this are: + + - Add the Seeed WiFi and MQTT libraries to the `.ini` file + - Add the config file and code to connect to WiFi + - Add the code to connect to the MQTT broker + - Add the code to publish telemetry + + > โš ๏ธ Refer to the [instructions for connecting to MQTT](../../../1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md) and the [instructions for sending telemetry](../../../1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md) from lesson 4 if needed. + +1. Make sure the `CLIENT_NAME` in the `config.h` header file reflects this project: + + ```cpp + const string CLIENT_NAME = ID + "temperature_sensor_client"; + ``` + +1. For the telemetry, instead of sending a light value, send the temperature value read from the DHT sensor in a property on the JSON document called `temperature` by changing the `loop` function in `main.cpp`: + + ```cpp + float temp_hum_val[2] = {0}; + dht.readTempAndHumidity(temp_hum_val); + + DynamicJsonDocument doc(1024); + doc["temperature"] = temp_hum_val[1]; + ``` + +1. The temperature value doesn't need to be read very often - it won't change much in a short space of time, so set the `delay` in the `loop` function to 10 minutes: + + ```cpp + delay(10 * 60 * 1000); + ``` + + > ๐Ÿ’ The `delay` function takes the time in milliseconds, so to make it easier to read the value is passed as the result of a calculation. 1,000ms in a second, 60s in a minute, so 10 x (60s in a minute) x (1000ms in a second) gives a 10 minute delay. + +1. Upload this to your Wio Terminal, and use the serial monitor to see the temperature being sent to the MQTT broker. + + ```output + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Connecting to WiFi.. + Connected! + Attempting MQTT connection...connected + Sending telemetry {"temperature":25} + Sending telemetry {"temperature":25} + ``` + +> ๐Ÿ’ You can find this code in the [code-publish-temperature/wio-terminal](code-publish-temperature/wio-terminal) folder. + +๐Ÿ˜€ You have successfully published the temperature as telemetry from your device. diff --git a/2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md b/2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md new file mode 100644 index 00000000..c867bbfb --- /dev/null +++ b/2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md @@ -0,0 +1,129 @@ +# Measure temperature - Wio Terminal + +In this part of the lesson, you will add a temperature sensor to your Wio Terminal, and read temperature values from it. + +## Hardware + +The Wio Terminal needs a temperature sensor. + +The sensor you'll use is a [DHT11 humidity and temperature sensor](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html), combining 2 sensors in one package. This is fairly popular, with a number of commercially available sensors combining temperature, humidity and sometimes atmospheric pressure. The temperature sensor component is a negative temperature coefficient (NTC) thermistor, a thermistor where the resistance decreases as the temperature increases. + +This is a digital sensor, so has an onboard ADC to create a digital signal containing the temperature and humidity data that the microcontroller can read. + +### Connect the temperature sensor + +The Grove temperature sensor can be connected to the Wio Terminals digital port. + +#### Task - connect the temperature sensor + +Connect the temperature sensor. + +![A grove temperature sensor](../../../images/grove-dht11.png) + +1. Insert one end of a Grove cable into the socket on the humidity and temperature sensor. It will only go in one way round. + +1. With the Wio Terminal disconnected from your computer or other power supply, connect the other end of the Grove cable to the right-hand side Grove socket on the Wio Terminal as you look at the screen. This is the socket farthest away from the power button. + +![The grove temperature sensor connected to the right hand socket](../../../images/wio-temperature-sensor.png) + +## Program the temperature sensor + +The Wio Terminal can now be programmed to use the attached temperature sensor. + +### Task program the temperature sensor + +Program the device. + +1. Create a brand new Wio Terminal project using PlatformIO. Call this project `temperature-sensor`. Add code in the `setup` function to configure the serial port. + + > โš ๏ธ You can refer to [the instructions for creating a PlatformIO project in project 1, lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#create-a-platformio-project). + +1. Add a library dependency for the Seeed Grove Humidity and Temperature sensor library to the projects `platformio.ini` file: + + ```ini + lib_deps = + seeed-studio/Grove Temperature And Humidity Sensor @ 1.0.1 + ``` + + > โš ๏ธ You can refer to [the instructions for adding libraries to a PlatformIO project in project 1, lesson 4 if needed](../../../1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md#install-the-wifi-and-mqtt-arduino-libraries). + +1. Add the following `#include` directives to the top of the file, under the existing `#include `: + + ```cpp + #include + #include + ``` + + This imports files needed to interact with the sensor. The `DHT.h` header file contains the code for the sensor itself, and adding the `SPI.h` header ensures the code needed to talk to the sensor is linked in when the app is compiled. + +1. Before the`setup` function, declare the DHT sensor: + + ```cpp + DHT dht(D0, DHT11); + ``` + + This declares an instance of the `DHT` class that manages the **D**igital **H**umidity and **T**emperature sensor. This is connected to port `D0`, the right-hand-side Grove socket on the Wio Terminal. The second parameter tells the code the sensor being used is the *DHT11* sensor - the library you are using supports other variants of this sensor. + +1. In the `setup` function, add code to set up the serial connection: + + ```cpp + void setup() + { + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + } + ``` + +1. At the end of the `setup` function, after the last `delay`, add a call to start the DHT sensor: + + ```cpp + dht.begin(); + ``` + +1. In the `loop` function, add code to call the sensor and print the temperature to the serial port: + + ```cpp + void loop() + { + float temp_hum_val[2] = {0}; + dht.readTempAndHumidity(temp_hum_val); + Serial.print("Temperature: "); + Serial.print(temp_hum_val[1]); + Serial.println ("ยฐC"); + + delay(10000); + } + ``` + + This code declares an empty array of 2 floats, and passes this to the call to `readTempAndHumidity` on the `DHT` instance. This call populates the array with 2 values - the humidity goes in the 0th item in the array (remember in C++ arrays are 0-based, so the 0th item is the 'first' item in the array), and the temperature goes into the 1st item. + + The temperature is read from the 1st item in the array, and printed to the serial port. + + > ๐Ÿ‡บ๐Ÿ‡ธ The temperature is read in Celsius. For Americans, to convert this to Fahrenheit, divide the Celsius value read by 5, then multiply by 9, then add 32. For example, a temperature reading of 20ยฐC becomes ((20/5)*9) + 32 = 68ยฐF. + +1. Build and upload the code to the Wio Terminal. + + > โš ๏ธ You can refer to [the instructions for creating a PlatformIO project in project 1, lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#write-the-hello-world-app). + +1. Once uploaded, you can monitor the temperature using the serial monitor: + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Temperature: 25.00ยฐC + Temperature: 25.00ยฐC + Temperature: 25.00ยฐC + Temperature: 24.00ยฐC + ``` + +> ๐Ÿ’ You can find this code in the [code-temperature/wio-terminal](code-temperature/wio-terminal) folder. + +๐Ÿ˜€ Your temperature sensor program was a success! diff --git a/2-farm/lessons/2-detect-soil-moisture/README.md b/2-farm/lessons/2-detect-soil-moisture/README.md new file mode 100644 index 00000000..59bcfac8 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/README.md @@ -0,0 +1,276 @@ +# Detect soil moisture + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/11) + +## Introduction + +In the last lesson we looked at measuring an ambient property and using it to predict plant growth. Temperature can be controlled, but it is expensive to do so, requiring controlled environments. The easiest ambient property to control for plants is water - something that is controlled everyday from large-scale irrigation systems to young kids with watering cans watering their gardens. + +![A child watering a garden](../../../images/child-watering-garden.jpg) + +In this lesson you will learn about measuring soil moisture, and in the next lesson you will learn how to control an automated watering system. This lesson introduces a third sensor, you've already used a light sensor, a temperature sensor, so in this lesson you will also learn more about how sensors and actuators communicate with IoT devices to understand more about how a soil moisture sensor can send data to an IoT device. + +In this lesson we'll cover: + +* [Soil moisture](#soil-moisture) +* [How sensors communicate with IoT devices](#how-sensors-communicate-with-iot-devices) +* [Measure the moisture levels in soil](#measure-the-moisture-levels-in-soil) +* [Sensor calibration](#sensor-calibration) + +## Soil moisture + +Plants require water to grow. They absorb water throughout the entire plant, with the majority being absorbed by the root system. Water is used by the plant for three things: + +* [Photosynthesis](https://wikipedia.org/wiki/Photosynthesis) - plants react water with carbon dioxide and light to produce carbohydrates and oxygen +* [Transpiration](https://wikipedia.org/wiki/Transpiration) - plants use water for diffusion of carbon dioxide from the air into the plant via pores in the leaves. This process also carries nutrients around the plant, and cools the plant, similar to how humans sweat. +* Structure - plants also need water to maintain their structure - they are 90% water (as opposed to humans at only 60%), and this water keeps the cells rigid. If a plant doesn't have enough water it will wilt and eventually die. + +![Water is absorbed through plant roots then carried around the plant, being used for photosynthesis and plant structure](../../../images/transpiration.png) + +***Water is absorbed through plant roots then carried around the plant, being used for photosynthesis and plant structure. Plant by Alex Muravev / Plant Cell by Lรฉa Lortal - all from the [Noun Project](https://thenounproject.com)*** + +โœ… Do some research: how much water is lost through transpiration? + +The root system provides water from moisture in the soil where the plant grows. Too little water in the soil and the plant cannot absorb enough to grow, too much water and roots cannot absorb enough oxygen needed to function. This leads to roots dying and the plant unable to get enough nutrients to survive. + +For a farmer to get the best plant growth, the soil needs to be not too wet and not too dry. IoT devices can help with this by measuring soil moisture, allowing a farmer to only water when needed. + +### Ways to measure soil moisture + +There are a range of different types of sensor you can use to measure soil moisture: + +* Resistive - a resistive sensor has 2 probes that go into the soil. An electric current is sent to one probe, and received by the other. The sensor then measures the resistance of the soil - measuring how much the current drops at the second probe. Water is a good conductor of electricity, so the higher the water content of the soil, the lower the resistance. + + ![A resistive soil moisture sensor](../../../images/resistive-soil-moisture-sensor.png) + + > ๐Ÿ’ You can build a resistive soil moisture sensor using two pieces of metal, such as nails, separated by a couple of centimeters, and measuring the resistance between them using a multimeter. + +* Capacitive - a capacitive moisture sensor measures the amount of electric charge that can be stored across a positive and a negative electrical plate, or [capacitance](https://wikipedia.org/wiki/Capacitance). The capacitance of soil changes as the moisture level changes, and this can be converted to a voltage that can be measured by an IoT device. The wetter the soil, the lower the voltage that comes out. + + ![A capacitive soil moisture sensor](../../../images/grove-capacitive-soil-moisture-sensor.png) + +These are both analog sensors, returning a voltage to indicate soil moisture. So how does this voltage get to your code? Before going any further with these sensors, lets look at how sensors and actuators communicate with IoT devices. + +## How sensors communicate with IoT devices + +So far in these lessons you've learned about a number of sensors and actuators, and these have been communicating with your IoT dev kit if you've been doing the physical hardware labs. But how does this communication work? How does a resistance measurement from a soil moisture sensor become a number you can use from code? + +To communicate with most sensors and actuators you need some hardware, and a communication protocol - that is a well defined way for data to be sent and received. Take for example a capacitive soil moisture sensor: + +* How is this sensor connected to the IoT device? +* If it measures a voltage that is an analog signal, it will need an ADC to create a digital representation of the value, and this value is sent as an alternating a voltage to send 0s and 1s - but how long is each bit sent for? +* If the sensor returns a digital value, that will be a stream of 0s and 1, again how long is each bit sent for? +* If the voltage is high for 0.1s is that a single 1 bit, or 2 consecutive 1 bits, or 10? +* At what point does the number start? Is `00001101` 25, or are the first 5 bits the end of the previous value? + +The hardware provides the physical connectivity over which the data is sent, the different communication protocols ensure that the data is sent or received in the correct way so it can be interpreted. + +### General Purpose Input Output (GPIO) pins + +GPIO is a set of pins you can use to connect hardware to your IoT device, and are often available on IoT developer kits such as the Raspberry Pi or Wio Terminal. You can use the various communication protocols covered in this section over the GPIO pins. Some GPIO pins provide a voltage, usually 3.3V or 5V, some pins are ground, and others can be programmatically set to either send a voltage (output), or receive a voltage (input). + +> ๐Ÿ’ An electrical circuit needs to connect a voltage to ground via whatever circuitry you are using. You can think of voltage as the positive (+ve) terminal of a battery and ground as the negative (-ve) terminal. + +You can use GPIO pins directly with some digital sensors and actuators when you only care about on or off values - on referred to as high, off as low. Some examples are: + +* Button. You can connect a button between a 5V pin and a pin set to input. When you press the button it completes a circuit between the 5V pin, through the button to the input pin. From code you can read the voltage at the input pin, and if it is high (5V) then the button is pressed, if it is low (0v) then the button is not pressed. Remember the actual voltage itself is not read, instead you get a digital signal of 1 or 0 depending on if the voltage is above a threshold or not. + + ![A button is sent 5 volts. When not pressed it returns 0 volts, or 0, when pressed it returns 5 volts, or 1](../../../images/button-with-digital.png) + + ***A button is sent 5 volts. When not pressed it returns 0 volts, or 0, when pressed it returns 5 volts, or 1. Microcontroller by Template / Button by Dan Hetteix - all from the [Noun Project](https://thenounproject.com)*** + +* LED. You can connect an LED between an output pin and a ground pin (using a resistor otherwise you'll burn out the LED). From code you can set the output pin to high and it will send 3.3V, making a circuit from the 3.3V pin, through the LED, to the ground pin. This will light the LED. + + ![An LED is sent a signal of 0 (3.3V), which lights the LED. If it is sent 0 (0v), the LED is not lit.](../../../images/led-digital-control.png) + + ***An LED is sent a signal of 0 (3.3V), which lights the LED. If it is sent 0 (0v), the LED is not lit. LED by abderraouf omara / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +For more advanced sensors, you can use GPIO pins to send and receive digital data directly with digital sensors and actuators, or via controller boards with ADCs and DACs to talk to analog sensors and actuators. + +> ๐Ÿ’ if you are using a Raspberry Pi for these labs, the Grove Base Hat has hardware to convert analog sensor signals to digital to send over GPIO. + +โœ… If you have an IoT device with GPIO pins, locate these pins and find a diagram indicating which pins are voltage, ground or programmable. + +### Analog pins + +Some devices, such as Arduino devices, provide analog pins. These are the same as GPIO pins, but instead of only supporting a digital signal, they have an ADC to convert voltage ranges to numerical values. Usually the ADC has a 10-bit resolution, meaning it converts voltages to a value of 0-1,023. + +For example, on a 3.3V board, if the sensor returns 3.3V, the value returned would be 1,023. If the voltage returned was 1.65v, the value returned will be 511. + +![A soil moisture sensor sent 3.3V and returning 1.65v, or a reading of 511](../../../images/analog-sensor-voltage.png) + +***A soil moisture sensor sent 3.3V and returning 1.65v, or a reading of 511. probe by Adnen Kadri / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +> ๐Ÿ’ Back in lesson 3, the light sensor returned a value from 0-1,023. If you are using a Wio Terminal, the sensor was connected to an analog pin. If you are using a Raspberry Pi, then the sensor was connected to an analog pin on the base hat that has an integrated ADC to communicate over the GPIO pins. The virtual device was set to send a value from 0-1,023 to simulate an analog pin. + +Soil moisture sensors rely on voltages, so will use analog pins and give values from 0-1,023. + +### Inter Integrated Circuit (I2C) + +I2C, pronounced *I-squared-C*, is a multi-controller, multi-peripheral protocol, with any connected device able to act as a controller or peripheral communicating over the I2C bus (the name for a communication system that transfers data). Data is sent as addressed packets, with each packet containing the address of the connected device it is intended for. + +> ๐Ÿ’ This model used to be referred to as master/slave, but this terminology is being dropped due to its association with slavery. The [Open Source Hardware Association has adopted controller/peripheral](https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/), but you may still see references to the old terminology. + +Devices have an address that is used when they connect to the I2C bus, and is usually hard coded on the device. For example, each type of Grove sensor from Seeed has the same address, so all the light sensors have the same address, all the buttons have the same address that is different from the light sensor address. Some devices have ways to change the address, by changing jumper settings or soldering pins together. + +I2C has a bus made of 2 main wires, along with 2 power wires: + +| Wire | Name | Description | +| ---- | --------- | ----------- | +| SDA | Serial Data | This wire is for sending data between devices. | +| SCL | Serial Clock | This wire sends a clock signal at a rate set by the controller. | +| VCC | Voltage common collector | The power supply for the devices. This is connected to the SDA and SCL wires to provide their power via a pull-up resistor that switches the signal off when no device is the controller. | +| GND | Ground | This provides a common ground for the electrical circuit. | + +![I2C bus with 3 devices connected to the SDA and SCL wires, sharing a common ground wire](../../../images/i2c.png) + +***I2C bus with 3 devices connected to the SDA and SCL wires, sharing a common ground wire. Microcontroller by Template / LED by abderraouf omara / ldr by Eucalyp - all from the [Noun Project](https://thenounproject.com)*** + +To send data, one device will issue a start condition to show it is ready to send data. It will then become the controller. The controller then sends the address of the device that it wants to communicate with, along with if it wants to read or write data. After the data has been transmitted, the controller sends a stop condition to indicate that it has finished. After this another device can become the controller and send or receive data. + +I2C has speed limits, with 3 different modes running at fixed speeds. The fastest is High Speed mode with a maximum speed of 3.4Mbps (megabits per second), though very few devices support that speed. The Raspberry Pi for example, is limited to fast mode at 400Kbps (kilobits per second). Standard mode runs at 100Kbps. + +> ๐Ÿ’ If you are using a Raspberry Pi with a Grove Base hat as your IoT hardware, you will be able to see a number of I2C sockets on the board you can use to communicate with I2C sensors. Analog Grove sensors also use I2C with an ADC to send analog values as digital data, so the light sensor you used simulated an analog pin, with the value sent over I2C as the Raspberry Pi only supports digital pins. + +### Universal asynchronous receiver-transmitter (UART) + +UART involves physical circuitry that allows two devices to communicate. Each device has 2 communication pins - transmit (Tx) and receive (Rx), with the Tx pin of the first device connected to the Rx pin of the second, and with the Tx pin of the second device connected to the Rx pin of the first. This allows data to be sent in both directions. + +* Device 1 transmits data from its Tx pin, which is received by device 2 on it's Rx pin +* Device 1 receives data on its Rx pin that is transmitted by device 2 from its Tx pin + +![UART with the Tx pin on one chip connected to the Rx pin on another, and vice versa](../../../images/uart.png) + +***UART with the Tx pin on one chip connected to the Rx pin on another, and vice versa. chip by Astatine Lab - all from the [Noun Project](https://thenounproject.com)*** + +> ๐ŸŽ“ The data is sent one bit at a time, and this is known as *serial* communication. Most operating systems and microcontrollers have *serial ports*, that is connections that can send and receive serial data that are available to your code. + +UART devices have a [baud rate](https://wikipedia.org/wiki/Symbol_rate) (also known as Symbol rate), which is the speed that data will be sent and received in bits per second. A common baud rate is 9,600, meaning 9,600 bits (0s and 1s) of data are sent each second. + +UART uses start and stop bits - that is is sends a start bit to indicate that it's about to send a byte (8 bits) of data, then a stop bit after it's sent the 8 bits. + +UART speed is dependent on hardware, but even the fastest implementations don't exceed 6.5 Mbps (megabits per second, or millions of bits, 0 or 1, sent per second). + +You can use UART over GPIO pins - you can set one pin as Tx and another as Rx, then connect these to another device. + +> ๐Ÿ’ If you are using a Raspberry Pi with a Grove Base hat as your IoT hardware, you will be able to see a UART socket on the board you can use to communicate with sensors that use the UART protocol. + +### Serial Peripheral Interface (SPI) + +SPI is designed for communicating over short distances, such as on a microcontroller to talk to a storage device such as flash memory. It is based on a controller/peripheral model with a single controller (usually the processor of the IoT device) interacting with multiple peripherals. The controller controls everything by selecting a peripheral and sending or requesting data. + +> ๐Ÿ’ Like I2C, the terms controller and peripheral are recent changes, so you may see the older terms still used. + +SPI controllers use 3 wires, along with 1 extra wire per peripheral. Peripherals use 4 wires. These wires are: + +| Wire | Name | Description | +| ---- | --------- | ----------- | +| COPI | Controller Output, Peripheral Input | This wire is for sending data from the controller to the peripheral. | +| CIPO | Controller Input, peripheral Output | This wire is for sending data from the peripheral to the controller. | +| SCLK | Serial Clock | This wire sends a clock signal at a rate set by the controller. | +| CS | Chip Select | The controller has multiple wires, one per peripheral, and each wire connects to the CS wire on the corresponding peripheral. | + +![SPI with on controller and two peripherals](../../../images/spi.png) + +***SPI with on controller and two peripherals. chip by Astatine Lab - all from the [Noun Project](https://thenounproject.com)*** + +The CS wire is used to activate one peripheral at a time, communicating over the COPI and CIPO wires. When the controller needs to change peripheral, it deactivates the CS wire connected to currently active peripheral, then activates the wire connected to the peripheral it wants to communicate with next. + +SPI is *full-duplex*, meaning the controller can send and receive data at the same time from the same peripheral using the COPI and CIPO wires. SPI uses a clock signal on the SCLK wire to keep the devices in sync, so unlike sending directly over UART it doesn't need start and stop bits. + +There are no defined speed limits for SPI, with implementations often able to transmit multiple megabytes of data per second. + +IoT developer kits often support SPI over some of the GPIO pins. For example, on a Raspberry Pi you can use GPIO pins 19, 21, 23, 24 and 26 for SPI. + +### Wireless + +Some sensors can communicate over standard wireless protocols, such as Bluetooth (mainly Bluetooth Low Energy, or BLE), LoRaWAN (a **Lo**ng **Ra**nge low power networking protocol), or WiFi. These allow for remote sensors not physically connected to an IoT device. + +One such example is in commercial soil moisture sensors. These will measure soil moisture in a field, then send the data over LoRaWan to a hub device, which will process the data or send it over the Internet. This allows the sensor to be away from the IoT device that manages the data, reducing power consumption and the need for large WiFi networks or long cables. + +BLE is popular for advanced sensors such as fitness trackers work on the wrist. These combine multiple sensors and send the sensor data to an IoT device in the form of your phone via BLE. + +โœ… Do you have any bluetooth sensors on your person, in your house or in your school? These might include temperature sensors, occupancy sensors, device trackers and fitness devices. + +One popular way for commercial devices to connect is Zigbee. Zigbee uses WiFi to form mesh networks between devices, where each device connects to as many nearby devices as possible, forming a large number of connections like a spiders web. When one device wants to send a message to the Internet it can send it to the nearest devices, which then forward it on to other nearby devices and so on, until it reaches a coordinator and can be sent to the Internet. + +> ๐Ÿ The name Zigbee refers to the waggle dance of honey bees after their return to the beehive. + +## Measure the moisture levels in soil + +You can measure the moisture level in soil using a soil moisture sensor, an IoT device, and a house plant or nearby patch of soil. + +### Task - measure soil moisture + +Work through the relevant guide to measure soil moisture using your IoT device: + +* [Arduino - Wio Terminal](wio-terminal-soil-moisture.md) +* [Single-board computer - Raspberry Pi](pi-soil-moisture.md) +* [Single-board computer - Virtual device](virtual-device-soil-moisture.md) + +## Sensor calibration + +Sensors rely on measuring electrical properties such as resistance or capacitance. + +> ๐ŸŽ“ Resistance, measured in ohms (โ„ฆ) is how much opposition there is to the electric current travelling through something. When a voltage is applied to a material, the amount of current that passes through it is dependant on the resistance of the material. You can read more on the [electrical resistance page on Wikipedia](https://wikipedia.org/wiki/Electrical_resistance_and_conductance). + +> ๐ŸŽ“ Capacitance, measured in farads (F), is the ability of a component or circuit to collect and store electrical energy. You can read more on capacitance on the [capacitance page on Wikipedia](https://wikipedia.org/wiki/Capacitance). + +These measurements are not always useful - imagine a temperature sensor that gave you a measurement of 22.5Kโ„ฆ! Instead the value measured needs to be converted into a useful unit by being calibrated - that is matching the values measured to the quantity measured to allow new measurements to be converted to the right unit. + +Some sensors come pre-calibrated. For example the temperature sensor you used in the last lesson was already calibrated so that it can return a temperature measurement in ยฐC. In the factory the first sensor created would be exposed to a range of known temperatures and the resistance measured. This would then be used to build a calculation that can convert from the value measured in โ„ฆ (the unit of resistance) to ยฐC. + +> ๐Ÿ’ The formula to calculate resistance from temperature is called the [Steinhartโ€“Hart equation](https://wikipedia.org/wiki/Steinhartโ€“Hart_equation). + +### Soil moisture sensor calibration + +Soil moisture is measured using gravimetric or volumetric water content. + +* Gravimetric is the weight of water in a unit weight of soil measured, as the number of kilograms of water per kilogram of dry soil +* Volumetric is the volume of water in a unit volume of soil measured, as the number of cubic metres of water per cubic metres of dry soil + +> ๐Ÿ‡บ๐Ÿ‡ธ For Americans, because of the consistency of the units, these can be measured in pounds instead of kilograms or cubic feet instead of cubic metres. + +Soil moisture sensors measure electrical resistance or capacitance - this not only varies by soil moisture, but also soil type as the components in the soil can change its electrical characteristics. Ideally sensors should be calibrated - that is taking readings from the sensor and comparing them to measurements found using a more scientific approach. For example a lab can calculate the gravimetric soil moisture using samples of a specific field taken a few times a year, and these numbers used to calibrate the sensor, matching the sensor reading to the gravimetric soil moisture. + +![A graph of voltage vs soil moisture content](../../../images/soil-moisture-to-voltage.png) + +The graph above shows how to calibrate a sensor . The voltage is captured for a soil sample that is then measured in a lab by comparing the moist weight to the dry weight (by measuring the weight wet, then drying in an over and measuring dry). Once a few readings have been taken, this can be plotted on a graph and a line fitted to the points. This line can then be used to convert soil moisture sensor readings taken by an IoT device into actual soil moisture measurements. + +๐Ÿ’ For resistive soil moisture sensors, the voltage increases as soil moisture increases. For capacitive soil moisture sensors, the voltage decreases as soil moisture increases, so the graphs for these would slope downwards, not upwards. + +![A soil moisture value interpolated from the graph](../../../images/soil-moisture-to-voltage-with-reading.png) + +The graph above shows a voltage reading from a soil moisture sensor, and by following that to the line on the graph, the actual soil moisture can be calculated. + +This approach means the farmer only needs to get a few lab measurements for a field, then they can use IoT devices to measure soil moisture - drastically speeding up the time to take measurements. + +--- + +## ๐Ÿš€ Challenge + +Resistive and capacitive soil moisture sensors have a number of differences. What are these differences, and which type (if any) is the best for a farmer to use? Does this answer change between developing and developed countries? + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/12) + +## Review & Self Study + +Read up on the hardware and protocols used by sensors and actuators: + +* [GPIO Wikipedia page](https://wikipedia.org/wiki/General-purpose_input/output) +* [UART Wikipedia page](https://wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter) +* [SPI Wikipedia page](https://wikipedia.org/wiki/Serial_Peripheral_Interface) +* [I2C Wikipedia page](https://wikipedia.org/wiki/IยฒC) +* [Zigbee Wikipedia page](https://wikipedia.org/wiki/Zigbee) + +## Assignment + +[Calibrate your sensor](assignment.md) diff --git a/2-farm/lessons/2-detect-soil-moisture/assignment.md b/2-farm/lessons/2-detect-soil-moisture/assignment.md new file mode 100644 index 00000000..b7959d5a --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/assignment.md @@ -0,0 +1,47 @@ +# Calibrate your sensor + +## Instructions + +In this lesson you gathered soil moisture sensor readings, measured as values from 0-1023. To convert these into actual soil moisture readings, you need to calibrate your sensor. You can do this by taking readings from soil samples, then calculating the gravimetric soil moisture content from these samples. + +You will need to repeat these steps multiple times to get the readings needed, with different wetness of soil each time. + +1. Take a soil moisture reading using the soil moisture sensor. Write down this reading. + +1. Take a sample of the soil, and weigh it. Write down this weight. + +1. Dry the soil - a warm oven at 110ยฐC (230ยฐF) for a few hours is the best way, you can do this in sunlight, or place it in a warm, dry place until the soil is completely dry. It should be powdery and loose. + + > ๐Ÿ’ In a lab for the most accurate results you would dry in an oven for 48-72 hours. If you have drying ovens at your school, see if you can use these to dry for longer. The longer, the more dry the sample and the more accurate the results. + +1. Weigh the soil again. + + > ๐Ÿ”ฅ If you dried it in an oven make sure it has cooled down first! + +The gravimetric soil moisture is calculated as: + +![soil moisture % is weight wet minus weight dry, divided by weight dry, times 100](../../../images/gsm-calculation.png) + +* Wwet - the weight of the wet soil +* Wdry - the weight of the dry soil + +For example, say you have a soil sample that weights 212g wet, and 197g dry. + +![The calculation filled in](../../../images/gsm-calculation-example.png) + +* Wwet = 212g +* Wdry = 197g +* 212 - 197 = 15 +* 15 / 197 = 0.076 +* 0.076 * 100 = 7.6% + +In this example, the soil has a gravimetric soil moisture of 7.6%. + +Once you have the readings for at least 3 samples, plot a graph of soil moisture % to soil moisture sensor reading and add line to best fit the points. You can then use this to calculate the gravimetric soil moisture content for a given sensor reading by reading the value from the line. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Gather calibration data | Capture at least 3 calibration samples | Capture at least 2 calibration samples |Capture at least 1 calibration sample | +| Make a calibrated reading | Successfully plot the calibration graph and make a reading from the sensor, and convert it to gravimetric soil moisture content | Successfully plot the calibration graph | Not able to plot the graph | diff --git a/2-farm/lessons/2-detect-soil-moisture/code/pi/soil-moisture-sensor/app.py b/2-farm/lessons/2-detect-soil-moisture/code/pi/soil-moisture-sensor/app.py new file mode 100644 index 00000000..e276bed2 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/pi/soil-moisture-sensor/app.py @@ -0,0 +1,10 @@ +import time +from grove.adc import ADC + +adc = ADC() + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/2-detect-soil-moisture/code/virtual-device/soil-moisture-sensor/app.py b/2-farm/lessons/2-detect-soil-moisture/code/virtual-device/soil-moisture-sensor/app.py new file mode 100644 index 00000000..f3153c9c --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/virtual-device/soil-moisture-sensor/app.py @@ -0,0 +1,13 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +from counterfit_shims_grove.adc import ADC + +adc = ADC() + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/.gitignore b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/.vscode/extensions.json b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/include/README b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/lib/README b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/platformio.ini b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/platformio.ini new file mode 100644 index 00000000..4e03e890 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino diff --git a/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/src/main.cpp b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/src/main.cpp new file mode 100644 index 00000000..80a721da --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/src/main.cpp @@ -0,0 +1,16 @@ +#include + +void setup() +{ + pinMode(A0, INPUT); +} + +void loop() +{ + int soil_moisture = analogRead(A0); + + Serial.print("Soil Moisture: "); + Serial.println(soil_moisture); + + delay(5000); +} \ No newline at end of file diff --git a/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/test/README b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/code/wio-terminal/soil-moisture-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md b/2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md new file mode 100644 index 00000000..f2484341 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md @@ -0,0 +1,94 @@ +# Measure soil moisture - Raspberry Pi + +In this part of the lesson, you will add a capacitive soil moisture sensor to your Raspberry Pi, and read values from it. + +## Hardware + +The Raspberry Pi needs a capacitive soil moisture sensor. + +The sensor you'll use is a [Capacitive Soil Moisture Sensor](https://www.seeedstudio.com/Grove-Capacitive-Moisture-Sensor-Corrosion-Resistant.html), that measures soil moisture by detecting the capacitance of the soil, a property than changes as the soil moisture changes. As the soil moisture increases, the voltage decreases. + +This is an analog sensor, so uses an analog pin, and the 10-bit ADC in the Grove Base Hat on the Pi to convert the voltage to a digital signal from 1-1,023. This is then sent over I2C via the GPIO pins on the Pi. + +### Connect the soil moisture sensor + +The Grove soil moisture sensor can be connected to the Raspberry Pi. + +#### Task - connect the soil moisture sensor + +Connect the soil moisture sensor. + +![A grove soil moisture sensor](../../../images/grove-capacitive-soil-moisture-sensor.png) + +1. Insert one end of a Grove cable into the socket on the soil moisture sensor. It will only go in one way round. + +1. With the Raspberry Pi powered off, connect the other end of the Grove cable to the analog socket marked **A0** on the Grove Base hat attached to the Pi. This socket is the second from the right, on the row of sockets next to the GPIO pins. + +![The grove soil moisture sensor connected to the A0 socket](../../../images/pi-soil-moisture-sensor.png) + +1. Insert the soil moisture sensor into soil. It has a 'highest position line' - a white line across the sensor. Insert the sensor up to but not past this line. + +![The grove soil moisture sensor in soil](../../../images/soil-moisture-sensor-in-soil.png) + +## Program the soil moisture sensor + +The Raspberry Pi can now be programmed to use the attached soil moisture sensor. + +### Task - program the soil moisture sensor + +Program the device. + +1. Power up the Pi and wait for it to boot + +1. Launch VS Code, either directly on the Pi, or connect via the Remote SSH extension. + + > โš ๏ธ You can refer to [the instructions for setting up and launch VS Code in lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/pi.md). + +1. From the terminal, create a new folder in the `pi` users home directory called `soil-moisture-sensor`. Create a file in this folder called `app.py`: + +1. Open this folder in VS Code + +1. Add the following code to the `app.py` file to import some required libraries: + + ```python + import time + from grove.adc import ADC + ``` + + The `import time` statement imports the `time` module that will be used later in this assignment. + + The `from grove.adc import ADC` statement imports the `ADC` from the Grove Python libraries. This library has code to interact with the analog to digital converter on the Pi base hat and read voltages from analog sensors. + +1. Add the following code below this to create an instance of the `ADC` class: + + ```python + adc = ADC() + ``` + +1. Add an infinite loop that reads from this ADC on the A0 pin, and write the result to the console. This loop can then sleep for 10 seconds between reads. + + ```python + while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + time.sleep(10) + ``` + +1. Run the Python app. You will see the soil moisture measurements written to the console. Add some water to the soil, or remove the sensor from the soil, and see the value change. + + ```output + pi@raspberrypi:~/soil-moisture-sensor $ python3 app.py + Soil moisture: 615 + Soil moisture: 612 + Soil moisture: 498 + Soil moisture: 493 + Soil moisture: 490 + Soil Moisture: 388 + ``` + + In the example output above, you can see the voltage drop as water is added. + +> ๐Ÿ’ You can find this code in the [code/pi](code/pi) folder. + +๐Ÿ˜€ Your soil moisture sensor program was a success! diff --git a/2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md b/2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md new file mode 100644 index 00000000..a0a1494a --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md @@ -0,0 +1,109 @@ +# Measure soil moisture - Virtual IoT Hardware + +In this part of the lesson, you will add a capacitive soil moisture sensor to your virtual IoT device, and read values from it. + +## Virtual Hardware + +The virtual IoT device will use a simulated Grove capacitive soil moisture sensor. This keeps this lab the same as using a Raspberry Pi with a physical Grove capacitive soil moisture sensor. + +In a physical IoT device, the soil moisture sensor would be a capacitive sensor that measures soil moisture by detecting the capacitance of the soil, a property than changes as the soil moisture changes. As the soil moisture increases, the voltage decreases. + +This is an analog sensor, so uses a simulated 10-bit ADC to report a value from 1-1,023. + +### Add the soil moisture sensor to CounterFit + +To use a virtual soil moisture sensor, you need to add it to the CounterFit app + +#### Task + +Add the soil moisture sensor to the CounterFit app. + +1. Create a new Python app on your computer in a folder called `soil-moisture-sensor` with a single file called `app.py` and a Python virtual environment, and add the CounterFit pip packages. + + > โš ๏ธ You can refer to [the instructions for creating and setting up a CounterFit Python project in lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/virtual-device.md). + +1. Make sure the CounterFit web app is running + +1. Create a soil moisture sensor: + + 1. In the *Create sensor* box in the *Sensors* pane, drop down the *Sensor type* box and select *Soil Moisture*. + + 1. Leave the *Units* set to *NoUnits* + + 1. Ensure the *Pin* is set to *0* + + 1. Select the **Add** button to create the humidity sensor on Pin 0 + + ![The soil moisture sensor settings](../../../images/counterfit-create-soil-moisture-sensor.png) + + The soil moisture sensor will be created and appear in the sensors list. + + ![The soil moisture sensor created](../../../images/counterfit-soil-moisture-sensor.png) + +## Program the soil moisture sensor app + +The soil moisture sensor app can now be programmed using the CounterFit sensors. + +### Task + +Program the soil moisture sensor app. + +1. Make sure the `soil-moisture-sensor` app is open in VS Code + +1. Open the `app.py` file + +1. Add the following code to the top of `app.py` to connect the app to CounterFit: + + ```python + from counterfit_connection import CounterFitConnection + CounterFitConnection.init('127.0.0.1', 5000) + ``` + +1. Add the following code to the `app.py` file to import some required libraries: + + ```python + import time + from counterfit_shims_grove.adc import ADC + ``` + + The `import time` statement imports the `time` module that will be used later in this assignment. + + The `from counterfit_shims_grove.adc import ADC` statement imports the `ADC` class to interact with a virtual analog to digital converter that can connect to a CounterFit sensor. + +1. Add the following code below this to create an instance of the `ADC` class: + + ```python + adc = ADC() + ``` + +1. Add an infinite loop that reads from this ADC on pin 0 and write the result to the console. This loop can then sleep for 10 seconds between reads. + + ```python + while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + time.sleep(10) + ``` + +1. From the CounterFit app, change the value of the soil moisture sensor that will be read by the app. You can do this in one of two ways: + + * Enter a number in the *Value* box for the soil moisture sensor, then select the **Set** button. The number you enter will be the value returned by the sensor. + + * Check the *Random* checkbox, and enter a *Min* and *Max* value, then select the **Set** button. Every time the sensor reads a value, it will read a random number between *Min* and *Max*. + +1. Run the Python app. You will see the soil moisture measurements written to the console. Change the *Value* or the *Random* settings to see the value change. + + ```output + (.venv) โžœ soil-moisture-sensor $ python app.py + Soil moisture: 615 + Soil moisture: 612 + Soil moisture: 498 + Soil moisture: 493 + Soil moisture: 490 + Soil Moisture: 388 + ``` + +> ๐Ÿ’ You can find this code in the [code/virtual-device](code/virtual-device) folder. + +๐Ÿ˜€ Your soil moisture sensor program was a success! diff --git a/2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md b/2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md new file mode 100644 index 00000000..6697d316 --- /dev/null +++ b/2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md @@ -0,0 +1,103 @@ +# Measure soil moisture - Wio Terminal + +In this part of the lesson, you will add a capacitive soil moisture sensor to your Wio Terminal, and read values from it. + +## Hardware + +The Wio Terminal needs a capacitive soil moisture sensor. + +The sensor you'll use is a [Capacitive Soil Moisture Sensor](https://www.seeedstudio.com/Grove-Capacitive-Moisture-Sensor-Corrosion-Resistant.html), that measures soil moisture by detecting the capacitance of the soil, a property than changes as the soil moisture changes. As the soil moisture increases, the voltage decreases. + +This is an analog sensor, so connects to analog pins on the Wio Terminal, using an onboard ADC to create a value from 0-1,023. + +### Connect the soil moisture sensor + +The Grove soil moisture sensor can be connected to the Wio Terminals configurable analog/digital port. + +#### Task - connect the soil moisture sensor + +Connect the soil moisture sensor. + +![A grove soil moisture sensor](../../../images/grove-capacitive-soil-moisture-sensor.png) + +1. Insert one end of a Grove cable into the socket on the soil moisture sensor. It will only go in one way round. + +1. With the Wio Terminal disconnected from your computer or other power supply, connect the other end of the Grove cable to the right-hand side Grove socket on the Wio Terminal as you look at the screen. This is the socket farthest away from the power button. + +![The grove soil moisture sensor connected to the right hand socket](../../../images/wio-soil-moisture-sensor.png) + +1. Insert the soil moisture sensor into soil. It has a 'highest position line' - a white line across the sensor. Insert the sensor up to but not past this line. + +![The grove soil moisture sensor in soil](../../../images/soil-moisture-sensor-in-soil.png) + +1. You can now connect the Wio Terminal to your computer. + +## Program the soil moisture sensor + +The Wio Terminal can now be programmed to use the attached soil moisture sensor. + +### Task - program the soil moisture sensor + +Program the device. + +1. Create a brand new Wio Terminal project using PlatformIO. Call this project `soil-moisture-sensor`. Add code in the `setup` function to configure the serial port. + + > โš ๏ธ You can refer to [the instructions for creating a PlatformIO project in project 1, lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#create-a-platformio-project). + +1. There isn't a library for this sensor, instead you can read from the analog pin using the built in Arduino [`analogRead`](https://www.arduino.cc/reference/en/language/functions/analog-io/analogread/) function. Start by configuring the analog pin for input so values can be read from it by adding the following to the `setup` function. + + ```cpp + pinMode(A0, INPUT); + ``` + + This sets the `A0` pin, the combined analog/digital pin, as an input pin that voltage can be read from. + +1. Add the following to the `loop` function to read the voltage from this pin: + + ```cpp + int soil_moisture = analogRead(A0); + ``` + +1. Below this code, add the following code to print the value to the serial port: + + ```cpp + Serial.print("Soil Moisture: "); + Serial.println(soil_moisture); + ``` + +1. Finally add a delay at the end of 10 seconds: + + ```cpp + delay(10000); + ``` + +1. Build and upload the code to the Wio Terminal. + + > โš ๏ธ You can refer to [the instructions for creating a PlatformIO project in project 1, lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#write-the-hello-world-app). + +1. Once uploaded, you can monitor the soil moisture using the serial monitor. Add some water to the soil, or remove the sensor from the soil, and see the value change. + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + Soil Moisture: 526 + Soil Moisture: 529 + Soil Moisture: 521 + Soil Moisture: 494 + Soil Moisture: 454 + Soil Moisture: 456 + Soil Moisture: 395 + Soil Moisture: 388 + Soil Moisture: 394 + Soil Moisture: 391 + ``` + + In the example output above, you can see the voltage drop as water is added. + +> ๐Ÿ’ You can find this code in the [code/wio-terminal](code/wio-terminal) folder. + +๐Ÿ˜€ Your soil moisture sensor program was a success! diff --git a/2-farm/lessons/3-automated-plant-watering/README.md b/2-farm/lessons/3-automated-plant-watering/README.md new file mode 100644 index 00000000..e8b9d4ce --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/README.md @@ -0,0 +1,298 @@ +# Automated plant watering + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/13) + +## Introduction + +In the last lesson, you learned how to monitor soil moisture. In this lesson you will learn how to build the core components of an automated watering system that responds to soil moisture. You'll also learn about timing - how sensors can take a while to respond to changes, and how actuators can take time to change the properties being measured by sensors. + +In this lesson we'll cover: + +* [Control high power devices from a low power IoT device](#control-high-power-devices-from-a-low-power-iot-device) +* [Control a relay](#control-a-relay) +* [Control your plant over MQTT](#control-a-relay) +* [Sensor and actuator timing](#sensor-and-actuator-timing) +* [Add timing to your plant control](#add-timing-to-your-plant-control) + +## Control high power devices from a low power IoT device + +IoT devices use a low voltage. While this is enough for sensors and low-power actuators like LEDs, this is too low to control larger hardware, such as a water pump used for irrigation. Even small pumps you could use for houseplants draw too much current for an IoT dev kit and would burn out the board. + +> ๐ŸŽ“ Current, measured in Amps (A), is the amount of electricity moving through a circuit. Voltage provides the push, current is how much is pushed. You can read more about current on the [electric current page on Wikipedia](https://wikipedia.org/wiki/Electric_current). + +The solution to this is to have a pump connected to an external power supply, and use an actuator to switch on the pump, similar to how you would switch on a light. It takes a tiny amount of power (in the form of energy in your body) for your finger to flip a switch, and this connects the light to mains electricity running at 110v/240v. + +![A light switch turns power on to a light](../../../images/light-switch.png) + +***A light switch turns power on to a light. switch by Chattapat / lightbulb by Maxim Kulikov - all from the [Noun Project](https://thenounproject.com)*** + +> ๐ŸŽ“ [Mains electricity](https://wikipedia.org/wiki/Mains_electricity) refers to the electricity delivered to homes an businesses through national infrastructure in may parts of the world. + +โœ… IoT devices can usually provide 3.3V or 5V, at less than 1 amp (1A) of current. Compare this to mains electricity which is most often at 230V (120V in North America and 100V in Japan), and can provide power for devices that draw 30A. + +There are a number of actuators that can do this, including mechanical devices you can attach to existing switches that mimic a finger turning them on. The most popular is a relay. + +### Relays + +A relay is an electromechanical switch that converts an electrical signal into a mechanical movement that turns on a switch. The core of a relay is an electromagnet. + +> ๐ŸŽ“ [Electromagnets](https://wikipedia.org/wiki/Electromagnet) are magnets that are created by passing electricity through a coil of wire. When the electricity is turned on, the coil becomes magnetized. When the electricity is turned off, the coil loses it magnetism. + +![When on, the electromagnet creates a magnetic field, turning on the switch for the output circuit](../../../images/relay-on.png) + +***When on, the electromagnet creates a magnetic field, turning on the switch for the output circuit. lightbulb by Maxim Kulikov - from the [Noun Project](https://thenounproject.com)*** + +In a relay, a control circuit powers the electromagnet. When the electromagnet is on, it pulls a lever that moves a switch, closing a pair of contacts and completing an output circuit. + +![When off, the electromagnet doesn't create a magnetic field, turning off the switch for the output circuit](../../../images/relay-off.png) + +***When off, the electromagnet doesn't create a magnetic field, turning off the switch for the output circuit. lightbulb by Maxim Kulikov - from the [Noun Project](https://thenounproject.com)*** + +When the control circuit is off, the electromagnet turns off, releasing the lever and opening the contacts, turning off the output circuit. Relays are digital actuators - a high signal to the relay turns it on, a low signal turns it off. + +The output circuit can be used to power additional hardware, like an irrigation system. The IoT device can turn the relay on, completing the output circuit that powers the irrigation system, and plants get watered. The IoT device can then turn the relay off, cutting the power to the irrigation system, turning the water off. + +![A relay turning on, turning on a pump sending water to a plant](../../../images/strawberry-pump.gif) + +In the video above, a relay is turned on. An LED on the relay lights up to indicate it is on (some relay boards have LEDs to indicate if the relay is on or off), and power is sent to the pump, turning it on and pumping water into a plant. + +> ๐Ÿ’ Relays can also be used to switch between two output circuits instead of turning one on and off. As the lever moves, it moves a switch from completing one output circuit to completing a different output circuit, usually sharing a common power connection, or common ground connection. + +โœ… Do some research: There are multiple types of relays, with differences such as if the control circuit turns the relay on or off when power is applied, or multiple output circuits. Find out about these different types. + +When the lever moves, you can usually hear it make contact with the electromagnet with a well defined click noise. + +> ๐Ÿ’ A relay can be wired so that making the connection actually breaks power to the relay, turning the relay off, which then sends power to the relay turning it back on again, and so on. This means the relay will click incredibly fast making a buzzing noise. This is how some of the first buzzers used in electric doorbells worked. + +### Relay power + +The electromagnet doesn't need a lot of power to activate and pull the lever, it can be controlled using the 3.3V or 5V output from an IoT dev kit. The output circuit can carry a lot more power, depending on the relay, including mains voltage or even higher power levels for industrial use. This way an IoT Dev kit can control an irrigation system, from a small pump for a single plant, up to a massive industrial system for an entire commercial farm. + +![A grove relay with the control circuit, output circuit and relay labelled](../../../images/grove-relay-labelled.png) + +The image above shows a Grove relay. The control circuit connects to an IoT device and turns the relay on or off using 3.3V or 5V. The output circuit has two terminals, either one can be power or ground. The output circuit can handle up to 250V at 10A, enough for a range of mains-powered devices. You can get relays that can handle even high power levels. + +![A pump wired through a relay](../../../images/pump-wired-to-relay.png) + +In the image above, power is supplied to a pump via a relay. There is a red wire connecting the +5V terminal of a USB power supply to one terminal of the output circuit of the relay, and another red wire connecting the other terminal of the output circuit to the pump. A black wire connects the pump to the ground on the USB power supply. When the relay turns on, it completes the circuit, sending 5V to the pump, turning the pump on. + +## Control a relay + +You can control a relay from your IoT Dev kit. + +### Task - control a relay + +Work through the relevant guide to control a relay using your IoT device: + +* [Arduino - Wio Terminal](wio-terminal-relay.md) +* [Single-board computer - Raspberry Pi](pi-relay.md) +* [Single-board computer - Virtual device](virtual-device-relay.md) + +## Control your plant over MQTT + +So far your relay is controlled by the IoT device directly based off a single soil moisture reading. In a commercial irrigation system, the control logic will be centralized, allowing it to make decisions on watering using data from multiple sensors, and allowing any configuration to be changed in one single place. To simulate this, you can control the relay over MQTT. + +### Task - control the relay over MQTT + +1. Add the relevant MQTT libraries/pip packages and code to your `soil-moisture-sensor` project to connect to MQTT. Name the client ID as `soilmoisturesensor_client` prefixed by your ID. + + > โš ๏ธ You can refer to [the instructions for for connecting to MQTT in project 1, lesson 4 if needed](../../../1-getting-started/lessons/4-connect-internet/README.md#connect-your-iot-device-to-mqtt). + +1. Add the relevant device code to send telemetry with the soil moisture settings. For the telemetry message, name the property `soil_moisture`. + + > โš ๏ธ You can refer to [the instructions for for sending telemetry to MQTT in project 1, lesson 4 if needed](../../../1-getting-started/lessons/4-connect-internet/README.md#send-telemetry-from-your-iot-device). + +1. Create some local server code to subscribe to telemetry and send a command to control the relay in a folder called `soil-moisture-sensor-server`. Name the property in the command message `relay_on`, and set the client ID as `soilmoisturesensor_server` prefixed by your ID. Keep the same structure as the server code you wrote for project 1, lesson 4 as you will be adding to this code later in this lesson. + + > โš ๏ธ You can refer to [the instructions for for sending telemetry to MQTT](../../../1-getting-started/lessons/4-connect-internet/README.md#write-the-server-code) and [sending commands over MQTT](../../../1-getting-started/lessons/4-connect-internet/README.md#send-commands-to-the-mqtt-broker) in project 1, lesson 4 if needed. + +1. Add the relevant device code to control the relay from received commands, using the `relay_on` property from the message. Send true for `relay_on` if the `soil_moisture` is greater than 450, otherwise send false, the same as the logic you added for the IoT device earlier. + + > โš ๏ธ You can refer to [the instructions for for responding to commands from MQTT in project 1, lesson 4 if needed](../../../1-getting-started/lessons/4-connect-internet/README.md#handle-commands-on-the-iot-device). + +> ๐Ÿ’ You can find this code in the [`code-mqtt`](./code-mqtt) folder. + +Make sure the code is running on your device and local server, and test it out by changing soil moisture levels, either by changing the values sent by the virtual sensor, or by changing the moisture levels of the soil by adding water or removing the sensor from the soil. + +## Sensor and actuator timing + +Back in lesson 3 you built a nightlight - an LED that turned on as soon as a low level of light was detected by a light sensor. The light sensor detected a change in light levels instantly, and the device was able to respond quickly, only limited by the length of the delay in the `loop` function or `while True:` loop. As an IoT developer, you can't always rely on such a fast feedback loop. + +### Timing for soil moisture + +If you did the last lesson on soil moisture using a physical sensor, you would have noticed that it took a few seconds for the soil moisture reading to drop after you watered your plant. This is not because the sensor is slow, but because it takes time for water to soak through the soil. + +> ๐Ÿ’ If you watered too close to the sensor you may have seen the reading drop quickly, then come back up - this is caused by water near the sensor spreading throughout the rest of the soil, reducing the soil moisture by the sensor. + +![A soil moisture measurement of 658 doesn't change during watering, it only drops to 320 after watering when water has soaked through the soil](../../../images/soil-moisture-travel.png) + +***A soil moisture measurement of 658 doesn't change during watering, it only drops to 320 after watering when water has soaked through the soil. Plant by Alex Muravev / Watering Can by Daria Moskvina - all from the [Noun Project](https://thenounproject.com)*** + +In the diagram above, a soil moisture reading shows 658. The plant is watered, but this reading doesn't change immediately, as the water has yet to reach the sensor. Watering can even finish before the water reaches the sensor and the value drops to reflect the new moisture level. + +If you were writing code to control an irrigation system via a relay based off soil moisture levels, you would need to take this delay into consideration and build smarter timing into your IoT device. + +โœ… Take a moment to think about how you might do this. + +### Control sensor and actuator timing + +Imagine you have been tasked with building an irrigation system for a farm. Based on the soil type, the ideal soil moisture level for the plants grown has been found to match an analog voltage reading of 400-450. + +You could program the device in the same way as the nightlight - all the time the sensor reads above 450, turn on a relay to turn on a pump. The problem is that water takes a while to get from the pump, through the soil to the sensor. The sensor will stop the water when it detects a level of 450, but the water level will continue dropping as the pumped water keeps soaking through the soil. The end result is wasted water, and the risk of root damage. + +โœ… Remember - too mch water can be as bad for plants as too little, and wastes a precious resource. + +The better solution is to understand that there is a delay between the actuator turning on and the property that the sensor reads changing. This means not only should the sensor wait for a while before measuring the value again, but the actuator needs to turn off for a while before the next sensor measurement is taken. + +How long should the relay be on each time? It's better to err on the side of caution and only turn the relay on for a short time, then wait for the water to soak through, then re-check the moisture levels. After all, you can always turn it on again to add more water, you can't remove water from the soil. + +> ๐Ÿ’ This kind of timing control is very specific to the IoT device you are building, the property you are measuring and the sensors and actuators used. + +![A strawberry plant connected to water via a pump, with the pump connected to a relay. The relay and a soil moisture sensor in the plant are both connected to a Raspberry Pi](../../../images/strawberry-with-pump.png) + +For example, I have a strawberry plant with a soil moisture sensor and a pump controlled by a relay.I've observed that when I add water it takes about 20 seconds for the soil moisture reading to stabilize. This means I need to turn the relay off and wait 20 seconds before checking the moisture levels. I'd rather have too little water than too much - I can always turn the pump on again, but I can't take water out of the plant. + +![Step 1, take measurement. Step 2, add water. Step 3, wait for water to soak through the soil. Step 4, retake measurement](../../../images/soil-moisture-delay.png) + +***Measure, add water, wait, remeasure. Plant by Alex Muravev / Watering Can by Daria Moskvina - all from the [Noun Project](https://thenounproject.com)*** + +This means the best process would be a watering cycle that is something like: + +* Turn on the pump for 5 seconds +* Wait 20 seconds +* Check the soil moisture +* If the level is still above what I need, repeat the above steps + +5 seconds could be too long for the pump, especially if the moisture levels are only slightly above the required level. The best way to know what timing to use is to try it, then adjust when you have sensor data, with a constant feedback loop. This can even lead to more granular timing, such as turning the pump on for 1s for every 100 above the required soil moisture, instead of a fixed 5 seconds. + +โœ… Do some research: Are there other timing considerations? Can the plant be watered any time that the soil moisture is too low, or are there specific times of day that are good and bad times to water plants? + +> ๐Ÿ’ Weather predictions can also be taken into consideration when controlling automated watering systems for outdoor growing. If rain is expected, then the watering can be put on hold till after the rain finishes. At that point the soil may be moist enough that it doesn't need watering, much more efficient that wasting water by watering just before rain. + +## Add timing to your plant control server + +The server code can be modified to add control around the timing of the watering cycle, and waiting for the soil moisture levels to change. The server logic for controlling the relay timing is: + +1. Telemetry message received +1. Check the soil moisture level +1. if it's ok, do nothing. If the reading is too high (meaning the soil moisture is too low) then: + 1. Send a command to turn the relay on + 1. Wait for 5 seconds + 1. Send a command to turn the relay off + 1. Wait for 20 seconds for the soil moisture levels to stabilize + +The watering cycle, the process from receiving the telemetry message to being ready to process soil moisture levels again, takes about 25 seconds. We're sending soil moisture levels every 10 seconds, so there is an overlap where a message is received whilst the server is waiting for soil moisture levels to stabilize, which could start another watering cycle. + +There are two options to work around this: + +* Change the IoT device code to only send telemetry every minute, this way the watering cycle will be completed before the next message is sent +* Unsubscribe from the telemetry during the watering cycle + +The first option is not always a good solution for large farms. The farmer might want to capture the soil moisture levels as the soil is being watered for later analysis, for example to be aware of water flow in different areas on the farm to guide more targeted watering. The second option is better - the code is just ignoring telemetry when it can't use it, but the telemetry is still there for other services that might subscribe to it. + +> ๐Ÿ’ IoT data is not sent from only one device to only one service, instead many devices can send data to a broker, and many services can listen to the data off the broker. For example, one service could listen to soil moisture data and store it in a database for analysis at a later date. Another service can also listen to the same telemetry to control an irrigation system. + +### Task - add timing to your plant control server + +Update your server code to run the relay for 5 seconds, then wait 20 seconds. + +1. Open the `soil-moisture-sensor-server` folder in VS Code if it isn't already open. Make sure the virtual environment is activated. + +1. Open the `app.py` file + +1. Add the following code before the `handle_telemetry` function that handles telemetry messages received by the server code: + + ```python + water_time = 5 + wait_time = 20 + ``` + + This defines how long to run the relay for (`water_time`), and how long to wait afterwards to check the soil moisture (`wait_time`). + +1. Below this code, add the following: + + ```python + def send_relay_command(client, state): + command = { 'relay_on' : state } + print("Sending message:", command) + client.publish(server_command_topic, json.dumps(command)) + ``` + + This code defines a function called `send_relay_command` that sends a command over MQTT to control the relay. The telemetry is created as a dictionary, then converted to a JSON string. The value passed in to `state` determines if the relay should be on or off. + +1. After the `send_relay_code` function, add the following code: + + ```python + def control_relay(client): + print("Unsubscribing from telemetry") + mqtt_client.unsubscribe(client_telemetry_topic) + + send_relay_command(client, True) + time.sleep(water_time) + send_relay_command(client, False) + + time.sleep(wait_time) + + print("Subscribing to telemetry") + mqtt_client.subscribe(client_telemetry_topic) + ``` + + This defines a function to control the relay based off the required timing. It starts by unsubscribing from telemetry so that soil moisture messages are not processed whilst the watering is happening. Next it sends a command to turn the relay on. It then waits for the `water_time` before sending a command to turn the relay off. Finally it waits for the soil moisture levels to stabilize for `wait_time` seconds. It then re-subscribes to telemetry. + +1. Change the `handle_telemetry` function to the following: + + ```python + def handle_telemetry(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + if payload['soil_moisture'] > 450: + threading.Thread(target=control_relay, args=(client,)).start() + ``` + + This code checks the soil moisture level. If it is greater than 450, the soil needs watering, so it calls the `control_relay` function. This function is run on a separate thread, running in the background. + +1. Make sure your IoT device is running, then run this code. Change the soil moisture levels and observe what happens to the relay - it should turn on for 5 seconds then remain off for at least 20 seconds, only turning on if the soil moisture levels are not sufficient. + + ```output + (.venv) โžœ soil-moisture-sensor-server โœ— python app.py + Message received: {'soil_moisture': 457} + Unsubscribing from telemetry + Sending message: {'relay_on': True} + Sending message: {'relay_on': False} + Subscribing to telemetry + Message received: {'soil_moisture': 302} + ``` + + A good way to test this in a simulated irrigation system is to use dry soil, then pour water in manually whilst the relay is on, stopping pouring when the relay turns off. + +> ๐Ÿ’ If you want to use a pump to build a real irrigation system, then you can use a [6V water pump](https://www.seeedstudio.com/6V-Mini-Water-Pump-p-1945.html) with a [USB terminal power supply](https://www.adafruit.com/product/3628). Make sure the power to or from the pump is connected via the relay. + +--- + +## ๐Ÿš€ Challenge + +Can you think of any other IoT or other electrical devices that have a similar problem where it takes a while for the results of the actuator to reach the sensor. You probably have a couple in your house or school. + +* What properties do they measure? +* How long does it take for the property to change after an actuator is used? +* Is it ok for the property to change past the required value? +* How can it be returned back to the required value if needed? + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/14) + +## Review & Self Study + +Read more on relays including their historical use in telephone exchanges on the [relay Wikipedia page](https://wikipedia.org/wiki/Relay). + +## Assignment + +[Build a more efficient watering cycle](assignment.md) diff --git a/2-farm/lessons/3-automated-plant-watering/assignment.md b/2-farm/lessons/3-automated-plant-watering/assignment.md new file mode 100644 index 00000000..b4639b60 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/assignment.md @@ -0,0 +1,40 @@ +# Build a more efficient watering cycle + +## Instructions + +This lesson covered how to control a relay via sensor data, and that relay could in turn control a pump for an irrigation system. For a defined body of soil, running a pump for a fixed length of time should always have the same impact on the soil moisture. This means you can get an idea of how many seconds of irrigation correspond to a certain drop in soil moisture reading. Using this data you can build a more controlled irrigation system. + +For this assignment you will calculate how long the pump should run for a particular rise in soil moisture. + +> โš ๏ธ If you are using virtual IoT hardware, you can work through this process, but simulate the results by increasing the soil moisture reading manually by a fixed amount per second the relay us on. + +1. Start with dry soil. Measure the soil moisture. + +1. Add a fixed amount of water, either by running the pump for 1 second or by pouring a fixed amount in. + + > The pump should always run at a constant rate, so every second the pump runs it should supply the same amount of water. + +1. Wait until the soil moisture level stabilizes and take a reading. + +1. Repeat this multiple times and create a table of the results. An example of this table is given below. + + | Total Pump time | Soil Moisture | Decrease | + | --- | --: | -: | + | Dry | 643 | 0 | + | 1s | 621 | 22 | + | 2s | 601 | 20 | + | 3s | 579 | 22 | + | 4s | 560 | 19 | + | 5s | 539 | 21 | + | 6s | 521 | 18 | + +1. Work out an average increase in soil moisture per second of water. In the example above, each second of water decreases the reading by an average of 20.3. + +1. Use this data to improve the efficiency of your server code, running the pump for the required time to get the soil moisture to the level needed. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Capture soil moisture date | Is able to capture multiple readings after adding fixed quantities of water | Is able to capture some readings with fixed quantities of water | Can only capture one or two readings, or is unable to use fixed quantities of water | +| Calibrate the server code | Is able to calculate an average decrease in soil moisture reading and update the serve code to use this | Is able to calculate an average decrease, but cannot update the server code, or is unable to correctly calculate an average, but uses this value to correctly update the server code | Is unable to calculate an average, or update the server code | diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/pi/soil-moisture-sensor/app.py b/2-farm/lessons/3-automated-plant-watering/code-mqtt/pi/soil-moisture-sensor/app.py new file mode 100644 index 00000000..3d0fffca --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/pi/soil-moisture-sensor/app.py @@ -0,0 +1,39 @@ +import time +from grove.adc import ADC +from grove.grove_relay import GroveRelay +import json +import paho.mqtt.client as mqtt + +adc = ADC() +relay = GroveRelay(5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'soilmoisturesensor_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +def handle_command(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + if payload['relay_on']: + relay.on() + else: + relay.off() + +mqtt_client.subscribe(server_command_topic) +mqtt_client.on_message = handle_command + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + mqtt_client.publish(client_telemetry_topic, json.dumps({'soil_moisture' : soil_moisture})) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/server/app.py b/2-farm/lessons/3-automated-plant-watering/code-mqtt/server/app.py new file mode 100644 index 00000000..ca269db8 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/server/app.py @@ -0,0 +1,30 @@ +import json +import time + +import paho.mqtt.client as mqtt + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'soilmoisturesensor_server' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +def handle_telemetry(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + command = { 'relay_on' : payload['soil_moisture'] > 450 } + print("Sending message:", command) + + client.publish(server_command_topic, json.dumps(command)) + +mqtt_client.subscribe(client_telemetry_topic) +mqtt_client.on_message = handle_telemetry + +while True: + time.sleep(2) \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/virtual-device/soil-moisture-sensor/app.py b/2-farm/lessons/3-automated-plant-watering/code-mqtt/virtual-device/soil-moisture-sensor/app.py new file mode 100644 index 00000000..3cd899c7 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/virtual-device/soil-moisture-sensor/app.py @@ -0,0 +1,42 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +from counterfit_shims_grove.adc import ADC +from counterfit_shims_grove.grove_relay import GroveRelay +import json +import paho.mqtt.client as mqtt + +adc = ADC() +relay = GroveRelay(5) + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'soilmoisturesensor_client' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +def handle_command(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + if payload['relay_on']: + relay.on() + else: + relay.off() + +mqtt_client.subscribe(server_command_topic) +mqtt_client.on_message = handle_command + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + mqtt_client.publish(client_telemetry_topic, json.dumps({'soil_moisture' : soil_moisture})) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/.gitignore b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/.vscode/extensions.json b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/include/README b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/lib/README b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/platformio.ini b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/platformio.ini new file mode 100644 index 00000000..38827344 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + knolleary/PubSubClient @ 2.8 + bblanchon/ArduinoJson @ 6.17.3 + seeed-studio/Seeed Arduino rpcWiFi @ 1.0.3 + seeed-studio/Seeed Arduino FS @ 2.0.2 + seeed-studio/Seeed Arduino SFUD @ 2.0.1 + seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 + seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/src/config.h b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/src/config.h new file mode 100644 index 00000000..7f546c0b --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/src/config.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +using namespace std; + +// WiFi credentials +const char *SSID = ""; +const char *PASSWORD = ""; + +// MQTT settings +const string ID = ""; + +const string BROKER = "test.mosquitto.org"; +const string CLIENT_NAME = ID + "soilmoisturesensor_client"; + +const string CLIENT_TELEMETRY_TOPIC = ID + "/telemetry"; +const string SERVER_COMMAND_TOPIC = ID + "/commands"; \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/src/main.cpp b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/src/main.cpp new file mode 100644 index 00000000..fd4bac4a --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/src/main.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include + +#include "config.h" + +void connectWiFi() +{ + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("Connecting to WiFi.."); + WiFi.begin(SSID, PASSWORD); + delay(500); + } + + Serial.println("Connected!"); +} + +WiFiClient wioClient; +PubSubClient client(wioClient); + +void clientCallback(char *topic, uint8_t *payload, unsigned int length) +{ + char buff[length + 1]; + for (int i = 0; i < length; i++) + { + buff[i] = (char)payload[i]; + } + buff[length] = '\0'; + + Serial.print("Message received:"); + Serial.println(buff); + + DynamicJsonDocument doc(1024); + deserializeJson(doc, buff); + JsonObject obj = doc.as(); + + bool relay_on = obj["relay_on"]; + + if (relay_on) + digitalWrite(PIN_WIRE_SCL, HIGH); + else + digitalWrite(PIN_WIRE_SCL, LOW); +} + +void reconnectMQTTClient() +{ + while (!client.connected()) + { + Serial.print("Attempting MQTT connection..."); + + if (client.connect(CLIENT_NAME.c_str())) + { + Serial.println("connected"); + client.subscribe(SERVER_COMMAND_TOPIC.c_str()); + } + else + { + Serial.print("Retying in 5 seconds - failed, rc="); + Serial.println(client.state()); + + delay(5000); + } + } +} + +void createMQTTClient() +{ + client.setServer(BROKER.c_str(), 1883); + client.setCallback(clientCallback); + reconnectMQTTClient(); +} + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + pinMode(A0, INPUT); + pinMode(PIN_WIRE_SCL, OUTPUT); + + connectWiFi(); + createMQTTClient(); +} + +void loop() +{ + reconnectMQTTClient(); + client.loop(); + + int soil_moisture = analogRead(A0); + + DynamicJsonDocument doc(1024); + doc["soil_moisture"] = soil_moisture; + + string telemetry; + JsonObject obj = doc.as(); + serializeJson(obj, telemetry); + + Serial.print("Sending telemetry "); + Serial.println(telemetry.c_str()); + + client.publish(CLIENT_TELEMETRY_TOPIC.c_str(), telemetry.c_str()); + + delay(10000); +} diff --git a/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/test/README b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-mqtt/wio-terminal/soil-moisture-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/pi/soil-moisture-sensor/app.py b/2-farm/lessons/3-automated-plant-watering/code-relay/pi/soil-moisture-sensor/app.py new file mode 100644 index 00000000..3be04a71 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/pi/soil-moisture-sensor/app.py @@ -0,0 +1,19 @@ +import time +from grove.adc import ADC +from grove.grove_relay import GroveRelay + +adc = ADC() +relay = GroveRelay(5) + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + if soil_moisture > 450: + print("Soil Moisture is too low, turning relay on.") + relay.on() + else: + print("Soil Moisture is ok, turning relay off.") + relay.off() + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/virtual-device/soil-moisture-sensor/app.py b/2-farm/lessons/3-automated-plant-watering/code-relay/virtual-device/soil-moisture-sensor/app.py new file mode 100644 index 00000000..a8f80b9a --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/virtual-device/soil-moisture-sensor/app.py @@ -0,0 +1,22 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +from counterfit_shims_grove.adc import ADC +from counterfit_shims_grove.grove_relay import GroveRelay + +adc = ADC() +relay = GroveRelay(5) + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + if soil_moisture > 450: + print("Soil Moisture is too low, turning relay on.") + relay.on() + else: + print("Soil Moisture is ok, turning relay off.") + relay.off() + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/.gitignore b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/.vscode/extensions.json b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/include/README b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/lib/README b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/platformio.ini b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/platformio.ini new file mode 100644 index 00000000..4e03e890 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/src/main.cpp b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/src/main.cpp new file mode 100644 index 00000000..a5531b6e --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/src/main.cpp @@ -0,0 +1,28 @@ +#include + +void setup() +{ + pinMode(A0, INPUT); + pinMode(PIN_WIRE_SCL, OUTPUT); +} + +void loop() +{ + int soil_moisture = analogRead(A0); + + Serial.print("Soil Moisture: "); + Serial.println(soil_moisture); + + if (soil_moisture > 450) + { + Serial.println("Soil Moisture is too low, turning relay on."); + digitalWrite(PIN_WIRE_SCL, HIGH); + } + else + { + Serial.println("Soil Moisture is ok, turning relay off."); + digitalWrite(PIN_WIRE_SCL, LOW); + } + + delay(10000); +} \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/test/README b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-relay/wio-terminal/soil-moisture-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/2-farm/lessons/3-automated-plant-watering/code-timing/server/app.py b/2-farm/lessons/3-automated-plant-watering/code-timing/server/app.py new file mode 100644 index 00000000..231f1477 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/code-timing/server/app.py @@ -0,0 +1,50 @@ +import json +import time + +import paho.mqtt.client as mqtt +import threading + +id = '' + +client_telemetry_topic = id + '/telemetry' +server_command_topic = id + '/commands' +client_name = id + 'soilmoisturesensor_server' + +mqtt_client = mqtt.Client(client_name) +mqtt_client.connect('test.mosquitto.org') + +mqtt_client.loop_start() + +water_time = 5 +wait_time = 20 + +def send_relay_command(client, state): + command = { 'relay_on' : state } + print("Sending message:", command) + client.publish(server_command_topic, json.dumps(command)) + +def control_relay(client): + print("Unsubscribing from telemetry") + mqtt_client.unsubscribe(client_telemetry_topic) + + send_relay_command(client, True) + time.sleep(water_time) + send_relay_command(client, False) + + time.sleep(wait_time) + + print("Subscribing to telemetry") + mqtt_client.subscribe(client_telemetry_topic) + +def handle_telemetry(client, userdata, message): + payload = json.loads(message.payload.decode()) + print("Message received:", payload) + + if payload['soil_moisture'] > 450: + threading.Thread(target=control_relay, args=(client,)).start() + +mqtt_client.subscribe(client_telemetry_topic) +mqtt_client.on_message = handle_telemetry + +while True: + time.sleep(2) \ No newline at end of file diff --git a/2-farm/lessons/3-automated-plant-watering/pi-relay.md b/2-farm/lessons/3-automated-plant-watering/pi-relay.md new file mode 100644 index 00000000..89cb39b8 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/pi-relay.md @@ -0,0 +1,109 @@ +# Control a relay - Raspberry Pi + +In this part of the lesson, you will add a relay to your Raspberry Pi in addition to the soil moisture sensor, and control it based off the soil moisture level. + +## Hardware + +The Raspberry Pi needs a relay. + +The relay you'll use is a [Grove relay](https://www.seeedstudio.com/Grove-Relay.html), a normally-open relay (meaning the output circuit is open, or disconnected when there is no signal sent to the relay) that can handle output circuits up to 250V and 10A. + +This is a digital actuator, so connects to a digital pin on the Grove Base Hat. + +### Connect the relay + +The Grove relay can be connected to the Raspberry Pi. + +#### Task + +Connect the relay. + +![A grove relay](../../../images/grove-relay.png) + +1. Insert one end of a Grove cable into the socket on the relay. It will only go in one way round. + +1. With the Raspberry Pi powered off, connect the other end of the Grove cable to the digital socket marked **D5** on the Grove Base hat attached to the Pi. This socket is the second from the left, on the row of sockets next to the GPIO pins. Leave the soil moisture sensor connected to the **A0** socket. + +![The grove relay connected to the D5 socket, and the soil moisture sensor connected to the A0 socket](../../../images/pi-relay-and-soil-moisture-sensor.png) + +1. Insert the soil moisture sensor into soil, if it isn't already from the previous lesson. + +## Program the relay + +The Raspberry Pi can now be programmed to use the attached relay. + +### Task + +Program the device. + +1. Power up the Pi and wait for it to boot + +1. Open the `soil-moisture-sensor` project from the last lesson in VS Code if it's not already open. You will be adding to this project. + +1. Add the following code to the `app.py` file below the existing imports: + + ```python + from grove.grove_relay import GroveRelay + ``` + + This statement imports the `GroveRelay` from the Grove Python libraries to interact with the Grove relay. + +1. Add the following code below the declaration of the `ADC` class to create a `GroveRelay` instance: + + ```python + relay = GroveRelay(5) + ``` + + This creates a relay using pin **D5**, the digital pin you connected the relay to. + +1. To test the relay is working, add the following to the `while True:` loop: + + ```python + relay.on() + time.sleep(.5) + relay.off() + ``` + + The code turns the relay on, waits 0.5 seconds, then turns the relay off. + +1. Run the Python app. The relay will turn on and off every 10 seconds, with a half second delay between turning on and off. You will hear the relay click on then click off. An LED on the Grove board will light when the relay is on, then go out when the relay is off. + + ![The relay turning on and off](../../../images/relay-turn-on-off.gif) + +## Control the relay from soil moisture + +Now that the relay is working, it can be controlled in response to soil moisture readings. + +### Task + +Control the relay. + +1. Delete the 3 lines of code that you added to test the relay. Replace them with the following code in its place: + + ```python + if soil_moisture > 450: + print("Soil Moisture is too low, turning relay on.") + relay.on() + else: + print("Soil Moisture is ok, turning relay off.") + relay.off() + ``` + + This code checks the soil moisture level from the soil moisture sensor. if it is above 450, it turns on the relay, turning it off if it goes below 450. + + > ๐Ÿ’ Remember the capacitive soil moisture sensor reads lower the more moisture there is in the soil. + +1. Run the Python app. You will see the relay turn on or off depending on the soil moisture levels. Try in dry soil, then add water. + + ```output + Soil Moisture: 638 + Soil Moisture is too low, turning relay on. + Soil Moisture: 452 + Soil Moisture is too low, turning relay on. + Soil Moisture: 347 + Soil Moisture is ok, turning relay off. + ``` + +> ๐Ÿ’ You can find this code in the [code-relay/pi](code-relay/pi) folder. + +๐Ÿ˜€ Your soil moisture sensor controlling a relay program was a success! diff --git a/2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md b/2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md new file mode 100644 index 00000000..85389d7f --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md @@ -0,0 +1,113 @@ +# Control a relay - Virtual IoT Hardware + +In this part of the lesson, you will add a relay to your Raspberry Pi in addition to the soil moisture sensor, and control it based off the soil moisture level. + +## Virtual Hardware + +The virtual IoT device will use a simulated Grove relay. This keeps this lab the same as using a Raspberry Pi with a physical Grove relay. + +In a physical IoT device, the relay would be a normally-open relay (meaning the output circuit is open, or disconnected when there is no signal sent to the relay). A relay like this can handle output circuits up to 250V and 10A. + +### Add the relay to CounterFit + +To use a virtual relay, you need to add it to the CounterFit app + +#### Task + +Add the relay to the CounterFit app. + +1. Open the `soil-moisture-sensor` project from the last lesson in VS Code if it's not already open. You will be adding to this project. + +1. Make sure the CounterFit web app is running + +1. Create a relay: + + 1. In the *Create actuator* box in the *Actuators* pane, drop down the *Actuator type* box and select *Relay*. + + 1. Set the *Pin* to *5* + + 1. Select the **Add** button to create the relay on Pin 5 + + ![The relay settings](../../../images/counterfit-create-relay.png) + + The relay will be created and appear in the actuators list. + + ![The relay created](../../../images/counterfit-relay.png) + +## Program the relay + +The soil moisture sensor app can now be programmed to use the virtual relay. + +### Task + +Program the virtual device. + +1. Open the `soil-moisture-sensor` project from the last lesson in VS Code if it's not already open. You will be adding to this project. + +1. Add the following code to the `app.py` file below the existing imports: + + ```python + from counterfit_shims_grove.grove_relay import GroveRelay + ``` + + This statement imports the `GroveRelay` from the Grove Python shim libraries to interact with the virtual Grove relay. + +1. Add the following code below the declaration of the `ADC` class to create a `GroveRelay` instance: + + ```python + relay = GroveRelay(5) + ``` + + This creates a relay using pin **5**, the pin you connected the relay to. + +1. To test the relay is working, add the following to the `while True:` loop: + + ```python + relay.on() + time.sleep(.5) + relay.off() + ``` + + The code turns the relay on, waits 0.5 seconds, then turns the relay off. + +1. Run the Python app. The relay will turn on and off every 10 seconds, with a half second delay between turning on and off. You will see the virtual relay in the CounterFit app close and open as the relay is turned on and off. + + ![The virtual relay turning on and off](../../../images/virtual-relay-turn-on-off.gif) + +## Control the relay from soil moisture + +Now that the relay is working, it can be controlled in response to soil moisture readings. + +### Task + +Control the relay. + +1. Delete the 3 lines of code that you added to test the relay. Replace them with the following code in its place: + + ```python + if soil_moisture > 450: + print("Soil Moisture is too low, turning relay on.") + relay.on() + else: + print("Soil Moisture is ok, turning relay off.") + relay.off() + ``` + + This code checks the soil moisture level from the soil moisture sensor. if it is above 450, it turns on the relay, turning it off if it goes below 450. + + > ๐Ÿ’ Remember the capacitive soil moisture sensor reads lower the more moisture there is in the soil. + +1. Run the Python app. You will see the relay turn on or off depending on the soil moisture levels. Change the *Value* or the *Random* settings for the soil moisture sensor to see the value change. + + ```output + Soil Moisture: 638 + Soil Moisture is too low, turning relay on. + Soil Moisture: 452 + Soil Moisture is too low, turning relay on. + Soil Moisture: 347 + Soil Moisture is ok, turning relay off. + ``` + +> ๐Ÿ’ You can find this code in the [code-relay/virtual-device](code-relay/virtual-device) folder. + +๐Ÿ˜€ Your virtual soil moisture sensor controlling a relay program was a success! diff --git a/2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md b/2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md new file mode 100644 index 00000000..99a7ca46 --- /dev/null +++ b/2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md @@ -0,0 +1,107 @@ +# Control a relay - Wio Terminal + +In this part of the lesson, you will add a relay to your Wio Terminal in addition to the soil moisture sensor, and control it based off the soil moisture level. + +## Hardware + +The Wio Terminal needs a relay. + +The relay you'll use is a [Grove relay](https://www.seeedstudio.com/Grove-Relay.html), a normally-open relay (meaning the output circuit is open, or disconnected when there is no signal sent to the relay) that can handle output circuits up to 250V and 10A. + +This is a digital actuator, so connects to digital pins on the Wio Terminal. The combined analog/digital port is already in use with the soil moisture sensor, so this plugs into the other port, which is a combined I2C and digital port. + +### Connect the relay + +The Grove relay can be connected to the Wio Terminals digital port. + +#### Task + +Connect the relay. + +![A grove relay](../../../images/grove-relay.png) + +1. Insert one end of a Grove cable into the socket on the relay. It will only go in one way round. + +1. With the Wio Terminal disconnected from your computer or other power supply, connect the other end of the Grove cable to the left-hand side Grove socket on the Wio Terminal as you look at the screen. Leave the soil moisture sensor connected to the right-hand socket. + +![The grove relay connected to the left-hand socket, and the soil moisture sensor connected to the right hand socket](../../../images/wio-relay-and-soil-moisture-sensor.png) + +1. Insert the soil moisture sensor into soil, if it isn't already from the previous lesson. + +## Program the relay + +The Wio Terminal can now be programmed to use the attached relay. + +### Task + +Program the device. + +1. Open the `soil-moisture-sensor` project from the last lesson in VS Code if it's not already open. You will be adding to this project. + +1. There isn't a library for this actuator - it's a digital actuator controlled by a high or low signal. To turn it on, you send a high signal to the pin (3.3V), turn turn it off you send a low signal (0V). You can do this using the built-in Arduino [`digitalWrite`](https://www.arduino.cc/reference/en/language/functions/digital-io/digitalwrite/) function. Start by adding the following to the bottom of the `setup` function to setup the combined I2C/digital port as an output pin to send a voltage to the relay: + + ```cpp + pinMode(PIN_WIRE_SCL, OUTPUT); + ``` + + `PIN_WIRE_SCL` is the port number for the combined I2C/digital port. + +1. To test the relay is working, add the following to the `loop` function, below the final `delay`: + + ```cpp + digitalWrite(PIN_WIRE_SCL, HIGH); + delay(500); + digitalWrite(PIN_WIRE_SCL, LOW); + ``` + + The code writes a high signal to the pin that the relay is connected to to turn it on, waits 500ms (half a second), then writes a low signal to turn the relay off. + +1. Build and upload the code to the Wio Terminal. + +1. Once uploaded, the relay will turn on and off every 10 seconds, with a half second delay between turning on and off. You will hear the relay click on then click off. An LED on the Grove board will light when the relay is on, then go out when the relay is off. + + ![The relay turning on and off](../../../images/relay-turn-on-off.gif) + +## Control the relay from soil moisture + +Now that the relay is working, it can be controlled in response to soil moisture readings. + +### Task + +Control the relay. + +1. Delete the 3 lines of code that you added to test the relay. Replace them with the following code in its place: + + ```cpp + if (soil_moisture > 450) + { + Serial.println("Soil Moisture is too low, turning relay on."); + digitalWrite(PIN_WIRE_SCL, HIGH); + } + else + { + Serial.println("Soil Moisture is ok, turning relay off."); + digitalWrite(PIN_WIRE_SCL, LOW); + } + ``` + + This code checks the soil moisture level from the soil moisture sensor. if it is above 450, it turns on the relay, turning it off if it goes below 450. + + > ๐Ÿ’ Remember the capacitive soil moisture sensor reads lower the more moisture there is in the soil. + +1. Build and upload the code to the Wio Terminal. + +1. Monitor the device via the serial monitor. You will see the relay turn on or off depending on the soil moisture levels. Try in dry soil, then add water. + + ```output + Soil Moisture: 638 + Soil Moisture is too low, turning relay on. + Soil Moisture: 452 + Soil Moisture is too low, turning relay on. + Soil Moisture: 347 + Soil Moisture is ok, turning relay off. + ``` + +> ๐Ÿ’ You can find this code in the [code-relay/wio-terminal](code-relay/wio-terminal) folder. + +๐Ÿ˜€ Your soil moisture sensor controlling a relay program was a success! diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md new file mode 100644 index 00000000..107eb6a8 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md @@ -0,0 +1,431 @@ +# Migrate your plant to the cloud + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/15) + +## Introduction + +In the last lesson, you learned how to connect your plant to an MQTT broker and controlled a relay from some server code running locally. This forms the core of the kind of internet-connected automated watering system that is used from individual plants at home up to commercial farms. + +The IoT device communicated with a public MQTT broker as a way to demonstrate the principles, but this is not the most reliable or secure way. In this lesson you will learn about the cloud, and the IoT capabilities provided by public cloud services. You will also learn how to migrate your plant to one of these cloud services from the public MQTT broker. + +In this lesson we'll cover: + +* [What is the cloud?](#what-is-the-cloud) +* [Create a cloud subscription](#create-a-cloud-subscription) +* [Cloud IoT services](#cloud-iot-services) +* [Create an IoT service in the cloud](#create-an-iot-service-in-the-cloud) +* [Communicate with IoT Hub](#communicate-with-iot-hub) +* [Connect your device to the IoT service](#connect-your-device-to-the-iot-service) + +## What is the cloud? + +Before the cloud, when a company wanted to provide services to their employees (such as databases or file storage), or to the public (such as web sites), they would build and run a data center. This ranged from a room with a small number of computers in it, to a building with many computers. The company would manage everything, including: + +* Buying computers +* Hardware maintenance +* Power and cooling +* Networking +* Security, including securing the building and securing the software on the computers +* Software installation and updates + +This could be very expensive, require a wide range of skilled employees, and be very slow to change when needed. For example, if an online store needed to plan for a busy holiday season, they would need to plan months in advance to buy more hardware, configure it, install it and install the software to run their sales process. After the holiday season was over and sales dropped back down, they would be left with computers they've paid for sitting idle till the next busy season. + +โœ… Do you think this would allow companies to move quickly? If an online clothing retailer suddenly got popular due to a celebrity being seen in their clothes, would they be able to increase their computing power quickly enough to support the sudden influx of orders? + +### Somebody else's computer + +The cloud is often jokingly referred to as 'somebody else's computer'. The initial idea was simple - instead of buying computers, you rent somebody else's computer. Someone else, a cloud computing provider, would manage huge data centers. They would be responsible for buying and installing the hardware, managing power and cooling, networking, building security, hardware and software updates, everything. As a customer, you would rent the computers you need, renting more as demand spikes, then reducing the number you rent if demand drops. These cloud data centers are all around the world. + +![A Microsoft cloud data center](../../../images/azure-region-existing.png) +![A Microsoft cloud data center planned expansion](../../../images/azure-region-planned-expansion.png) + +These data centers can be multiple square kilometers in size. The images above were taken a few years ago at a Microsoft cloud data center, and show the initial size, along with a planned expansion. The area cleared for the expansion is over 5 square kilometers. + +> ๐Ÿ’ These data centers require such large amounts of power that some have their own power stations. Because of their size and the level of investment from the cloud providers, they are usually very environmentally friendly. They are more efficient than huge numbers of small data centers, they run mostly on renewable energy, and cloud providers work hard to reduce waste, cut water usage, and replant forests to make up for those cut down to provide space to build data centers. You can read mode about how one cloud provider is working on sustainability on the [Azure sustainability site](https://azure.microsoft.com/global-infrastructure/sustainability/?WT.mc_id=academic-17441-jabenn). + +โœ… Do some research: Read up on the major clouds such as [Azure from Microsoft](https://azure.microsoft.com/?WT.mc_id=academic-17441-jabenn) or [GCP from Google](https://cloud.google.com). How many data centers do they have, and where are they in the world? + +Using the cloud keeps costs down for companies, and allows them to focus on what they do best, leaving the cloud computing expertise in the hands of the provider. Companies no longer need to rent or buy data center space or pay different providers for connectivity and power and employee experts. Instead they can pay one monthly bill to the cloud provider to have everything taken care off. + +The cloud provider can then use economies of scale to drive costs down, buying computers in bulk at lower costs, investing in tooling to reduce their workload for maintenance, even designing and building their own hardware to improve their cloud offering. + +### Microsoft Azure + +Azure is the developer cloud from Microsoft, and this is the cloud you will be using for these lessons. The video below gives a short overview of Azure: + +[![Overview of Azure video](../../../images/what-is-azure-video-thumbnail.png)](https://www.microsoft.com/videoplayer/embed/RE4Ibng?WT.mc_id=academic-17441-jabenn) + +## Create a cloud subscription + +To use services in the cloud, you will need to sign up for a subscription with a cloud provider. For this lesson, you will be signing up for a Microsoft Azure subscription. If you already have an Azure subscription you can skip this task. The subscription details described here are correct at the time of writing, but may change. + +> ๐Ÿ’ If you are accessing these lessons through your school, you may already have an Azure subscription available to you. Check with your teacher. + +There are two different types of free Azure subscription you can sign up for: + +* **Azure for Students** - This is a subscription designed for students 18+. You don't need a credit card to sign up, and you use your school email address to validate that you are a student. When you sign up you get US$100 to spend on cloud resources, along with free services including a free version of an IoT service. This lasts 12 months, and you can renew every year that you remain a student. + +* **Azure free subscription** - This is a subscription for anyone who is not a student. You will need a credit card to sign up to for the subscription, but your card will not be billed, this is just used to verify you are a real human, not a bot. You get $200 of credit to use in the first 30 days on any service, along with free tiers of Azure services. Once your credit has been used up, your card will not be charged unless you convert to a pay as you go subscription. + +> ๐Ÿ’ Microsoft does offer an Azure for Students Starter subscription for students under 18, but at the time of writing this doesn't support any IoT services. + +### Task - sign up for a free cloud subscription + +If you are a student aged 18+, then you can sign up for an Azure for Students subscription. You will need to validate with a school email address. You can do this in one of two ways: + +* Sign up for a GitHub student developer pack at [education.github.com/pack](https://education.github.com/pack). This gives you access to a range of tools and offers, including GitHub and Microsoft Azure. Once you sign up for the developer pack, you can then activate the Azure for Students offer. + +* Sign up directly for an Azure for Students account at [azure.microsoft.com/free/students](https://azure.microsoft.com/free/students/?WT.mc_id=academic-17441-jabenn). + +> โš ๏ธ If your school email address is not recognized, raise an [issue in this repo](https://github.com/Microsoft/IoT-For-Beginners/issues) and we'll see if it can be added to the Azure for Students allow list. + +If you are not a student, or you don't have a valid school email address, then you can sign up for an Azure Free subscription. + +* Sign up for an Azure Free Subscription at [azure.microsoft.com/free](https://azure.microsoft.com/free/?WT.mc_id=academic-17441-jabenn) + +## Cloud IoT services + +The public test MQTT broker you have been using is a great tool when learning, but has a number of drawbacks as a tool to use in a commercial setting: + +* Reliability - it's a free service with no guarantees, and can be turned off at any time +* Security - it is public, so anyone could listen to your telemetry or send commands to control your hardware +* Performance - it is designed for only a few test messages, so wouldn't cope with a large amount of messages being sent +* Discovery - there is no way to know what devices are connected + +IoT services in the cloud solve these problems. They are maintained by large cloud providers who invest heavily in reliability and are on hand to fix any issues that might arise. They have security baked in to stop hackers reading your data or sending rogue commands. They are also high performance, being able to handle many millions of messages every day, taking advantage of the cloud to scale as needed. + +> ๐Ÿ’ Although you pay for these upsides with a monthly fee, most cloud providers offer a free version of their IoT service with a limited amount of messages per day or devices that can connect. This free version is usually more than enough for a developer to learn about the service. In this lesson you will be using a free version. + +IoT devices connect to a cloud service either using a device SDK (a library that provides code to work with the features of the service), or directly via a communication protocol such as MQTT or HTTP. The device SDK is usually the easiest route to take as it handles everything for you, such as knowing what topics to publish or subscribe to, and how to handle security. + +![Devices connect to a service using a device SDK. Server code also connects to the service via an SDK](../../../images/iot-service-connectivity.png) + +***Devices connect to a service using a device SDK. Server code also connects to the service via an SDK. Microcontroller by Template / Cloud by Debi Alpa Nugraha / IoT by Adrien Coquet - all from the [Noun Project](https://thenounproject.com)*** + +Your device then communicates with other parts of your application over this service - similar to how you sent telemetry and received commands over MQTT. This is usually using a service SDK or a similar library. Messages come from your device to the service where other components of your application can then read them, and messages can then be sent back to your device. + +![Devices without a valid secret key cannot connect to the IoT service](../../../images/iot-service-allowed-denied-connection.png) + +***Devices without a valid secret key cannot connect to the IoT service. Microcontroller by Template / Cloud by Debi Alpa Nugraha / IoT by Adrien Coquet - all from the [Noun Project](https://thenounproject.com)*** + +These services implement security by knowing about all the devices that can connect and send data, either by having the devices pre-registered with the service, or by giving the devices secret keys or certificates they can use to register themselves with the service the first time they connect. Unknown devices are unable to connect, if they try the service rejects the connection and ignores messages sent by them. + +โœ… Do some research: What is the downside of having an open IoT service where any device or code can connect? Can you find specific examples of hackers taking advantage of this? + +Other components of your application can connect to the IoT service and learn about all the devices that are connected or registered, and communicate with them directly in bulk or individually. + +> ๐Ÿ’ IoT services also implement additional capabilities, and the cloud providers have additional services and applications that can be connected to the service. For example, if you want to store all the telemetry messages sent by all the devices in a database, it's usually only a few clicks in the cloud provider's configuration tool to connect the service to a database and stream the data in. + +## Create an IoT service in the cloud + +Now that you have an Azure subscription, you can sign up for an IoT service. The IoT service from Microsoft is called Azure IoT Hub. + +![The Azure IoT Hub logo](../../../images/azure-iot-hub-logo.png) + +The video below gives a short overview of Azure IoT Hub: + +[![Overview of Azure IoT Hub video](https://img.youtube.com/vi/smuZaZZXKsU/0.jpg)](https://www.youtube.com/watch?v=smuZaZZXKsU) + +โœ… Take a moment to do some research and read the overview of IoT hub in the [Microsoft IoT Hub documentation](https://docs.microsoft.com/azure/iot-hub/about-iot-hub?WT.mc_id=academic-17441-jabenn). + +The cloud services available in Azure can be configured through a web-based portal, or via a command-line interface (CLI). For this task, you will use the CLI. + +### Task - install the Azure CLI + +To use the Azure CLI, first it must be installed on your PC or Mac. + +1. Follow the instructions in the [Azure CLI documentation](https://docs.microsoft.com/cli/azure/install-azure-cli?WT.mc_id=academic-17441-jabenn) to install the CLI. + +1. The Azure CLI supports a number of extensions that add capabilities to manage a wide range of Azure services. Install the IoT extension by running the following command from your command line or terminal: + + ```sh + az extension add --name azure-iot + ``` + +1. From your command line or terminal, run the following command to log in to your Azure subscription from the Azure CLI. + + ```sh + az login + ``` + + A web page will be launched in your default browser. Log in using the account you used to sign up for your Azure subscription. Once you are logged in, you can close the browser tab. + +1. If you have multiple Azure subscriptions, such as a school provided one, and your own Azure for Students subscription, you will need to select the one you want to use. Run the following command to list all the subscriptions you have access to: + + ```sh + az account list --output table + ``` + + In the output, you will see the name of each subscription along with its `SubscriptionId`. + + ```output + โžœ ~ az account list --output table + Name CloudName SubscriptionId State IsDefault + ---------------------- ----------- ------------------------------------ ------- ----------- + School-subscription AzureCloud cb30cde9-814a-42f0-a111-754cb788e4e1 Enabled True + Azure for Students AzureCloud fa51c31b-162c-4599-add6-781def2e1fbf Enabled False + ``` + + To select the subscription you want to use, use the following command: + + ```sh + az account set --subscription + ``` + + Replace `` with the Id of hte subscription you want to use. After running this command, re-run the command to list your accounts. You will see the `IsDefault` column will be marked as `True` for the subscription you have just set. + +### Task - create a resource group + +Azure services, such as IoT Hub instances, virtual machines, databases, or AI services, are referred to as **resources**. Every resource has to live inside a **Resource Group**, a logical grouping of one or more resources. + +> ๐Ÿ’ Using resource groups means you can manage multiple services at once. For example, once you have finished all the lessons for this project you can delete the resource group, and all the resources in it will be deleted automatically. + +1. There are multiple Azure data centers around the world, divided up into regions. When you create an Azure resource or resource group, you have to specify where you want it created. Run the following command to get the list of locations: + + ```sh + az account list-locations --output table + ``` + + You will see a list of locations. This list will be long. + + > ๐Ÿ’ At the time of writing, there are 65 locations you can deploy to. + + ```output + โžœ ~ az account list-locations --output table + DisplayName Name RegionalDisplayName + ------------------------ ------------------- ------------------------------------- + East US eastus (US) East US + East US 2 eastus2 (US) East US 2 + South Central US southcentralus (US) South Central US + ... + ``` + + Note down the value from the `Name` column of the region closest to you. You can find the regions on a map on the [Azure geographies page](https://azure.microsoft.com/global-infrastructure/geographies/?WT.mc_id=academic-17441-jabenn). + +1. Run the following command to create a resource group called `soil-moisture-sensor`. Resource group names have to be unique in your subscription. + + ```sh + az group create --name soil-moisture-sensor \ + --location + ``` + + Replace `` with the location you selected in the previous step. + +### Task - create an IoT Hub + +You can now create an IoT Hub resource in your resource group. + +1. Use the following command to create your IoT hub resource: + + ```sh + az iot hub create --resource-group soil-moisture-sensor \ + --sku F1 \ + --partition-count 2 \ + --name + ``` + + Replace `` with a name for your hub. This name needs to be globally unique - that is no other IoT Hub created by anyone can have the same name. This name is used in a URL that points to the hub, so needs to be unique. Use something like `soil-moisture-sensor-` and add a unique identifier on the end, like some random words or your name. + + The `--sku F1` option tells it to use a free tier. The free tier supports 8,000 messages a day along with most of the features of the full-price tiers. + + > ๐ŸŽ“ Different pricing levels of Azure services are referred to as tiers. Each tier has a different cost and provides different features or data volumes. + + > ๐Ÿ’ If you want to learn more about pricing, you can check out the [Azure IoT Hub pricing guide](https://azure.microsoft.com/pricing/details/iot-hub/?WT.mc_id=academic-17441-jabenn). + + The `--partition-count 2` option defines how many streams of data the IoT Hub supports, more partitions reduce data blocking when multiple things read and write from the IoT Hub. Partitions are outside the scope of these lessons, but this value needs to be set to create a free tier IoT Hub. + + > ๐Ÿ’ You can only have one free tier IoT Hub per subscription. + +The IoT Hub will be created. It make take a minute or so for this to complete. + +## Communicate with IoT Hub + +In the previous lesson, you used MQTT and sent messages back and forward on different topics, with the different topics having different purposes. Rather than send messages over different topics, IoT Hub has a number of defined ways for the device to communicate with the Hub, or the Hub to communicate with the device. + +> ๐Ÿ’ Under the hood this communication between IoT Hub and your device can use MQTT, HTTPS or AMQP. + +* Device to cloud (D2C) messages - these are messages sent from a device to IoT Hub, such as telemetry. They can then ber read off the IoT Hub by your application code + + > ๐ŸŽ“ Under the hood, IoT Hub uses an Azure service called [Event Hubs](https://docs.microsoft.com/azure/event-hubs/?WT.mc_id=academic-17441-jabenn). When you write code to read messages sent to the hub, these are often called events. + +* Cloud to device (C2D) messages - these are messages sent from application code, via an IoT Hub to an IoT device + +* Direct method requests - these are messages sent from application code via an IoT Hub to an IoT device to request that the device does something, such as control an actuator. These messages require a response so your application code can tell if it was successfully processed. + +* Device twins - these are JSON documents kept synchronized between the device and IoT Hub, and are used to store settings or other properties either reported by the device, or should be set on the device (known as desired) by the IoT Hub. + +IoT Hub can store messages and direct method requests for a configurable period of time (defaulting to one day), so if a device or application code loses connection, it can still retrieve messages sent whilst it was offline after it reconnects. Device twins are kept permanently in the IoT Hub, so at any time a device can reconnect and get the latest device twin. + +โœ… Do some research: Read more on these message types on the [Device-to-cloud communications guidance](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-d2c-guidance?WT.mc_id=academic-17441-jabenn), an the [Cloud-to-device communications guidance](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-c2d-guidance?WT.mc_id=academic-17441-jabenn) in the IoT Hub documentation. + +## Connect your device to the IoT service + +Once the hub is created, your IoT device can connect to it. Only registered devices can connect to a service, so you will need to register your device first. When you register you can get back a connection string that the device can use to connect. This connection string is device specific, and contains information about the IoT Hub, the device, and a secret key that will allow this device to connect. + +> ๐ŸŽ“ A connection string is a generic term for a piece of text that contains connection details. These are used when connecting to IoT Hubs, databases and many other services. They usually consist of an identifier for the service, such as a URL, and security information such as a secret key. These are passed to SDKs to connect to the service. + +> โš ๏ธ Connection strings should be kept secure! Security will be covered in more detail in a future lesson. + +### Task - register your IoT device + +The IoT device can be registered with your IoT Hub using the Azure CLI. + +1. Run the following command to register a device: + + ```sh + az iot hub device-identity create --device-id soil-moisture-sensor \ + --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + + This will create a device with an ID of `soil-moisture-sensor`. + +1. When your IoT device connects to your IoT Hub using the SDK, it needs to use a connection string that gives the URL of the hub, along with a secret key. Run the following command to get the connection string: + + ```sh + az iot hub device-identity connection-string show --device-id soil-moisture-sensor \ + --output table \ + --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + +1. Store the connection string that is shown in the output as you will need it later. + +### Task - connect your IoT device to the cloud + +Work through the relevant guide to connect your IoT device to the cloud: + +* [Arduino - Wio Terminal](wio-terminal-connect-hub.md) +* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-connect-hub.md) + +### Task - monitor events + +For now, you won't be updating your server code. Instead you can use the Azure CLI to monitor events from your IoT device. + +1. Make sure your IoT device is running and sending soil moisture telemetry values + +1. Run the following command in your command prompt or terminal to monitor messages sent to your IoT Hub: + + ```sh + az iot hub monitor-events --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + + You will see messages appear in the console output as they are sent by your IoT device. + + ```output + Starting event monitor, use ctrl-c to stop... + { + "event": { + "origin": "soil-moisture-sensor", + "module": "", + "interface": "", + "component": "", + "payload": "{\"soil_moisture\": 376}" + } + }, + { + "event": { + "origin": "soil-moisture-sensor", + "module": "", + "interface": "", + "component": "", + "payload": "{\"soil_moisture\": 381}" + } + } + ``` + + The contents of the `payload` will match the message sent by your IoT device. + +1. These messages have a number of properties attached to them automatically, such as the timestamp they were sent. These are known as *annotations*. To view all the message annotations, use the following command: + + ```sh + az iot hub monitor-events --properties anno --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + + You will see messages appear in the console output as they are sent by your IoT device. + + ```output + Starting event monitor, use ctrl-c to stop... + { + "event": { + "origin": "soil-moisture-sensor", + "module": "", + "interface": "", + "component": "", + "properties": {}, + "annotations": { + "iothub-connection-device-id": "soil-moisture-sensor", + "iothub-connection-auth-method": "{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}", + "iothub-connection-auth-generation-id": "637553997165220462", + "iothub-enqueuedtime": 1619976150288, + "iothub-message-source": "Telemetry", + "x-opt-sequence-number": 1379, + "x-opt-offset": "550576", + "x-opt-enqueued-time": 1619976150277 + }, + "payload": "{\"soil_moisture\": 381}" + } + } + ``` + + The time values in the annotations are in [UNIX time](https://wikipedia.org/wiki/Unix_time), representing the number of seconds since midnight on 1st January 1970. + +### Task - control your IoT device + +You can also use the Azure CLI to call direct methods on your IoT device. + +1. Run the following command in your command prompt or terminal to invoke the `relay_on` method on the IoT device: + + ```sh + az iot hub invoke-device-method --device-id soil-moisture-sensor \ + --method-name relay_on \ + --method-payload '{}' \ + --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + + This sends a direct method request for the method specified by `method-name`. Direct methods can take a payload containing data for the method, and this can be specified in the `method-payload` parameter as JSON. + + You will see the relay turn on, and the corresponding output from your IoT device: + + ```output + Direct method received - relay_on + ``` + +1. Repeat the above step, but set the `--method-name` to `relay_off`. You will see the relay turn off and the corresponding output from the IoT device. + +--- + +## ๐Ÿš€ Challenge + +The free tier of IoT Hub allows 8,000 messages a day. The code you wrote sends telemetry messages every 10 seconds. How many messages a day is one message every 10 seconds? + +Think about how often soil moisture measurements should be sent? How can you change your code to stay within the free tier and check as often as needed but not too often? What if you wanted to add a second device? + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/16) + +## Review & Self Study + +The IoT Hub SDK is open source for both Arduino and Python. In the code repos on GitHub there are a number of samples showing how work with different IoT Hub features. + +* If you are using a Wio Terminal, check out the [Arduino samples on GitHub](https://github.com/Azure/azure-iot-pal-arduino/tree/master/pal/samples) +* If you are using a Raspberry Pi or Virtual device, check out the [Python samples on GitHub](https://github.com/Azure/azure-iot-sdk-python/tree/master/azure-iot-hub/samples) + +## Assignment + +[Learn about cloud services](assignment.md) diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md new file mode 100644 index 00000000..87d285ad --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md @@ -0,0 +1,19 @@ +# Learn about cloud services + +## Instructions + +Clouds, such as Azure from Microsoft, offer more than just computes to rent. The main type of cloud offerings include: + +* Infrastructure as a service (IaaS) +* Platform as a service (PaaS) +* Serverless +* Software as a service (SaaS) + +Learn about these different types of offerings, and explain what they are and how they differ. Explain which offerings are relevant for IoT developers. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Explain the different cloud offerings | Gave clear explanations of all 4 types of offerings | Was able to explain 3 types of offerings | Was only able to explain 1 or 2 of the offerings | +| Explain which offering is relevant for IoT | Described an explanation of which offerings are relevant for IoT developers and why | Described an explanation of which offerings are relevant for IoT developers but not why | Was unable to describe which offerings are relevant for IoT developers | diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/pi/soil-moisture-sensor/app.py b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/pi/soil-moisture-sensor/app.py new file mode 100644 index 00000000..4447137c --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/pi/soil-moisture-sensor/app.py @@ -0,0 +1,38 @@ +import time +from grove.adc import ADC +from grove.grove_relay import GroveRelay +import json +from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse + +connection_string = "" + +adc = ADC() +relay = GroveRelay(5) + +device_client = IoTHubDeviceClient.create_from_connection_string(connection_string) + +print("Connecting") +device_client.connect() +print("Connected") + +def handle_method_request(request): + print("Direct method received - ", request.name) + + if request.name == "relay_on": + relay.on() + elif request.name == "relay_off": + relay.off() + + method_response = MethodResponse.create_from_method_request(request, 200) + device_client.send_method_response(method_response) + +device_client.on_method_request_received = handle_method_request + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + message = Message(json.dumps({ "soil_moisture": soil_moisture })) + device_client.send_message(message) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/virtual-device/soil-moisture-sensor/app.py b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/virtual-device/soil-moisture-sensor/app.py new file mode 100644 index 00000000..6a118e48 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/virtual-device/soil-moisture-sensor/app.py @@ -0,0 +1,41 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +from counterfit_shims_grove.adc import ADC +from counterfit_shims_grove.grove_relay import GroveRelay +import json +from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse + +connection_string = "" + +adc = ADC() +relay = GroveRelay(5) + +device_client = IoTHubDeviceClient.create_from_connection_string(connection_string) + +print("Connecting") +device_client.connect() +print("Connected") + +def handle_method_request(request): + print("Direct method received - ", request.name) + + if request.name == "relay_on": + relay.on() + elif request.name == "relay_off": + relay.off() + + method_response = MethodResponse.create_from_method_request(request, 200) + device_client.send_method_response(method_response) + +device_client.on_method_request_received = handle_method_request + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + message = Message(json.dumps({ "soil_moisture": soil_moisture })) + device_client.send_message(message) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/.gitignore b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/.vscode/extensions.json b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/include/README b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/lib/README b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/platformio.ini b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/platformio.ini new file mode 100644 index 00000000..a240bd42 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/platformio.ini @@ -0,0 +1,30 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + bblanchon/ArduinoJson @ 6.17.3 + seeed-studio/Seeed Arduino rpcWiFi @ 1.0.3 + seeed-studio/Seeed Arduino FS @ 2.0.2 + seeed-studio/Seeed Arduino SFUD @ 2.0.1 + seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 + seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 + seeed-studio/Seeed Arduino RTC @ 2.0.0 + arduino-libraries/AzureIoTHub @ 1.6.0 + azure/AzureIoTUtility @ 1.6.1 + azure/AzureIoTProtocol_MQTT @ 1.6.0 + azure/AzureIoTProtocol_HTTP @ 1.6.0 + azure/AzureIoTSocket_WiFi @ 1.0.2 + +build_flags = + -DDONT_USE_UPLOADTOBLOB \ No newline at end of file diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/config.h b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/config.h new file mode 100644 index 00000000..112c3f56 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/config.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +using namespace std; + +// WiFi credentials +const char *SSID = ""; +const char *PASSWORD = ""; + +// IoT Hub settings +const char *CONNECTION_STRING = ""; \ No newline at end of file diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/main.cpp b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/main.cpp new file mode 100644 index 00000000..23ef9191 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/main.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include + +#include "config.h" + +#include +#include +#include +#include "ntp.h" + +IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle; + +static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *user_context) +{ + if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED) + { + Serial.println("The device client is connected to iothub"); + } + else + { + Serial.println("The device client has been disconnected"); + } +} + +int directMethodCallback(const char *method_name, const unsigned char *payload, size_t size, unsigned char **response, size_t *response_size, void *userContextCallback) +{ + Serial.printf("Direct method received %s\r\n", method_name); + + if (strcmp(method_name, "relay_on") == 0) + { + digitalWrite(PIN_WIRE_SCL, HIGH); + } + else if (strcmp(method_name, "relay_off") == 0) + { + digitalWrite(PIN_WIRE_SCL, LOW); + } + + char resultBuff[16]; + sprintf(resultBuff, "{\"Result\":\"\"}"); + *response_size = strlen(resultBuff); + *response = (unsigned char *)malloc(*response_size); + memcpy(*response, resultBuff, *response_size); + + return IOTHUB_CLIENT_OK; +} + +void connectIoTHub() +{ + IoTHub_Init(); + + _device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol); + + if (_device_ll_handle == NULL) + { + Serial.println("Failure creating Iothub device. Hint: Check your connection string."); + return; + } + + IoTHubDeviceClient_LL_SetConnectionStatusCallback(_device_ll_handle, connectionStatusCallback, NULL); + IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL); +} + +void sendTelemetry(const char *telemetry) +{ + IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(telemetry); + IoTHubDeviceClient_LL_SendEventAsync(_device_ll_handle, message_handle, NULL, NULL); + IoTHubMessage_Destroy(message_handle); +} + +void connectWiFi() +{ + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("Connecting to WiFi.."); + WiFi.begin(SSID, PASSWORD); + delay(500); + } + + Serial.println("Connected!"); +} + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + pinMode(A0, INPUT); + pinMode(PIN_WIRE_SCL, OUTPUT); + + connectWiFi(); + + initTime(); + + connectIoTHub(); + delay(2000); +} + +void work_delay(int delay_time) +{ + int current = 0; + do + { + IoTHubDeviceClient_LL_DoWork(_device_ll_handle); + delay(100); + current += 100; + } while (current < delay_time); +} + +void loop() +{ + int soil_moisture = analogRead(A0); + + DynamicJsonDocument doc(1024); + doc["soil_moisture"] = soil_moisture; + + string telemetry; + JsonObject obj = doc.as(); + serializeJson(obj, telemetry); + + Serial.print("Sending telemetry "); + Serial.println(telemetry.c_str()); + sendTelemetry(telemetry.c_str()); + + work_delay(10000); +} diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/ntp.h b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/ntp.h new file mode 100644 index 00000000..573b2de1 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/src/ntp.h @@ -0,0 +1,43 @@ +#pragma once + +#include "DateTime.h" +#include +#include "samd/NTPClientAz.h" +#include + +static void initTime() +{ + WiFiUDP _udp; + time_t epochTime = (time_t)-1; + NTPClientAz ntpClient; + + ntpClient.begin(); + + while (true) + { + epochTime = ntpClient.getEpochTime("0.pool.ntp.org"); + + if (epochTime == (time_t)-1) + { + Serial.println("Fetching NTP epoch time failed! Waiting 2 seconds to retry."); + delay(2000); + } + else + { + Serial.print("Fetched NTP epoch time is: "); + + char buff[32]; + sprintf(buff, "%.f", difftime(epochTime, (time_t)0)); + Serial.println(buff); + break; + } + } + + ntpClient.end(); + + struct timeval tv; + tv.tv_sec = epochTime; + tv.tv_usec = 0; + + settimeofday(&tv, NULL); +} \ No newline at end of file diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/test/README b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal/soil-moisture-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md new file mode 100644 index 00000000..2dc79635 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md @@ -0,0 +1,116 @@ +# Connect your IoT device to the cloud - Virtual IoT Hardware and Raspberry Pi + +In this part of the lesson, you will connect your virtual IoT device or Raspberry Pi to your IoT Hub, to send telemetry and receive commands. + +## Connect your device to IoT Hub + +The next step is to connect your device to IoT Hub. + +### Task - connect to IoT Hub + +1. Open the `soil-moisture-sensor` folder in VS Code. Make sure the virtual environment is running in the terminal if you are using a virtual IoT device. + +1. Install some additional Pip packages: + + ```sh + pip3 install azure-iot-device + ``` + + `azure-iot-device` is a library to communicate with your IoT Hub. + +1. Add the following imports to the top of the `app.py` file, below the existing imports: + + ```python + from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse + ``` + + This code imports the SDK to communicate with your IoT Hub. + +1. Remove the `import paho.mqtt.client as mqtt` line as this library is no longer needed. Remove all the MQTT code including the topic names, all code that uses `mqtt_client` and the `handle_command`. Keep the `while True:` loop, just delete the `mqtt_client.publish` line form this loop. + +1. Add the following code below the import statements: + + ```python + connection_string = "" + ``` + + Replace `` with the connection string you retrieved for the device earlier in this lesson. + + > ๐Ÿ’ This is not best practice. Connection strings should never be stored in source code, as this can be checked into source code control and found by anyone. We are doing this here for the sake of simplicity. Ideally you should use something like an environment variable and a tool like [`python-dotenv`](https://pypi.org/project/python-dotenv/). You will learn more about this in an upcoming lesson. + +1. Below this code, add the following to create a device client object that can communicate with IoT Hub, and connect it: + + ```python + device_client = IoTHubDeviceClient.create_from_connection_string(connection_string) + + print("Connecting") + device_client.connect() + print("Connected") + ``` + +1. Run this code. You will see your device connect. + + ```output + pi@raspberrypi:~/soil-moisture-sensor $ python3 app.py + Connecting + Connected + Soil moisture: 379 + ``` + +## Send telemetry + +Now that your device is connected, you can send telemetry to the IoT Hub instead of the MQTT broker. + +### Task - send telemetry + +1. Add the following code inside the `while True` loop, just before the sleep: + + ```python + message = Message(json.dumps({ "soil_moisture": soil_moisture })) + device_client.send_message(message) + ``` + + This code creates an IoT Hub `Message` containing the soil moisture reading as a JSON string, then sends this to the IoT Hub as a device to cloud message. + +## Handle commands + +Your device needs to handle a command from the server code to control the relay. This is sent as a direct method request. + +## Task - handle a direct method request + +1. Add the following code before the `while True` loop: + + ```python + def handle_method_request(request): + print("Direct method received - ", request.name) + + if request.name == "relay_on": + relay.on() + elif request.name == "relay_off": + relay.off() + ``` + + This defines a method, `handle_method_request`, that will be called when a direct method is called by the IoT Hub. Each direct method has a name, and this code expects a method called `relay_on` to turn the relay on, and `relay_off` to turn the relay off. + + > ๐Ÿ’ This could also be implemented in a single direct method request, passing the desired state of the relay in a payload that can be passed with the method request and available from the `request` object. + +1. Direct methods require a response to tell the calling code that they have been handled. Add the following code at the end of the `handle_method_request` function to create a response to the request: + + ```python + method_response = MethodResponse.create_from_method_request(request, 200) + device_client.send_method_response(method_response) + ``` + + This code sends a response to the direct method request with an HTTP status code of 200, and sends this back to the IoT Hub. + +1. Add the following code below this function definition: + + ```python + device_client.on_method_request_received = handle_method_request + ``` + + This code tells the IoT Hub client to call the `handle_method_request` function when a direct method is called. + +> ๐Ÿ’ You can find this code in the [code/pi](code/pi) or [code/virtual-device](code/virtual-device) folder. + +๐Ÿ˜€ Your soil moisture sensor program is connected to your IoT Hub! diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md new file mode 100644 index 00000000..abff3ed8 --- /dev/null +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md @@ -0,0 +1,292 @@ +# Connect your IoT device to the cloud - Wio Terminal + +In this part of the lesson, you will connect your Wio Terminal to your IoT Hub, to send telemetry and receive commands. + +## Connect your device to IoT Hub + +The next step is to connect your device to IoT Hub. + +### Task - connect to IoT Hub + +1. Open the `soil-moisture-sensor` project in VS Code + +1. Open the `platformio.ini` file. Remove the `knolleary/PubSubClient` library dependency. This was used to connect to the public MQTT broker, and is not needed to connect to IoT Hub. + +1. Add the following library dependencies: + + ```ini + seeed-studio/Seeed Arduino RTC @ 2.0.0 + arduino-libraries/AzureIoTHub @ 1.6.0 + azure/AzureIoTUtility @ 1.6.1 + azure/AzureIoTProtocol_MQTT @ 1.6.0 + azure/AzureIoTProtocol_HTTP @ 1.6.0 + azure/AzureIoTSocket_WiFi @ 1.0.2 + ``` + + The `Seeed Arduino RTC` library provides code to interact with a real-time clock in the Wio Terminal, used to track the time. The remaining libraries allow your IoT device to connect to IoT Hub. + +1. Add the following to the bottom of the `platformio.ini` file: + + ```ini + build_flags = + -DDONT_USE_UPLOADTOBLOB + ``` + + This sets a compiler flag that is needed when compiling the Arduino IoT Hub code. + +1. Open the `config.h` header file. Remove all the MQTT settings and add the following constant for the device connection string: + + ```cpp + // IoT Hub settings + const char *CONNECTION_STRING = ""; + ``` + + Replace `` with the connection string for your device you copied earlier. + +1. The connection to IoT Hub uses a time-based token. This means the IoT device needs to know the current time. Unlike operating systems like Windows, macOS or Linux, microcontrollers don't automatically synchronize the current time over the Internet. This means you will need to add code to get the current time from an [NTP](https://wikipedia.org/wiki/Network_Time_Protocol) server. Once the time has been retrieved, it can be stored in a real-time clock in the Wio Terminal, allowing the correct time to be requested at a later date, assuming the device doesn't lose power. Add a new file called `ntp.h` with the following code: + + ```cpp + #pragma once + + #include "DateTime.h" + #include + #include "samd/NTPClientAz.h" + #include + + static void initTime() + { + WiFiUDP _udp; + time_t epochTime = (time_t)-1; + NTPClientAz ntpClient; + + ntpClient.begin(); + + while (true) + { + epochTime = ntpClient.getEpochTime("0.pool.ntp.org"); + + if (epochTime == (time_t)-1) + { + Serial.println("Fetching NTP epoch time failed! Waiting 2 seconds to retry."); + delay(2000); + } + else + { + Serial.print("Fetched NTP epoch time is: "); + + char buff[32]; + sprintf(buff, "%.f", difftime(epochTime, (time_t)0)); + Serial.println(buff); + break; + } + } + + ntpClient.end(); + + struct timeval tv; + tv.tv_sec = epochTime; + tv.tv_usec = 0; + + settimeofday(&tv, NULL); + } + ``` + + The details of this code are outside the scope of this lesson. It defines a function called `initTime` that gets the current time from an NTP server and uses it to set the clock on the Wio Terminal. + +1. Open the `main.cpp` file and remove all the MQTT code, including the `PubSubClient.h` header file, the declaration of the `PubSubClient` variable, the `reconnectMQTTClient` and `createMQTTClient` methods, and any calls to these variables and methods. This file should only contain code to connect to WiFi, get the soil moisture and create a JSON document with it in. + +1. Add the following `#include` directives to the top of the `main.cpp` file to include header files for the IoT Hub libraries, and for setting the time: + + ```cpp + #include + #include + #include + #include "ntp.h" + ``` + +1. Add the following call to the end of the `setup` function to set the current time: + + ```cpp + initTime(); + ``` + +1. Add the following variable declaration to the top of the file, just below the include directived: + + ```cpp + IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle; + ``` + + This declares an `IOTHUB_DEVICE_CLIENT_LL_HANDLE`, a handle to a connection to the IoT Hub. + +1. Below this, add the following code: + + ```cpp + static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *user_context) + { + if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED) + { + Serial.println("The device client is connected to iothub"); + } + else + { + Serial.println("The device client has been disconnected"); + } + } + ``` + + This declares a callback function that will be called when the connection to the IoT Hub changes status, such as connecting or disconnecting. The status is sent to the serial port. + +1. Below this, add a function to connect to IoT Hub: + + ```cpp + void connectIoTHub() + { + IoTHub_Init(); + + _device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol); + + if (_device_ll_handle == NULL) + { + Serial.println("Failure creating Iothub device. Hint: Check your connection string."); + return; + } + + IoTHubDeviceClient_LL_SetConnectionStatusCallback(_device_ll_handle, connectionStatusCallback, NULL); + } + ``` + + This code initializes the IoT Hub library code, then creates a connection using the connection string in the `config.h` header file. This connection is based on MQTT. If the connection fails, this is sent to the serial port - if you see this in the output, check the connection string. Finally the connection status callback is set up. + +1. Call this function in the `setup` function below the call to `initTime`: + + ```cpp + connectIoTHub(); + ``` + +1. Just like with the MQTT client, this code runs on a single thread so needs time to process messages being sent by the hub, and sent to the hub. Add the following to the top of the `loop` function to do this: + + ```cpp + IoTHubDeviceClient_LL_DoWork(_device_ll_handle); + ``` + +1. Build and upload this code. You will see the connection in the serial monitor: + + ```output + Connecting to WiFi.. + Connected! + Fetched NTP epoch time is: 1619983687 + Sending telemetry {"soil_moisture":391} + The device client is connected to iothub + ``` + + In the output you can see the NTP time being fetched, followed by the device client connecting. It can take a few seconds to connect, so you may see the soil moisture in the output whilst the device is connecting. + + > ๐Ÿ’ You can convert the UNIX time for the NTP to a more readable version using a web site like [unixtimestamp.com](https://www.unixtimestamp.com) + +## Send telemetry + +Now that your device is connected, you can send telemetry to the IoT Hub instead of the MQTT broker. + +### Task - send telemetry + +1. Add the following function above the `setup` function: + + ```cpp + void sendTelemetry(const char *telemetry) + { + IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(telemetry); + IoTHubDeviceClient_LL_SendEventAsync(_device_ll_handle, message_handle, NULL, NULL); + IoTHubMessage_Destroy(message_handle); + } + ``` + + This code creates an IoT Hub message from a string passed as a parameter, sends it to the hub, then cleans up the message object. + +1. Call this code in the `loop` function, just after the line where the telemetry is sent to the serial port: + + ```cpp + sendTelemetry(telemetry.c_str()); + ``` + +## Handle commands + +Your device needs to handle a command from the server code to control the relay. This is sent as a direct method request. + +## Task - handle a direct method request + +1. Add the following code before the `connectIoTHub` function: + + ```cpp + int directMethodCallback(const char *method_name, const unsigned char *payload, size_t size, unsigned char **response, size_t *response_size, void *userContextCallback) + { + Serial.printf("Direct method received %s\r\n", method_name); + + if (strcmp(method_name, "relay_on") == 0) + { + digitalWrite(PIN_WIRE_SCL, HIGH); + } + else if (strcmp(method_name, "relay_off") == 0) + { + digitalWrite(PIN_WIRE_SCL, LOW); + } + } + ``` + + This code defines a callback method that the IoT Hub library can call when it receives a direct method request. The method that is requested is sent in the `method_name` parameter. This function prints the method called to the serial port, then turns the relay on or off depending on the method name. + + > ๐Ÿ’ This could also be implemented in a single direct method request, passing the desired state of the relay in a payload that can be passed with the method request and available from the `payload` parameter. + +1. Add the following code to the end of the `directMethodCallback` function: + + ```cpp + char resultBuff[16]; + sprintf(resultBuff, "{\"Result\":\"\"}"); + *response_size = strlen(resultBuff); + *response = (unsigned char *)malloc(*response_size); + memcpy(*response, resultBuff, *response_size); + + return IOTHUB_CLIENT_OK; + ``` + + Direct method requests need a response, and the response is in two parts - a response as text, and a return code. This code will create a result as the following JSON document: + + ```JSON + { + "Result": "" + } + ``` + + This is then copied into the `response` parameter, and the size of this response is set in the `response_size` parameter. This code then returns `IOTHUB_CLIENT_OK` to show the method was handled correctly. + +1. Wire up the callback by adding the following to the end of the `connectIoTHub` function: + + ```cpp + IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL); + ``` + +1. The `loop` function will call the `IoTHubDeviceClient_LL_DoWork` function to process events send by IoT Hub. This is only called every 10 seconds due to the `delay`, meaning direct methods are only processed every 10 seconds. To make this more efficient, the 10 second delay can be implemented as many shorter delays, calling `IoTHubDeviceClient_LL_DoWork` each time. To do this, add the following code above the `loop` function: + + ```cpp + void work_delay(int delay_time) + { + int current = 0; + do + { + IoTHubDeviceClient_LL_DoWork(_device_ll_handle); + delay(100); + current += 100; + } while (current < delay_time); + } + ``` + + This code will loop repeatedly, calling `IoTHubDeviceClient_LL_DoWork` and delaying for 100ms each time. It will do this as many times as needed to delay for the amount of time given in the `delay_time` parameter. This means the device is waiting at most 100ms to process direct method requests. + +1. In the `loop` function, remove the call to `IoTHubDeviceClient_LL_DoWork`, and replace the `delay(10000)` call with the following to call this new function: + + ```cpp + work_delay(10000); + ``` + +> ๐Ÿ’ You can find this code in the [code/wio-terminal](code/wio-terminal) folder. + +๐Ÿ˜€ Your soil moisture sensor program is connected to your IoT Hub! diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/README.md b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md new file mode 100644 index 00000000..5120becd --- /dev/null +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md @@ -0,0 +1,589 @@ +# Migrate your application logic to the cloud + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/17) + +## Introduction + +In the last lesson, you learned how to connect your plant soil moisture monitoring and relay control to a cloud-based IoT service. The next step is to move the server code that controls the timing of the relay to the cloud. In this lesson you will learn how to do this using serverless functions. + +In this lesson we'll cover: + +* [What is serverless?](#what-is-serverless) +* [Create a serverless application](#create-a-serverless-application) +* [Create an IoT Hub event trigger](#create-an-iot-hub-event-trigger) +* [Send direct method requests from serverless code](#send-direct-method-requests-from-serverless-code) +* [Deploy your serverless code to the cloud](#deploy-your-serverless-code-to-the-cloud) + +## What is serverless? + +Serverless, or serverless computing, involves creating small blocks of code that are run in the cloud in response to different kinds of events. When the event happens your code is run, and it is passed data about the event. These events can be from many different things, including web requests, messages put on a queue, changes to data in a database, or messages sent to an IoT service by IoT devices. + +![Events being sent from an IoT service to a serverless service, all being processed at the same time by multiple functions being run](../../../images/iot-messages-to-serverless.png) + +***Events being sent from an IoT service to a serverless service, all being processed at the same time by multiple functions being run. IoT by Adrien Coquet from the [Noun Project](https://thenounproject.com)*** + +> ๐Ÿ’ If you've used database triggers before, you can think of this as the same thing, code being triggered by an event such as inserting a row. + +![When many events are sent at the same time, the serverless service scales up to run them all at the same time](../../../images/serverless-scaling.png) + +***When many events are sent at the same time, the serverless service scales up to run them all at the same time. IoT by Adrien Coquet from the [Noun Project](https://thenounproject.com)*** + +Your code is only run when the event happens, there is nothing keeping your code alive at other times. The event happens, your code is loaded and run. This makes serverless very scalable - if many events happen at the same time, the cloud provider can run your function as many times as you need at the same time across whatever servers they have available. The downside to this is if you need to share information between events, you need to save it somewhere like a database rather than storing it in memory. + +Your code is written as a function that takes details about the event as a parameter. You can use a wide range of programming languages to write these serverless functions. + +> ๐ŸŽ“ Serverless is also referred to as Functions as a service (FaaS) as each event trigger is implemented as a function in code. + +Despite the name, serverless does actually use servers. The naming is because you as a developer don't care about the servers needed to run your code, all you care about is that your code is run in response to an event. The cloud provider has a serverless *runtime* that manages allocating servers, networking, storage, CPU, memory and everything else needed to run your code. This model means you can't pay per server for the service, as there is no server. Instead you pay for the time your code is running, and the amount of memory used. + +> ๐Ÿ’ฐ Serverless is one of the cheapest ways to run code in the cloud. For example, at the time of writing, one cloud provider allows all of your serverless functions to execute a combined 1,000,000 times a month before they start charging you, and after that they charge US$0.20 for each 1,000,000 executions. When your code is not running, you don't pay. + +As an IoT developer, the serverless model is ideal. You can write a function that is called in response to messages sent from any IoT device that is connected to your cloud-hosted IoT service. Your code will handle all messages sent, but only be running when needed. + +โœ… Look back at the code you wrote as server code listening to messages over MQTT. As is, how might this run in the cloud using serverless? How do you think the code might be changed to support serverless computing? + +> ๐Ÿ’ The serverless model is moving to other cloud services in addition to running code. For example, serverless databases are available in the cloud using a serverless pricing model where you pay per request made against the database, such as a query or insert, usually using pricing based on how much work is done to service the request. For example a single select of one row against a primary key will cost less than a complicated operation joining many tables and returning thousands of rows. + +## Create a serverless application + +The serverless computing service from Microsoft is called Azure Functions. + +![The Azure Functions logo](../../../images/azure-functions-logo.png) + +The short video below has an overview of Azure Functions + +[![Azure Functions overview video](https://img.youtube.com/vi/8-jz5f_JyEQ/0.jpg)](https://www.youtube.com/watch?v=8-jz5f_JyEQ) + +โœ… Take a moment to do some research and read the overview of Azure Functions in the [Microsoft Azure Functions documentation](https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=academic-17441-jabenn). + +To write Azure Functions, you start with an Azure Functions app in the language of your choice. Out of the box Azure Functions supports Python, JavaScript, TypeScript, C#, F#, Java, and Powershell. In this lesson you will learn how to write an Azure Functions app in Python. + +> ๐Ÿ’ Azure Functions also supports custom handlers so you can write your functions in any language that supports HTTP requests, including older languages such as COBOL. + +Functions apps consist of one or more *triggers* - functions that respond to events. You can have multiple triggers inside one function app, all sharing common configuration. For example, in the configuration file for your Functions app you can have the connection details of your IoT Hub, and all the functions in the app can use this to connect and listen for events. + +### Task - install the Azure Functions tooling + +One great feature of Azure Functions is that you can run them locally. The same runtime that is used in the cloud can be run on your computer, allowing you to write code that responds to IoT messages and run it locally. You can even debug your code as events are handled. Once you are happy with your code, it can be deployed to the cloud. + +The Azure Functions tooling is available as a CLI, known as the Azure Functions Core Tools. + +1. Install the Azure Functions core tools by following the instructions on the [Azure Functions Core Tools documentation](https://docs.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=academic-17441-jabenn) + +1. Install the Azure Functions extension for VS Code. This extension provides support for creating, debugging and deploying Azure functions. Refer to the [Azure Functions extension documentation](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions&WT.mc_id=academic-17441-jabenn) for instructions on installing this extension in VS Code. + +When you deploy your Azure Functions app to the cloud, it needs to use a small amount of cloud storage to store things like the application files and log files. When you run your Functions app locally, you still need to connect to cloud storage, but instead of using actual cloud storage, you can use a storage emulator called [Azurite](https://github.com/Azure/Azurite). This runs locally but acts like cloud storage. + +> ๐ŸŽ“ In Azure, the storage that Azure Functions uses is an Azure Storage Account. These accounts can store files, blobs, data in tables or data in queues. You can share one storage account between many apps, such as a Functions app and a web app. + +1. Azurite is a Node.js app, so you will need to install Node.js. You can find the download and installation instructions on the [Node.js website](https://nodejs.org/). If you are using a Mac, you can also install it from [Homebrew](https://formulae.brew.sh/formula/node). + +1. Install Azurite using the following command (`npm` is a tool that is installed when you install Node.js): + + ```sh + npm install -g azurite + ``` + +1. Create a folder called `azurite` for Azurite to use to store data: + + ```sh + mkdir azurite + ``` + +1. Run Azurite, passing it this new folder: + + ```sh + azurite --location azurite + ``` + + The Azurite storage emulator will launch and be ready for the local Functions runtime to connect. + + ```output + โžœ ~ azurite --location azurite + Azurite Blob service is starting at http://127.0.0.1:10000 + Azurite Blob service is successfully listening at http://127.0.0.1:10000 + Azurite Queue service is starting at http://127.0.0.1:10001 + Azurite Queue service is successfully listening at http://127.0.0.1:10001 + Azurite Table service is starting at http://127.0.0.1:10002 + Azurite Table service is successfully listening at http://127.0.0.1:10002 + ``` + +### Task - create an Azure Functions project + +The Azure Functions CLI can be used to create a new Functions app. + +1. Create a folder for your Functions app and navigate to it. Call it `soil-moisture-trigger` + + ```sh + mkdir soil-moisture-trigger + cd soil-moisture-trigger + ``` + +1. Create a Python virtual environment inside this folder: + + ```sh + python3 -m venv .venv + ``` + +1. Activate the virtual environment: + + * On Windows run: + + ```cmd + .venv\Scripts\activate.bat + ``` + + * On macOS or Linux, run: + + ```cmd + source ./.venv/bin/activate + ``` + +1. Run the following command to create a Functions app in this folder: + + ```sh + func init --worker-runtime python soil-moisture-trigger + ``` + + This will create three files inside the current folder: + + * `host.json` - this JSON document contains settings for your Functions app. You won't need to modify these settings. + * `local.settings.json` - this JSON document contains settings your app would use when running locally, such as connection strings for your IoT Hub. These settings are local only, and should not be added to source code control. When you deploy the app to the cloud, these settings are not deployed, instead your settings are loaded from application settings. This will be covered later in this lesson. + * `requirements.txt` - this is a [Pip requirements file](https://pip.pypa.io/en/stable/user_guide/#requirements-files) that contains the Pip packages needed to run your Functions app. + +1. The `local.settings.json` file has a setting for the storage account that the Functions app will use. This defaults to an empty setting, so needs to be set. To connect to the Azurite local storage emulator, set this value to the following: + + ```json + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + ``` + +1. Install the necessary Pip packages using the requirements file: + + ```sh + pip install -r requirements.txt + ``` + + > ๐Ÿ’ The required Pip packages need to be in this file, so that when the Functions app is deployed to the cloud, the runtime can ensure it installs the correct packages. + +1. To test everything is working correctly, you can start the Functions runtime. Run the following command to do this: + + ```sh + func start + ``` + + You will see the runtime start up and report that it hasn't found any job functions (triggers). + + ```output + (.venv) โžœ soil-moisture-trigger func start + Found Python version 3.9.1 (python3). + + Azure Functions Core Tools + Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit) + Function Runtime Version: 3.0.15417.0 + + [2021-05-05T01:24:46.795Z] No job functions found. + ``` + + > โš ๏ธ If you get a firewall notification, grant access as the `func` application needs to be able to read and write to your network. + +1. Stop the Functions app by pressing `ctrl+c`. + +1. Open the current folder in VS Code, either by opening VS Code, then opening this folder, or by running the following: + + ```sh + code . + ``` + + VS Code will detect your Functions project and show a notification saying: + + ```output + Detected an Azure Functions Project in folder "soil-moisture-trigger" that may have been created outside of + VS Code. Initialize for optimal use with VS Code? + ``` + + ![The notification](../../../images/vscode-azure-functions-init-notification.png) + + Select **Yes** from this notification. + +1. Make sure the Python virtual environment is running in the VS Code terminal. Terminate it and restart it if necessary. + +## Create an IoT Hub event trigger + +The Functions app is the shell of your serverless code. To respond to IoT hub events, you can add an IoT Hub trigger to this app. This trigger needs to connect to the stream of messages that are sent to the IoT Hub and respond to them. To get this stream of messages, your trigger needs to connect to the IoT Hubs *event hub compatible endpoint*. + +IoT Hub is based upon another Azure service called Azure Event Hubs. Event Hubs is a service that allows you to send and receive messages, IoT Hub extends this to add features for IoT devices. The way you connect to read messages off the IoT Hub is the same as you would if you were using Event Hubs. + +โœ… Do some research: Read the overview of Event Hubs in the [Azure Event Hubs documentation](https://docs.microsoft.com/azure/event-hubs/event-hubs-about?WT.mc_id=academic-17441-jabenn). How do the basic features compare to IoT Hub? + +For an IoT device to connect to the IoT Hub, it has to use a secret key that ensures only allowed devices can connect. The same applies when connecting to read off messages, your code will need a connection string that contains a secret key, along with details of the IoT Hub. + +> ๐Ÿ’ The default connection string you get has **iothubowner** permissions, which gives any code that uses it full permissions on the IoT Hub. Ideally you should connect with the lowest level of permissions needed. This will be covered in the next lesson. + +Once your trigger has connected, the code inside the function will be called for every message sent to the IoT Hub, regardless of which device sent it. The trigger will be passed the message as a parameter. + +### Task - get the Event Hub compatible endpoint connection string + +1. From the VS Code terminal run the following command to get the connection string for the IoT Hubs Event Hub compatible endpoint: + + ```sh + az iot hub connection-string show --default-eventhub \ + --output table \ + --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + +1. In VS Code, open the `local.settings.json` file. Add the following additional value inside the `Values` section: + + ```json + "IOT_HUB_CONNECTION_STRING": "" + ``` + + Replace `` with the value from the previous step. You will need to add a comma after the line above to make this valid JSON. + +### Task - create an event trigger + +You are now ready to create the event trigger. + +1. From the VS Code terminal run the following command from inside the `soil-moisture-trigger` folder: + + ```sh + func new --name iot_hub_trigger --template "Azure Event Hub trigger" + ``` + + This creates a new Function called `iot_hub_trigger`. The trigger will connect to the Event Hub compatible endpoint on the IoT Hub, so you can use an event hub trigger. There is no specific IoT Hub trigger. + +This will create a folder inside the `soil-moisture-trigger` folder called `iot_hub_trigger` that contains this function. This folder will have the following files inside it: + +* `__init__.py` - this is the Python code file that contains the trigger, using the standard Python file name convention to turn this folder into a Python module. + + This file will contain the following code: + + ```python + from typing import List + import logging + import azure.functions as func + + def main(events: List[func.EventHubEvent]): + for event in events: + logging.info('Python EventHub trigger processed an event: %s', + event.get_body().decode('utf-8')) + ``` + + The core of the trigger is the `main` function. It is this function that is called with the events from the IoT Hub. This function has a parameter called `events` that contains a list of `EventHubEvent`. Each event in this list is a message sent to IoT Hub, along with properties that are the same as the annotations you saw in the last lesson. + + This trigger processes a list of events, rather than individual events. When you first run the trigger it wil process any unprocessed events on the IoT Hub (remember that messages are stored for a while so they are not lost if your application code is offline). After this it will generally process a list containing only one event, unless a lot of events are sent to the Hub in a short space of time. + + The core of this function loops through the list and logs the events. + +* `function.json` - this contains configuration for the trigger. The main configuration is in a section called `bindings`. A binding is the term for a connection between Azure Functions and other Azure services. This function has an input binding to an event hub - it connects to an event hub and receives data. + + > ๐Ÿ’ You can also have output bindings so that the output of a function is sent to another service. For example you could add an output binding to a database and return the IoT Hub event from the function, and it will automatically be inserted into the database. + + โœ… Do some research: Read up on bindings in the [Azure Functions triggers and bindings concepts documentation](https://docs.microsoft.com/azure/azure-functions/functions-triggers-bindings?tabs=python&WT.mc_id=academic-17441-jabenn). + + The `bindings` section includes configuration for the binding. The values of interest are: + + * `"type": "eventHubTrigger"` - this tells the function it needs to listen to events from an Event Hub + * `"name": "events"` - this is the parameter name to use for the Event Hub events. This matches the parameter name in the `main` function in the Python code. + * `"direction": "in",` - this is an input binding, the data from the event hub comes into the function + * `"connection": ""` - this defines the name of the setting to read the connection string from. When running locally, this will read this setting from the `local.settings.json` file. + + > ๐Ÿ’ The connection string cannot be stored in the `function.json` file, it has to be read from the settings. This is to stop you accidentally exposing your connection string. + +1. Update the value of `"connection"` in the `function.json` file to point to the new value you added to the `local.settings.json` file: + + ```json + "connection": "IOT_HUB_CONNECTION_STRING", + ``` + + > ๐Ÿ’ Remember - this needs to point to the setting, not contain the actual connection string. + +### Task - run the event trigger + +1. To run the Functions app, run the following command from the VS Code terminal + + ```sh + func start + ``` + + The Functions app will start up, and will discover the `iot_hub_trigger` function. It will then process any events that have already been sent to the IoT Hub in the past day. + + ```output + (.venv) โžœ soil-moisture-trigger func start + Found Python version 3.9.1 (python3). + + Azure Functions Core Tools + Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit) + Function Runtime Version: 3.0.15417.0 + + Functions: + + iot_hub_trigger: eventHubTrigger + + For detailed output, run func with --verbose flag. + [2021-05-05T02:44:07.517Z] Worker process started and initialized. + [2021-05-05T02:44:09.202Z] Executing 'Functions.iot_hub_trigger' (Reason='(null)', Id=802803a5-eae9-4401-a1f4-176631456ce4) + [2021-05-05T02:44:09.205Z] Trigger Details: PartionId: 0, Offset: 1011240-1011632, EnqueueTimeUtc: 2021-05-04T19:04:04.2030000Z-2021-05-04T19:04:04.3900000Z, SequenceNumber: 2546-2547, Count: 2 + [2021-05-05T02:44:09.352Z] Python EventHub trigger processed an event: {"soil_moisture":628} + [2021-05-05T02:44:09.354Z] Python EventHub trigger processed an event: {"soil_moisture":624} + [2021-05-05T02:44:09.395Z] Executed 'Functions.iot_hub_trigger' (Succeeded, Id=802803a5-eae9-4401-a1f4-176631456ce4, Duration=245ms) + ``` + + Each call to the function will be surrounded by a `Executing 'Functions.iot_hub_trigger'`/`Executed 'Functions.iot_hub_trigger'` block in the output, so you can how many messages were processed in each function call. + + > If you get the following error: + + ```output + The listener for function 'Functions.iot_hub_trigger' was unable to start. Microsoft.WindowsAzure.Storage: Connection refused. System.Net.Http: Connection refused. System.Private.CoreLib: Connection refused. + ``` + + Then check Azurite is running and you have set the `AzureWebJobsStorage` in the `local.settings.json` file to `UseDevelopmentStorage=true`. + +1. Make sure your IoT device is running, You will see new soil moisture messages appearing in the Functions app. + +1. Stop and restart the Functions app. You will see that it won't process messages previous messages again, it will only process new messages. + +> ๐Ÿ’ VS Code also supports debugging your Functions. You can set break points by clicking on the border by the start of each line of code, or putting the cursor on a line of code and selecting *Run -> Toggle breakpoint*, or pressing `F9`. You can launch the debugger by selecting *Run -> Start debugging*, pressing `F5`, or selecting the *Run and debug* pane and selecting the **Start debugging** button. By doing this you can see the details of the events being processed. + +## Send direct method requests from serverless code + +So far your Functions app is listening to messages from the IoT Hub using the Event Hub compatible end point. You now need to send commands to the IoT device. This is done by using a different connection to the IoT Hub via the *Registry Manager*. The Registry Manager is a tool that allows you to see what devices are registered with the IoT Hub, and communicate with those devices by sending cloud to device messages, direct method requests or updating the device twin. You can also use it to register, update or delete IoT devices from the IoT Hub. + +To connect to the Registry Manager, you need a connection string. + +### Task - get the Registry Manager connection string + +1. To get the connection string, run the following command: + + ```sh + az iot hub connection-string show --policy-name service \ + --output table \ + --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + + The connection string is requested for the *ServiceConnect* policy using the `--policy-name service` parameter. When you request a connection string, you can specify what permissions that connection string will allow. The ServiceConnect policy allows yor code to connect and send messages to IoT devices. + + โœ… Do some research: Read up on the different policies in the [IoT Hub permissions documentation](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-security#iot-hub-permissions?WT.mc_id=academic-17441-jabenn) + +1. In VS Code, open the `local.settings.json` file. Add the following additional value inside the `Values` section: + + ```json + "REGISTRY_MANAGER_CONNECTION_STRING": "" + ``` + + Replace `` with the value from the previous step. You will need to add a comma after the line above to make this valid JSON. + +### Task - send a direct method request to a device + +1. The SDK for the Registry Manager is available via a Pip package. Add the following line to the `requirements.txt` file to add the dependency on this package: + + ```sh + azure-iot-hub + ``` + +1. Make sure the VS Code terminal has the virtual environment activated, and run the following command to install the Pip packages: + + ```sh + pip install -r requirements.txt + ``` + +1. Add the following imports to the `__init__.py` file: + + ```python + import json + import os + from azure.iot.hub import IoTHubRegistryManager + from azure.iot.hub.models import CloudToDeviceMethod + ``` + + This imports some system libraries, as well as the libraries to interact with the Registry Manager and send direct method requests. + +1. Remove the code from inside the `main` method, but keep the method itself. + +1. When multiple messages are received, it only makes sense to process the last one as this is the current soil moisture. It makes no sense to process messages from before. Add the following code to get the last message from the `events` parameter: + + ```python + event = events[-1] + ``` + +1. Below this, add the following code: + + ```python + body = json.loads(event.get_body().decode('utf-8')) + device_id = event.iothub_metadata['connection-device-id'] + + logging.info(f'Received message: {body} from {device_id}') + ``` + + This code extracts the body of the event which contains the JSON message sent by the IoT device. + + It then gets the device ID from the annotations passed with the message. The body of the event contains the message sent as telemetry, the `iothub_metadata` dictionary contains properties set by the IoT Hub such as the device ID of the sender, and the time that the message was sent. + + This information is then logged. You will see this logging in the terminal when you run the Function app locally. + +1. Below this, add the following code: + + ```python + soil_moisture = body['soil_moisture'] + + if soil_moisture > 450: + direct_method = CloudToDeviceMethod(method_name='relay_on', payload='{}') + else: + direct_method = CloudToDeviceMethod(method_name='relay_off', payload='{}') + ``` + + This code gets the soil moisture from the message. It then checks the soil moisture, and depending on the value, creates a helper class for the direct method request for the `relay_on` or `relay_off` direct method. The method request doesn't need a payload, so an empty JSON document is sent. + +1. Below this add the following code: + + ```python + logging.info(f'Sending direct method request for {direct_method.method_name} for device {device_id}') + + registry_manager_connection_string = os.environ['REGISTRY_MANAGER_CONNECTION_STRING'] + registry_manager = IoTHubRegistryManager(registry_manager_connection_string) + ``` + + This code loads the `REGISTRY_MANAGER_CONNECTION_STRING` from the `local.settings.json` file. The values in this file are made available as environment variables, and these can be read using the `os.environ` function, a function that returns a dictionary of all the environment variables. + + > ๐Ÿ’ When this code is deployed to the cloud, the values in the `local.settings.json` file will be set as *Application Settings*, and these can be read from environment variables. + + The code then creates an instance of the Registry Manager helper class using the connection string. + +1. Below this add the following code: + + ```python + registry_manager.invoke_device_method(device_id, direct_method) + + logging.info('Direct method request sent!') + ``` + + This code tells the registry manager to send the direct method request to the device that sent the telemetry. + + > ๐Ÿ’ In the versions of the app you created in earlier lessons using MQTT, the relay control commands were sent to all devices. The code assumed you would only have one device. This version of the code sends the method request to a single device, so would work if you had multiple setups of moisture sensors and relays, sending the right direct method request to the right device. + +1. Run the Functions app, and make sure your IoT device is sending data. You will see the messages being processed and the direct method requests being sent. Move the soil moisture sensor in and out of the soil to see the values change and the relay turn on and off + +> ๐Ÿ’ You can find this code in the [code/functions](code/functions) folder. + +## Deploy your serverless code to the cloud + +Your code is now working locally, so the next step is to deploy the Functions App to the cloud. + +### Task - create the cloud resources + +Your Functions app needs to be deployed to a Functions App resource in Azure, living inside the Resource Group you created for your IoT Hub. You will also need a Storage Account created in Azure to replace the emulated one you have running locally. + +1. Run the following command to create a storage account: + + ```sh + az storage account create --resource-group soil-moisture-sensor \ + --sku Standard_LRS \ + --name + ``` + + Replace `` with a name for your storage account. This will need to be globally unique as it forms part of the URL used to access the storage account. You can only use lower case letters and numbers for this name, no other characters, and it's limited to 24 characters. Use something like `sms` and add a unique identifier on the end, like some random words or your name. + + The `--sku Standard_LRS` selects the pricing tier, selecting the lowest cost general-purpose account. There isn't a free tier of storage, and you pay for what you use. The costs are relatively low, with the most expensive storage at less than US$0.05 per month per gigabyte stored. + + โœ… Read up on pricing on the [Azure Storage Account pricing page](https://azure.microsoft.com/pricing/details/storage/?WT.mc_id=academic-17441-jabenn) + +1. Run the following command to create a Function App: + + ```sh + az functionapp create --resource-group soil-moisture-sensor \ + --runtime python \ + --functions-version 3 \ + --os-type Linux \ + --consumption-plan-location \ + --storage-account \ + --name + ``` + + Replace `` with the location you used when creating the Resource Group in the previous lesson. + + Replace `` with the name of the storage account you created in the previous step. + + Replace `` with a unique name for your Functions App. This will need to be globally unique as it forms part of a URL that can be used to access the Functions App. Use something like `soil-moisture-sensor-` and add a unique identifier on the end, like some random words or your name. + + The `--functions-version 3` option sets the version of Azure Functions to use. Version 3 is the latest version. + + The `--os-type Linux` tells the Functions runtime to use Linux as the OS to host these functions. Functions can be hosted on Linux or Windows, depending on the programming language used. Python apps are only supported on Linux. + +### Task - upload your application settings + +When you developed your Functions App, you stored some settings in the `local.settings.json` file for the connection strings for your IoT Hub. These need to be written to Application Settings in your Function App in Azure so that they can be used by your code. + +> ๐ŸŽ“ The `local.settings.json` file is for local development settings only, and these should not be checked into source code control, such as GitHub. When deployed to the cloud, Application Settings are used. Application Settings are key/value pairs hosted in the cloud and are read from environment variables either in your code or by the runtime when connecting your code to IoT Hub. + +1. Run the following command to set the `IOT_HUB_CONNECTION_STRING` setting in the Functions App Application Settings: + + ```sh + az functionapp config appsettings set --resource-group soil-moisture-sensor \ + --name \ + --settings "IOT_HUB_CONNECTION_STRING=" + ``` + + Replace `` with the name you used for your Functions App. + + Replace `` with the value of `IOT_HUB_CONNECTION_STRING` from your `local.settings.json` file. + +1. Repeat the step above, but set the value of `REGISTRY_MANAGER_CONNECTION_STRING` to the corresponding value from your `local.settings.json` file. + +When you run these commands, they will also output a list of all the Application Settings for the function app. You can use this to check that your values are set correctly. + +> ๐Ÿ’ You will see a value already set for `AzureWebJobsStorage`. In your `local.settings.json` file, this was set to a value to use the local storage emulator. When you created the Functions App, you pass the storage account as a parameter, and this gets set automatically in this setting. + +### Task - deploy your Functions App to the cloud + +Now that the Functions App is ready, your code can be deployed. + +1. Run the following command from the VS Code terminal to publish your Functions App: + + ```sh + func azure functionapp publish + ``` + + Replace `` with the name you used for your Functions App. + +The code will be packaged up and sent to the Functions App, where it will be deployed and started. There will be a lot of console output, ending in confirmation of the deployment and a list of the functions deployed. In this case the list will only contain the trigger. + +```output +Deployment successful. +Remote build succeeded! +Syncing triggers... +Functions in soil-moisture-sensor: + iot_hub_trigger - [eventHubTrigger] +``` + +Make sure your IoT device is running. Change the moisture levels by adjusting the soil moisture, or moving the sensor in and out of the soil. You will see the relay turn on and off as the soil moisture changes. + +--- + +## ๐Ÿš€ Challenge + +In the previous lesson, you managed timing for the relay by unsubscribing from MQTT messages whilst the relay was on, and for a short while after it was turned off. You can't use this method here - you cannot unsubscribe your IoT Hub trigger. + +Think about different ways you could handle this in your Functions App. + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/18) + +## Review & Self Study + +* Read up on serverless computing on the [Serverless Computing page on Wikipedia](https://wikipedia.org/wiki/Serverless_computing) +* Read about using serverless in Azure including some more examples on the [Go serverless for your IoT needs Azure blog post](https://azure.microsoft.com/blog/go-serverless-for-your-iot-needs/?WT.mc_id=academic-17441-jabenn) +* Learn more about Azure Functions on the [Azure Functions YouTube channel](https://www.youtube.com/c/AzureFunctions) + +## Assignment + +[Add manual relay control](assignment.md) diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md b/2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md new file mode 100644 index 00000000..4980d6fc --- /dev/null +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md @@ -0,0 +1,56 @@ +# Add manual relay control + +## Instructions + +Serverless code can be triggered by many different things, including HTTP requests. You can use HTTP triggers to add a manual override to your relay control, allowing someone to turn the relay on or off from a web request. + +For this assignment, you need to add two HTTP triggers to your Functions App to turn the relay on and off, reusing what you have learned from this lesson to send commands to the device. + +Some hints: + +* You can add an HTTP trigger to your existing Functions App with the following command: + + ```sh + func new --name --template "HTTP trigger" + ``` + + Replace `` with the name for your HTTP trigger. Use something like `relay_on` and `relay_off` + +* HTTP triggers can have access control. By default they require a function-specific API key to be passed with the URL to run. For this assignment, you can remove this restriction so anyone can run the function. To do this, update the `authLevel` setting in the `function.json` file for the HTTP triggers to the following: + + ```json + "authLevel": "anonymous" + ``` + + > ๐Ÿ’ You can read more about this access control in the [Function access keys documentation](https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?WT.mc_id=academic-17441-jabenn#authorization-keys). + +* HTTP triggers by default support GET and POST requests. This means you can call them using your web browser - web browsers make GET requests. + + When you run your Functions App locally, you will see the URL of the trigger: + + ```output + Functions: + + relay_off: [GET,POST] http://localhost:7071/api/relay_off + + relay_on: [GET,POST] http://localhost:7071/api/relay_on + + iot_hub_trigger: eventHubTrigger + ``` + + Paste the URL into your browser and hit `return`, or `Ctrl+click` (`Cmd+click` on macOS) the link in the terminal window in VS Code to open it in your default browser. This will run the trigger. + + > ๐Ÿ’ Notice that the URL has `/api` in it - HTTP triggers are by default in the `api` subdomain. + +* When you deploy the Functions App, the HTTP trigger URL will be: + + `https://.azurewebsites.net/api/` + + Where `` is the name of your Functions App, and `` is the name of your trigger. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Create HTTP triggers | Created 2 triggers to turn the relay on and off, with appropriate names | Created one trigger with an appropriate name | Was unable to create any triggers | +| Control the relay from the HTTP triggers | Was able to connect both triggers to IoT Hub and control the relay appropriately | Was able to connect one trigger to IoT Hub and control the relay appropriately | Was unable connect the triggers to IoT Hub | diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/host.json b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/host.json new file mode 100644 index 00000000..291065f8 --- /dev/null +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/host.json @@ -0,0 +1,15 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[2.*, 3.0.0)" + } +} \ No newline at end of file diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot_hub_trigger/__init__.py b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot_hub_trigger/__init__.py new file mode 100644 index 00000000..8d1f5865 --- /dev/null +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot_hub_trigger/__init__.py @@ -0,0 +1,33 @@ +from typing import List +import logging + +import azure.functions as func + +import json +import os +from azure.iot.hub import IoTHubRegistryManager +from azure.iot.hub.models import CloudToDeviceMethod + +def main(events: List[func.EventHubEvent]): + event = events[-1] + + body = json.loads(event.get_body().decode('utf-8')) + device_id = event.iothub_metadata['connection-device-id'] + + logging.info(f'Received message: {body} from {device_id}') + + soil_moisture = body['soil_moisture'] + + if soil_moisture > 450: + direct_method = CloudToDeviceMethod(method_name='relay_on', payload='{}') + else: + direct_method = CloudToDeviceMethod(method_name='relay_off', payload='{}') + + logging.info(f'Sending direct method request for {direct_method.method_name} for device {device_id}') + + registry_manager_connection_string = os.environ['REGISTRY_MANAGER_CONNECTION_STRING'] + registry_manager = IoTHubRegistryManager(registry_manager_connection_string) + + registry_manager.invoke_device_method(device_id, direct_method) + + logging.info('Direct method request sent!') diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot_hub_trigger/function.json b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot_hub_trigger/function.json new file mode 100644 index 00000000..0117bdf5 --- /dev/null +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot_hub_trigger/function.json @@ -0,0 +1,15 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "type": "eventHubTrigger", + "name": "events", + "direction": "in", + "eventHubName": "samples-workitems", + "connection": "IOT_HUB_CONNECTION_STRING", + "cardinality": "many", + "consumerGroup": "$Default", + "dataType": "binary" + } + ] +} \ No newline at end of file diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/local.settings.json b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/local.settings.json new file mode 100644 index 00000000..f72a4875 --- /dev/null +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/local.settings.json @@ -0,0 +1,9 @@ +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "python", + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "IOT_HUB_CONNECTION_STRING": "", + "REGISTRY_MANAGER_CONNECTION_STRING": "" + } +} \ No newline at end of file diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/requirements.txt b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/requirements.txt new file mode 100644 index 00000000..cd046912 --- /dev/null +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/requirements.txt @@ -0,0 +1,4 @@ +# Do not include azure-functions-worker as it may conflict with the Azure Functions platform + +azure-functions +azure-iot-hub \ No newline at end of file diff --git a/2-farm/lessons/6-keep-your-plant-secure/README.md b/2-farm/lessons/6-keep-your-plant-secure/README.md new file mode 100644 index 00000000..fe4b6616 --- /dev/null +++ b/2-farm/lessons/6-keep-your-plant-secure/README.md @@ -0,0 +1,237 @@ +# Keep your plant secure + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/19) + +## Introduction + +In the last few lessons you've created a soil monitoring IoT device and connected it to the cloud. But what if hackers working for a rival farmer managed to seize control of your IoT devices? What if they sent high soil moisture readings so your plants never got watered, or turned on your watering system to run all the time killing your plants from over-watering and costing you a small fortune in water? + +In this lesson you will learn about securing IoT devices. As this is the last lesson for this project, you will also learn how to clean up your cloud resources, reducing any potential costs. + +In this lesson we'll cover: + +* [Why do you need to secure IoT devices?](#why-do-you-need-to-secure-iot-devices) +* [Cryptography](#cryptography) +* [Secure your IoT devices](#secure-your-iot-devices) +* [Generate and use an X.509 certificate](#generate-and-use-an-x.509-certificates) + +> ๐Ÿ—‘ This is the last lesson in this project, so after completing this lesson and the assignment, don't forget to clean up your cloud services. You will need the services to complete the assignment, so make sure to complete that first. +> +> Refer to [the clean up your project guide](../../../clean-up.md) if necessary for instructions on how to do this. + +## Why do you need to secure IoT devices? + +IoT security involves ensuring that only expected devices can connect to your cloud IoT service and send them telemetry, and only your cloud service can send commands to your devices. IoT data can also be personal, including medical or intimate data, so your entire application needs to consider security to stop this data being leaked. + +If your IoT application is not secure, there are a number of risks: + +* A fake device could send incorrect data, causing your application to respond incorrectly. For example, they could send constant high soil moisture readings, meaning your irrigation system never turns on and your plants die from lack of water +* Unauthorized users could read data from IoT devices including personal or business critical data +* Hackers could send commands to control a device in a way that could cause damage to the device or connected hardware +* By connecting to an IoT device, hackers can use this to access additional networks to get access to private systems +* Malicious users could access personal data and use this for blackmail + +These are real world scenarios, and happen all the time. Some examples were given in earlier lessons, but here are some more: + +* In 2018 hackers used an open WiFi access point on a fish tank thermostat to gain access to a casino's network to steal data. [The Hacker News - Casino Gets Hacked Through Its Internet-Connected Fish Tank Thermometer](https://thehackernews.com/2018/04/iot-hacking-thermometer.html) +* In 2016, the Mirai Botnet launched a denial of service attack against Dyn, an Internet service provider, taking down large portions of the Internet. This botnet used malware to connect to IoT devices such as DVRs and cameras that used default usernames and passwords, and from there launched the attack. [The Guardian - DDoS attack that disrupted internet was largest of its kind in history, experts say](https://www.theguardian.com/technology/2016/oct/26/ddos-attack-dyn-mirai-botnet) +* Spiral Toys had a database of users of their CloudPets connected toys publicly available over the Internet. [Troy Hunt - Data from connected CloudPets teddy bears leaked and ransomed, exposing kids' voice messages](https://www.troyhunt.com/data-from-connected-cloudpets-teddy-bears-leaked-and-ransomed-exposing-kids-voice-messages/). +* Strava tagged runners that you ran past and showed their routes, allowing strangers to effectively see where you live. [Kim Komndo - Fitness app could lead a stranger right to your home โ€” change this setting](https://www.komando.com/security-privacy/strava-fitness-app-privacy/755349/). + +โœ… Do some research: Search for more examples IoT Hacks and breaches of IoT data, especially with personal items such as Internet connected toothbrushes or scales. Think about the impact these hacks could have on the victims or customers. + +> ๐Ÿ’ Security is a massive topic, and this lesson will only touch on some of the basics around connecting your device to the cloud. Other topics that won't be covered include monitoring for data changes in transit, hacking devices directly, or changes to device configurations. IoT hacking is such a threat, tools like [Azure Defender for IoT](https://azure.microsoft.com/services/azure-defender-for-iot/?WT.mc_id=academic-17441-jabenn) have been developed. These tools are similar to the anti-virus and security tools you might have on your computer, just designed for small, low powered IoT devices. + +## Cryptography + +When a device connects to an IoT service, it uses an ID to identify itself. The problem is this ID can be cloned - a hacker could set up a malicious device that uses the same ID as a real device but sends bogus data. + +![Both valid and malicious devices could use the same ID to send telemetry](../../../images/iot-device-and-hacked-device-connecting.png) + +***Both valid and malicious devices could use the same ID to send telemetry. Microcontroller by Template / IoT by Adrien Coquet - all from the [Noun Project](https://thenounproject.com)*** + +The way round this is to convert the data being sent into a scrambled format, using some kind of value to scramble the data known to the device and the cloud only. This process is called *encryption*, and the value used to encrypt the data is called an *encryption key*. + +![If encryption is used, then only encrypted messages will be accepted, others will be rejected](../../../images/iot-device-and-hacked-device-connecting-encryption.png) + +***If encryption is used, then only encrypted messages will be accepted, others will be rejected. Microcontroller by Template / IoT by Adrien Coquet - all from the [Noun Project](https://thenounproject.com)*** + +The cloud service can then convert the data back to a readable format, using a process called *decryption*, using either the same encryption key, or a *decryption key*. If the encrypted message cannot be decrypted by the key, the device has been hacked and the message is rejected. + +The technique for doing encryption and decryption is called *cryptography*. + +### Early cryptography + +The earliest types of cryptography were substitution ciphers, dating back 3,500 years. Substitution ciphers involve substituting one letter for another. For example, the [Caesar cipher](https://wikipedia.org/wiki/Caesar_cipher) involves shifting the alphabet by a defined amount, with only the sender of the encrypted message, and the intended recipient knowing how many letters to shift. + +The [Vigenรจre cipher](https://wikipedia.org/wiki/Vigenรจre_cipher) took this further by using words to encrypt text, so that each letter in the original text was shifted by a different amount, rather than always shifting by the same number of letters. + +Cryptography was used for a wide range of purposes, such as protecting a potters glaze recipe in ancient Mesopotamia, writing secret love notes in India, or keeping ancient Egyptian magical spells secret. + +### Modern cryptography + +Modern cryptography is much more advanced, making it harder to crack than early methods. Modern cryptography uses complicated mathematics to encrypt data with far too many possible keys to make brute force attacks possible. + +Cryptography is used in a lot of different ways for secure communications. If you are reading this page on GitHub, you may notice the web site address starts with *HTTPS*, meaning that the communication between your browser and the web servers of GitHub is encrypted. If someone was able to read the internet traffic flowing between your browser and GitHub, they wouldn't be able to read the data as it is encrypted. Your computer might even encrypt all the data on your hard drive so if someone steals it, they won't be able to read any of your data without your password. + +> ๐ŸŽ“ HTTPS stands for HyperText Transfer Protocol **Secure** + +Unfortunately, not everything is secure. Some devices have no security, others are secured using easy to crack keys, or sometimes even all the devices of the same type using the same key. There have been accounts of very personal IoT devices that all have the same password to connect to them over WiFi or Bluetooth. If you can connect to your own device, you can connect to someone else's. Once connected you could access some very private data, or have control over their device. + +> ๐Ÿ’ Despite the complexities of modern cryptography and the claims that breaking encryption can take billions of years, the rise of quantum computing has led to the possibility of breaking all know encryption in a very short space of time! + +### Symmetric and asymmetric keys + +Encryption comes in two types - symmetric and asymmetric. + +**Symmetric** encryption uses the same key to encrypt and decrypt the data. Both the sender and receive need to know the same key. This is the least secure type, as the key needs to be shared somehow. For a sender to send an encrypted message to a recipient, the sender first might have to send the recipient the key. + +![Symmetric key encryption uses the same key to encrypt and decrypt a message](../../../images/send-message-symmetric-key.png) + +If the key gets stolen in transit, or the sender or recipient get hacked and the key is found, the encryption can be cracked. + +![Symmetric key encryption is only secure if a hacker doesn't get the key - if so they can intercept and decrypt the message](../../../images/send-message-symmetric-key-hacker.png) + +**Asymmetric** encryption uses 2 keys - an encryption key and a decryption key, referred to as a public/private key pair. The public key is used to encrypt the message, but cannot be used to decrypt it, the private key is used to decrypt the message but cannot be used to encrypt it. + +![Asymmetric encryption uses a different key to encrypt and decrypt. The encryption key is sent to any message senders so they can encrypt a message before sending it to the recipient who owns the keys](../../../images/send-message-asymmetric.png) + +The recipient shares their public key, and the sender uses this to encrypt the message. Once the message is sent, the recipient decrypts it with their private key. Asymmetric encryption is more secure as the private key is kept private by the recipient and never shared. Anyone can have the public key as it can only be used to encrypt messages. + +Symmetric encryption is faster than asymmetric encryption, asymmetric is more secure. Some systems will use both - using asymmetric encryption to encrypt and share the symmetric key, then using the symmetric key to encrypt all data. This makes it more secure to share the symmetric key between sender and recipient, and faster when encrypting and decrypting data. + +## Secure your IoT devices + +IoT devices can be secured using symmetric or asymmetric encryption. Symmetric is easier, but less secure. + +### Symmetric keys + +When you set up your IoT device to interact with IoT Hub, you used a connection string. An example connection string is: + +```output +HostName=soil-moisture-sensor.azure-devices.net;DeviceId=soil-moisture-sensor;SharedAccessKey=Bhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0= +``` + +This connection string is made of three parts separated by semi-colons, with each part a key and a value: + +| Key | Value | Description | +| --- | ----- | ----------- | +| HostName | `soil-moisture-sensor.azure-devices.net` | The URL of the IoT Hub | +| DeviceId | `soil-moisture-sensor` | The unique ID of the device | +| SharedAccessKey | `Bhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0=` | A symmetric key known by the device and the IoT Hub | + +The last part of this connection string, the `SharedAccessKey`, is the symmetric key known by both the device and the IoT Hub. This key is never sent from the device to the cloud, or the cloud to the device. Instead it is used to encrypt data that is sent or received. + +โœ… Do an experiment. What do you think will happen if you change the `SharedAccessKey` part of the connection string when connecting your IoT device? Try it out. + +When the device first tries to connect it sends a shared access signature (SAS) token consisting of the URL of the IoT Hub, a timestamp that the access signature will expire (usually 1 day from the current time), and a signature. This signature consists of the URL and the expiry time encrypted with the shared access key from the connection string. + +The IoT Hub decrypts this signature with the shared access key, and if the decrypted value matches the URL and expiry, the device is allowed to connect. It also verifies that the current time is before the expiry, to stop a malicious device capturing the SAS token of a real device and using it. + +This is an elegant way to verify that the sender is the correct device. By sending some known data in both a decrypted and encrypted form, the server can verify the device by ensuring when it decrypts the encrypted data, the result matches the decrypted version that was sent. If it matches, then both the sender and recipient have the same symmetric encryption key. + +> ๐Ÿ’ Because of the expiry time, your IoT device needs to know the accurate time, usually read from an [NTP](https://wikipedia.org/wiki/Network_Time_Protocol) server. If the time is not accurate, the connection will fail. + +After the connection, all data sent to the IoT Hub from the device, or to the device from the IoT Hub will be encrypted with the shared access key. + +โœ… What do you think will happen if multiple devices share the same connection string? + +> ๐Ÿ’ It is bad security practice to store this key in code. If a hacker gets your source code, they can get your key. It is also harder when releasing code as you would need to recompile with an updated key for every device. It is better to load this key from a hardware security module - a chip on the IoT device that stores encrypted values that can be read by your code. +> +> When learning IoT it is often easier to put the key in code, as you did in an earlier lesson, but you must ensure this key is not checked into public source code control. + +### X.509 certificates + +When you are using a asymmetric encryption with a public/private key pair, you need to provide your public key to anyone who wants to send you data. The problem is, how can the recipient of your key be sure it's actually your public key, not someone else pretending to be you? Instead of providing a key, you can instead provide your public key inside a certificate that has been verified by a trusted third party, called an X.509 certificate. + +X.509 certificates are digital documents that contain the public key part of the public/private key pair. They are usually issued by one of a number of trusted organizations called [Certification authorities](https://wikipedia.org/wiki/Certificate_authority) (CAs), and digitally signed by the CA to indicate the key is valid and comes from you. You trust the certificate and that the public key is from who the certificate says it is from, because you trust the CA, similar to how you would trust a passport or driving license because you trust the country issuing it. Certificates cost money, so you can also 'self-sign', that is create a certificate yourself that is signed by you, for testing purposes. + +> ๐Ÿ’ You should never use a self-signed certificate for a production release. + +These certificates have a number of fields in them, including who the public key is from, the details of the CA who issued it, how long it is valid for, and the public key itself. Before using a certificate, it is good practice to verify it by checking that is was signed by the original CA. + +โœ… You can read a full list of the fields in the certificate in the [Microsoft Understanding X.509 Public Key Certificates tutorial](https://docs.microsoft.com/azure/iot-hub/tutorial-x509-certificates?WT.mc_id=academic-17441-jabenn#certificate-fields) + +When using X.509 certificates, both the sender and the recipient will have their own public and private keys, as well as both having X.509 certificates that contain the public key. They then exchange X.509 certificates somehow, using each others public keys to encrypt the data they send, and their own private key to decrypt the data they receive. + +![Instead of sharing a public key, you can share a certificate. The user of the certificate can verify that it comes from you by checking with the certificate authority who signed it.](../../../images/send-message-certificate.png) + +***nstead of sharing a public key, you can share a certificate. The user of the certificate can verify that it comes from you by checking with the certificate authority who signed it. Certificate by alimasykurm from the [Noun Project](https://thenounproject.com)*** + +One big advantage of using X.509 certificates is that they can be shared between devices. You can create one certificate, upload it to IoT Hub, and use this for all your devices. Each device then just needs to know the private key to decrypt the messages it receives from IoT Hub. + +The certificate used by your device to encrypt messages it sends to the IoT Hub is published by Microsoft. It is the same certificate that a lot of Azure services use, and is sometimes built into the SDKs + +> ๐Ÿ’ Remember, a public key is just that - public. The Azure public key can only be used to encrypt data sent to Azure, not to decrypt it, so it can be shared everywhere, including in source code. For example, you can see it in the [Azure IoT C SDK source code](https://github.com/Azure/azure-iot-sdk-c/blob/master/certs/certs.c). + +โœ… There is a lot of jargon with X.509 certificates. You can read the definitions of some of the terms you might come across in [The laymanโ€™s guide to X.509 certificate jargon](https://techcommunity.microsoft.com/t5/internet-of-things/the-layman-s-guide-to-x-509-certificate-jargon/ba-p/2203540?WT.mc_id=academic-17441-jabenn) + +## Generate and use an X.509 certificate + +The steps to generate an X.509 certificate are: + +1. Create a public/private key pair. One of the most widely used algorithm to generate a public/private key pair is called [RSA](https://wikipedia.org/wiki/RSA_(cryptosystem)). + +1. Submit the public key with associated data for signing, either by a CA, or by self-signing + +The Azure CLI has commands to create a new device identity in IoT Hub, and automatically generate the public/private key pair and create a self-signed certificate. + +> ๐Ÿ’ If you want to see the steps in detail, rather than using the Azure CLI, you can find it in the [Using OpenSSL to create self-signed certificates tutorial in the Microsoft IoT Hub documentation](https://docs.microsoft.com/azure/iot-hub/tutorial-x509-self-sign?WT.mc_id=academic-17441-jabenn) + +### Task - create a device identity using an X.509 certificate + +1. Run the following command to register the new device identity, automatically generating the keys and certificates: + + ```sh + az iot hub device-identity create --device-id soil-moisture-sensor-x509 \ + --am x509_thumbprint \ + --output-dir . \ + --hub-name + ``` + + Replace `` with the name you used for your IoT Hub. + + This will create a device with an ID of `soil-moisture-sensor-x509` to distinguish from the device identity you created in the last lesson. This command will also create 2 files in the current directory: + + * `soil-moisture-sensor-x509-key.pem` - this file contains the private key for the device. + * `soil-moisture-sensor-x509-cert.pem` - this is the X.509 certificate file for the device. + + Keep these files safe! The private key file should not be checked into public source code control. + +### Task - use the X.509 certificate in your device code + +Work through the relevant guide to connect your IoT device to the cloud using the X.509 certificate: + +* [Arduino - Wio Terminal](wio-terminal-x509.md) +* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-x509.md) + +--- + +## ๐Ÿš€ Challenge + +There are multiple ways to create, manage and delete Azure services such as Resource Groups and IoT Hubs. One way is the [Azure Portal](https://portal.azure.com?WT.mc_id=academic-17441-jabenn) - a web-based interface that gives you a GUI to manage your Azure services. + +Head to [portal.azure.com](https://portal.azure.com?WT.mc_id=academic-17441-jabenn) and investigate the portal. See if you can create an IoT Hub using the portal, then delete it. + +**Hint** - when creating services through the portal, you don't need to create a Resource Group up front, one can be created when you are creating the service. Make sure you delete it when you are finished! + +You can find plenty of documentation, tutorials and guides on the Azure Portal in the [Azure portal documentation](https://docs.microsoft.com/azure/azure-portal/?WT.mc_id=academic-17441-jabenn). + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/20) + +## Review & Self Study + +* Read up on the history of cryptography on the [History of cryptography page on Wikipedia](https://wikipedia.org/wiki/History_of_cryptography). +* Read up on X.509 certificates on the [X.509 page on Wikipedia](https://wikipedia.org/wiki/X.509). + +## Assignment + +[Build a new IoT device](assignment.md) diff --git a/2-farm/lessons/6-keep-your-plant-secure/assignment.md b/2-farm/lessons/6-keep-your-plant-secure/assignment.md new file mode 100644 index 00000000..a00ad5ef --- /dev/null +++ b/2-farm/lessons/6-keep-your-plant-secure/assignment.md @@ -0,0 +1,15 @@ +# Build a new IoT device + +## Instructions + +Over the last 6 lessons you have learned about digital agriculture and how to use IoT devices to gather data to predict plant growth, and automate watering based off soil moisture readings. + +Use what you have learned to build a new IoT device using a sensor and actuator or your choice. Send telemetry to an IoT Hub, and use that to control an actuator via serverless code. You can use a sensor and an actuator you have already used in this or the previous project, or if you have other hardware try something new. + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Code an IoT device to use a sensor and actuator | Coded an IoT device that works with a sensor and actuator | Coded an IoT device that works with a sensor or an actuator | Was unable code an IoT device to use a sensor or an actuator | +| Connect the IoT device to IoT Hub | Was able to deploy an IoT Hub and send telemetry to it, and receive commands from it | Was able to deploy an IoT Hub and either send telemetry or receive commands | Was unable to deploy an IoT Hub and communicate with it from an IoT device | +| Control the actuator using serverless code | Was able to deploy an Azure Function to control the device triggered by telemetry events | Was able to deploy an Azure Function triggered by telemetry events but was unable to control the actuator | Was unable to deploy an Azure Function | diff --git a/2-farm/lessons/6-keep-your-plant-secure/code/pi/soil-moisture-sensor/app.py b/2-farm/lessons/6-keep-your-plant-secure/code/pi/soil-moisture-sensor/app.py new file mode 100644 index 00000000..7346d4d3 --- /dev/null +++ b/2-farm/lessons/6-keep-your-plant-secure/code/pi/soil-moisture-sensor/app.py @@ -0,0 +1,40 @@ +import time +from grove.adc import ADC +from grove.grove_relay import GroveRelay +import json +from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse, X509 + +adc = ADC() +relay = GroveRelay(5) + +host_name = ".azure-devices.net" +device_id = "soil-moisture-sensor-x509" +x509 = X509("./soil-moisture-sensor-x509-cert.pem", "./soil-moisture-sensor-x509-key.pem") + +device_client = IoTHubDeviceClient.create_from_x509_certificate(x509, host_name, device_id) + +print("Connecting") +device_client.connect() +print("Connected") + +def handle_method_request(request): + print("Direct method received - ", request.name) + + if request.name == "relay_on": + relay.on() + elif request.name == "relay_off": + relay.off() + + method_response = MethodResponse.create_from_method_request(request, 200) + device_client.send_method_response(method_response) + +device_client.on_method_request_received = handle_method_request + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + message = Message(json.dumps({ "soil_moisture": soil_moisture })) + device_client.send_message(message) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/6-keep-your-plant-secure/code/virtual-device/soil-moisture-sensor/app.py b/2-farm/lessons/6-keep-your-plant-secure/code/virtual-device/soil-moisture-sensor/app.py new file mode 100644 index 00000000..87e95aa1 --- /dev/null +++ b/2-farm/lessons/6-keep-your-plant-secure/code/virtual-device/soil-moisture-sensor/app.py @@ -0,0 +1,43 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +from counterfit_shims_grove.adc import ADC +from counterfit_shims_grove.grove_relay import GroveRelay +import json +from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse, X509 + +adc = ADC() +relay = GroveRelay(5) + +host_name = ".azure-devices.net" +device_id = "soil-moisture-sensor-x509" +x509 = X509("./soil-moisture-sensor-x509-cert.pem", "./soil-moisture-sensor-x509-key.pem") + +device_client = IoTHubDeviceClient.create_from_x509_certificate(x509, host_name, device_id) + +print("Connecting") +device_client.connect() +print("Connected") + +def handle_method_request(request): + print("Direct method received - ", request.name) + + if request.name == "relay_on": + relay.on() + elif request.name == "relay_off": + relay.off() + + method_response = MethodResponse.create_from_method_request(request, 200) + device_client.send_method_response(method_response) + +device_client.on_method_request_received = handle_method_request + +while True: + soil_moisture = adc.read(0) + print("Soil moisture:", soil_moisture) + + message = Message(json.dumps({ "soil_moisture": soil_moisture })) + device_client.send_message(message) + + time.sleep(10) \ No newline at end of file diff --git a/2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md b/2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md new file mode 100644 index 00000000..93bf7985 --- /dev/null +++ b/2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md @@ -0,0 +1,55 @@ +# Use the X.509 certificate in your device code - Virtual IoT Hardware and Raspberry Pi + +In this part of the lesson, you will connect your virtual IoT device or Raspberry Pi to your IoT Hub using the X.509 certificate. + +## Connect your device to IoT Hub + +The next step is to connect your device to IoT Hub using the X.509 certificates. + +### Task - connect to IoT Hub + +1. Copy the key and certificate files to the folder containing your IoT device code. If you are using a Raspberry Pi through VS Code Remote SSH and created the keys on your PC or Mac, you can drag and drop the files into the explorer in VS Code to copy them. + +1. Open the `app.py` file + +1. To connect using an X.509 certificate, you will need the host name of the IoT Hub, and the X.509 certificate. Start by creating a variable containing the host name by adding the following code before the device client is created: + + ```python + host_name = "" + ``` + + Replace `` with your IoT Hub's host name. You can get this from the `HostName` section in the `connection_string`. It will be the name of your IoT Hub, ending with `.azure-devices.net` + +1. Below this, declare a variable with the device ID: + + ```python + device_id = "soil-moisture-sensor-x509" + ``` + +1. You will need an instance of the `X509` class containing the X.509 files. Add `X509` to the list of classes imported from the `azure.iot.device` module: + + ```python + from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse, X509 + ``` + +1. Create an `X509` class instance using your certificate and key files by adding this code below the `host_name` declaration: + + ```python + x509 = X509("./soil-moisture-sensor-x509-cert.pem", "./soil-moisture-sensor-x509-key.pem") + ``` + + This will create the `X509` class using the `soil-moisture-sensor-x509-cert.pem` and `soil-moisture-sensor-x509-key.pem` files created earlier. + +1. Replace the line of code that creates the `device_client` from a connection string, with the following: + + ```python + device_client = IoTHubDeviceClient.create_from_x509_certificate(x509, host_name, device_id) + ``` + + This will connect using the X.509 certificate instead of a connection string. + +1, RUn your code. Monitor the messages sent to IoT Hub, and send direct method requests as before. You will see the device connecting and sending soil moisture readings, as well as receiving direct method requests. + +> ๐Ÿ’ You can find this code in the [code/pi](code/pi) or [code/virtual-device](code/virtual-device) folder. + +๐Ÿ˜€ Your soil moisture sensor program is connected to your IoT Hub using an X.509 certificate! diff --git a/2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md b/2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md new file mode 100644 index 00000000..c08da606 --- /dev/null +++ b/2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md @@ -0,0 +1,3 @@ +# Use the X.509 certificate in your device code - Wio Terminal + +At the time of writing, the Azure Arduino SDK doesn't support X.509 certificates. If you want to experiment with X.509 certificates, you can refer to the [Virtual IoT device instructions using the Python SDK](single-board-computer-x509.md) diff --git a/3-transport/README.md b/3-transport/README.md new file mode 100644 index 00000000..cb7c9cb4 --- /dev/null +++ b/3-transport/README.md @@ -0,0 +1,24 @@ +# Transport from farm to factory - using IoT to track food deliveries + +Many farmers grow food to sell - either they are commercial growers who sell everything they grow, or they are subsistence farmers who sell their excess produce to buy necessities. Somehow the food has to get from the farm to the consumer, and this is usually relies on bulk transport from farms, to hubs or processing plants, then on to stores. For example, a tomato farmer will harvest tomatoes, pack them into boxes, load the boxes into a truck then deliver to a processing plant. The tomatoes will then be sorted, and from there delivered to the customers in the form of retail, food processing, or restaurants. + +IoT can help with this supply chain by tracking the food in transit - ensuring drivers are going where they should, monitoring vehicle locations, and getting alerts when vehicles arrive so that food can be unloaded ready for processing as soon as possible. + +> ๐ŸŽ“ A *supply chain* is the sequence of activities to make and deliver something. For example, in tomato farming it covers seed, soil, fertilizer and water supply, growing tomatoes, delivering tomatoes to a central hub, transporting them to a supermarkets local hub, transporting to the individual supermarket, being put out on display, then sold to a consumer and taken home to eat. Each step is like the links in a chain. + +> ๐ŸŽ“ The transportation part of the supply chain is know as *logistics*. + +In these 4 lessons you'll learn how to apply the Internet of Things to improve the supply chain by monitoring food as it is loaded onto a (virtual) truck which is tracked as it moves to it's destination. You will learn about GPS tracking, how to store and visualize GPS data, and how to be alerted when a truck arrives at its destination. + +> ๐Ÿ’ These lessons will use some cloud resources. If you don't complete all the lessons in this project, make sure you follow the [Clean up your project](lessons/4-keep-your-plant-secure/README.md#clean-up-your-project) step in [lesson 4](lessons/6-keep-your-plant-secure/README.md). + +## Topics + +1. [Location tracking](lessons/1-location-tracking/README.md) +1. [Store location data](./3-transport/lessons/2-store-location-data/README.md) +1. [Visualize location data](lessons/3-visualize-location-data/README.md) +1. [Geofences](lessons/4-geofences/README.md) + +## Credits + +All the lessons were written with โ™ฅ๏ธ by [Jim Bennett](https://GitHub.com/JimBobBennett) diff --git a/3-transport/lessons/1-location-tracking/README.md b/3-transport/lessons/1-location-tracking/README.md new file mode 100644 index 00000000..05ebab64 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/README.md @@ -0,0 +1,204 @@ +# Location tracking + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/21) + +## Introduction + +The main process for getting food from a farmer to a consumer involves loading boxes of produce on to trucks, ships, airplanes or other commercial transport vehicles, and delivering the food somewhere - either direct to a customer, or to a central hub or warehouse for processing. The whole end-to-end process from farm to consumer is part of a process called the *supply chain*. The video below from Arizona State University's W. P. Carey School of Business talks about the idea of the supply chain and how it is managed in more detail. + +[![What is Supply Chain Management? A video from Arizona State University's W. P. Carey School of Business](https://img.youtube.com/vi/Mi1QBxVjZAw/0.jpg)](https://www.youtube.com/watch?v=Mi1QBxVjZAw) + +Adding IoT devices can drastically improve your supply chain, allowing you to manage where items are, plan transport and goods handling better, and respond quicker to problems. + +When managing a fleet of vehicles such as trucks, it is helpful to know where each vehicle is at a given time. Vehicles can be fitted with GPS sensors that send their location to IoT systems, allowing the owners to pinpoint their location, see the route they have taken, and know when they will arrive at their destination. Most vehicles operate outside of WiFi coverage, so they use cellular networks to send this kind of data. Sometimes the GPS sensor is built into more complex IoT devices such as electronic log books. These devices track how long a truck has been driven for to ensure drivers are in compliance with local laws on working hours. + +In this lesson you will learn how to track a vehicles location using a Global Positioning System (GPS) sensor. + +In this lesson we'll cover: + +* [Connected vehicles](#connected-vehicles) +* [Geospatial coordinates](#geospatial-coordinates) +* [Global Positioning Systems (GPS)](#global-positioning-systems-gps) +* [Read GPS sensor data](#read-gps-sensor-data) +* [NMEA GPS data](#nmea-gps-data) +* [Decode GPS sensor data](#decode-gps-sensor-data) + +## Connected vehicles + +IoT is transforming the way goods are transported by creating fleets of *connected vehicles*. These vehicles are connected to central IT systems reporting information on their location, and other sensor data. Having a fleet of connected vehicles has a wide range of benefits: + +* Location tracking - you can pinpoint where a vehicle is at any time, allowing you to: + + * Get alerts when a vehicle is about to arrive at a destination to prepare a crew for unloading + * Locate stolen vehicles + * Combine location and route data with traffic problems to allow you to re-route vehicles mid-journey + * Be compliant with tax. Some countries charge vehicles for the amount of mileage driven on public roads (such as [New Zealand's RUC](https://www.nzta.govt.nz/vehicles/licensing-rego/road-user-charges/)), so knowing when a vehicle is on public roads vs private roads makes it easier to calculate tax owed. + * Know where to send maintenance crews in the event of a breakdown + +* Driver telemetry - being able to ensure drivers are adhering to speed limits, cornering at appropriate speeds, braking early and efficiently, and driving safely. Connected vehicles can also have cameras to record incidents. This can be linked to insurance, giving reduced rates for good drivers. + +* Driver hours compliance - ensuring drivers only drive for their legally allowed hours based on the times they turn the engine on and off. + +These benefits can be combined - for example, combining driver hours compliance with location tracking to re-route drivers if they cannot reach their destination within their allowed driving hours. These can also be combined with other vehicle-specific telemetry, such as temperature data from temperature controlled trucks, allow vehicles to be re-routed if their current route would mean goods cannot be kept at temperature. + +> ๐ŸŽ“ Logistics is the process of transporting goods from one place to another, such as from a farm to a supermarket via one or more warehouses. A farmer packs boxes of tomatoes that are loaded onto a truck, delivered to a central warehouse, and put onto a second truck that may contain a mixture of different types of produce which are then delivered to a supermarket. + +The core component of vehicle tracking is GPS - sensors that can pinpoint their location anywhere on Earth. In this lesson you will learn how to use a GPS sensor, starting with learning about how to define a location on Earth. + +## Geospatial coordinates + +Geospatial coordinates are used to define points on the Earth's surface, similar to how coordinates can be used to draw to a pixel on a computer screen or position stitches in cross stitch. For a single point, you have a pair of coordinates. For example, the Microsoft Campus in Redmond, Washington, USA is located at 47.6423109,-122.1390293. + +### Latitude and longitude + +The Earth is a sphere - a three-dimensional circle. Because of this, points are defined is by dividing it into 360 degrees, the same as the geometry of circles. Latitude measures the number of degrees north to south, longitude measures the number of degrees east to west. + +> ๐Ÿ’ No-one really knows the original reason why circles are divided into 360 degrees. The [degree (angle) page on Wikipedia](https://wikipedia.org/wiki/Degree_(angle)) covers some of the possible reasons. + +![Lines of latitude from 90ยฐ at the North Pole, 45ยฐ halfway between the North Pole and the equator, 0ยฐ at the equator, -45ยฐ halfway between the equator and the South Pole, and -90ยฐ at the South Pole](../../../images/latitude-lines.png) + +Latitude is measured using lines that circle the Earth and run parallel to the equator, dividing the Northern and Southern Hemispheres into 90ยฐ each. The equator is at 0ยฐ, the North Pole is 90ยฐ, also known as 90ยฐ North, and the South Pole is at -90ยฐ, or 90ยฐ South. + +Longitude is measured as the number of degrees measured east and west. The 0ยฐ origin of longitude is called the *Prime Meridian*, and was defined in 1884 to be a line from the North to the South Pole that goes through the [British Royal Observatory in Greenwich, England](https://wikipedia.org/wiki/Royal_Observatory,_Greenwich). + +![Lines of longitude that go from -180ยฐ to the west of the Prime Meridian, to 0ยฐ on the Prime Meridian, to 180ยฐ east of the Prime Meridian](../../../images/longitude-meridians.png) + +> ๐ŸŽ“ A meridian is an imaginary straight line that goes from the North Pole to the South Pole, forming a semicircle. + +To measure the longitude of a point, you measure the number of degrees round the equator from the Prime Meridian to a meridian that passes through that point. Longitude goes from -180ยฐ, or 180ยฐ West, through 0ยฐ at the Prime Meridian, to 180ยฐ, or 180ยฐ East. 180ยฐ and -180ยฐ refer to the same point, the antimeridian or 180th meridian. This is a meridian on the opposite side of the Earth from the Prime Meridian. + +> ๐Ÿ’ The antimeridian is not to be confused with the International Date Line, which is in approximately the same position, but is not a straight line and varies to fit around geo-political boundaries. + +โœ… Do some research: Try to find the latitude and longitude of your current location. + +### Degrees, minutes and seconds vs decimal degrees + +Traditionally, measurements of degrees of latitude and longitude were done using sexagesimal numbering, or base-60, a numbering system used by the Ancient Babylonians who did the first measurements and recordings of time and distance. You use sexagesimal every day probably without even realising it - dividing hours into 60 minutes and minutes into 60 seconds. + +Longitude and latitude is measured in degrees, minutes and seconds, with one minute being 1/60 of a degree, and 1 second being 1/60 minute. + +For example, at the equator: + +* 1ยฐ of latitude is **111.3 kilometers** +* 1 minute of latitude is 111.3/60 = **1.855 kilometers** +* 1 second of latitude is 1.855/60 = **0.031 kilometers** + +The symbol for a minute is a single quote, for a second it is a double quote. 2 degrees, 17 minutes and 43 seconds for example, would be written as 2ยฐ17'43". Parts of seconds are given as decimals, for example half a second is 0ยฐ0'0.5". + +Computers don't work in base-60, so these coordinates are given as decimal degrees when using GPS data in most computer systems. For example, 2ยฐ17'43" is 2.295277. The degree symbol is usually omitted. + +Coordinates for a point are always given as `latitude,longitude`, so the example earlier of the Microsoft Campus at 47.6423109,-122.117198 has: + +* A latitude of 47.6423109 (47.6423109 degrees north of the equator) +* A longitude of -122.1390293 (122.1390293 degrees west of the Prime Meridian). + +![The Microsoft Campus at 47.6423109,-122.117198](../../../images/microsoft-gps-location-world.png) + +## Global Positioning Systems (GPS) + +GPS systems use multiple satellites orbiting the Earth to locate your position. You've probably used GPS systems without even knowing it - to find your location on a mapping app on your phone such as Apple Maps or Google Maps, or to see where your ride is in a ride hailing app such as Uber or Lyft, or when using satellite navigation (sat-nav) in your car. + +> ๐ŸŽ“ The satellites in 'satellite navigation' are GPS satellites! + +GPS systems work by having a number of satellites that send a signal with each satellites current position, and an accurate timestamp. These signals are sent over radio waves and are detected by an antenna in the GPS sensor. A GPS sensor will detect these signals, and using the current time measure how long it took for the signal to reach the sensor from the satellite. Because the speed of radio waves is constant, the GPS sensor can use the time stamp that was sent to work out how far away the sensor is from the satellite. By combining the data from at least 3 satellites with the positions sent, the GPS sensor is able to pinpoint its location on Earth. + +> ๐Ÿ’ GPS sensors need antennas to detect radio waves. The antennas built into trucks and cars with on-board GPS are positioned to get a good signal, usually on the windshield or roof. If you are using a separate GPS system, such as a smartphone or an IoT device, then you need to ensure that the antenna built into the GPS system or phone has a clear view of the sky, such as being mounted on your windshield. + +![By knowing the distance from the sensor to multiple satellites, the location be calculated](../../../images/gps-satellites.png) + +***By knowing the distance from the sensor to multiple satellites, the location be calculated. Satellite by Noura Mbarki from the [Noun Project](https://thenounproject.com)*** + +GPS satellites are circling the Earth, not at a fixed point above the sensor, so location data includes altitude above sea level as well as latitude and longitude. + +GPS used to have limitations on accuracy enforced by the US military, limiting accuracy to around 5 meters. This limitation was removed in 2000, allowing an accuracy of 30 centimeters. Getting this accuracy is not always possible due to interference with the signals. + +โœ… If you have a smart phone, launch the mapping app and see how accurate your location is. It may take a short period of time for your phone to detect multiple satellites to get a more accurate location. + +> ๐Ÿ’ The satellites contain atomic clocks that are incredibly accurate, but they drift by 38 microseconds (0.0000038 seconds) a day compared to atomic clocks, due to time slowing down as speed increases as predicted by Einstein's theories of special and general relativity - the satellites travel faster than the Earth's rotation. This drift has been used to prove the predictions of special and general relativity, and has to be adjusted for in the design of GPS systems. Literally time runs slower on a GPS satellite. + +GPS systems have been developed and deployed by a number of countries and political unions including the US, Russia, Japan, India, the EU, and China. Modern GPS sensor can connect to most of these systems to get faster and more accurate fixes. + +> ๐ŸŽ“ The groups of satellites in each deployment are referred to as constellations. + +## Read GPS sensor data + +Most GPS sensors send GPS data over UART. + +> โš ๏ธ UART was covered in [project 2, lesson 2](../../../2-farm/lessons/2-detect-soil-moisture/README.md#universal-asynchronous-receiver-transmitter-uart). Refer back to that lesson if needed. + +You can use a GPS sensor on your IoT device to get GPS data. + +### Task - connect a GPS sensor and read GPS data + +Work through the relevant guide to measure soil moisture using your IoT device: + +* [Arduino - Wio Terminal](wio-terminal-gps-sensor.md) +* [Single-board computer - Raspberry Pi](pi-gps-sensor.md) +* [Single-board computer - Virtual device](virtual-device-gps-sensor.md) + +## NMEA GPS data + +When you ran your code, you would have seen what might appear to be gibberish in the output. This is actually standard GPS data, and it all has meaning. + +GPS sensors output data using NMEA messages, using the NMEA 0183 standard. NMEA is an acronym for the [National Marine Electronics Association](https://www.nmea.org), a US-based trade organization that sets standard for communication between marine electronics. + +> ๐Ÿ’ This standard is proprietary and sells for at least US$2,000, but enough information about it is in the public domain that most of the standard has been reverse engineered and can be used in open source and other non-commercial code. + +These messages are text-based. Each message consists of a *sentence* that starts with a `$` character, followed by 2 characters to indicate the source of the message (e.g GP for the US GPS system, GN for GLONASS, the Russian GPS system), and 3 characters to indicate the type of message. The rest of the message is fields separated by commas, ending in a new line character. + +Some of the types of message that can be received are: + +| Type | Description | +| ---- | ----------- | +| GGA | GPS Fix Data, including the latitude, longitude, and altitude of the GPS sensor, along with the number of satellites in view to calculate this fix. | +| ZDA | The current date and time, including the local time zone | +| GSV | Details of the satellites in view - defined as the satellited that GPS sensor can detect signals from | + +> ๐Ÿ’ GPS data includes time stamps, so your IoT device can get the time if needed from a GPS sensor, rather than relying on an NTP server or internal real-time clock. + +The GGA message includes the current location using the `(dd)dmm.mmmm` format, along with a single character to indicate direction. The `d` in the format is degrees, the `m` is minutes, with seconds as decimals of minutes. For example, 2ยฐ17'43" would be 217.716666667 - 2 degrees, 17.716666667 minutes. + +The direction character can be `N` or `S` for latitude to indicate north or south, and `E` or `W` for longitude to indicate east or west. For example, a latitude of 2ยฐ17'43" would have a direction character of `N`, -2ยฐ17'43" would have a direction character of `S`. + +For example - the NMEA sentence `$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67` + +* The latitude part is `4738.538654,N`, which converts to 47.6423109 in decimal degrees. `4738.538654` is 47.6423109, and the direction is `N` (north), so it is a positive latitude. + +* The longitude part is `12208.341758,W`, which converts to -122.1390293 in decimal degrees. `12208.341758` is 122.1390293ยฐ, and the direction is `W` (west), so it is a negative longitude. + +## Decode GPS sensor data + +Rather than use the raw NMEA data, it is better to decode it into a more useful format. There are multiple open-source libraries you can use to help extract useful data from the raw NMEA messages. + +### Task - decode GPS sensor data + +Work through the relevant guide to measure soil moisture using your IoT device: + +* [Arduino - Wio Terminal](wio-terminal-gps-decode.md) +* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-gps-decode.md) + +--- + +## ๐Ÿš€ Challenge + +Write your own NMEA decoder! Rather than relying on third party libraries to decode NMEA sentences, can you write your own decoder to extract latitude and longitude from NMEA sentences? + +## Post-lecture quiz + +[Post-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/22) + +## Review & Self Study + +* Read more on Geospatial Coordinates on the [Geographic coordinate system page on Wikipedia](https://wikipedia.org/wiki/Geographic_coordinate_system). +* Read up on the Prime Meridians on other celestial bodies besides the Earth on the [Prime Meridian page on Wikipedia](https://wikipedia.org/wiki/Prime_meridian#Prime_meridian_on_other_planetary_bodies) +* Research the various different GPS systems from various world governments and political unions such as the EU, Japan, Russia, India and the US. + +## Assignment + +[Investigate other GPS data](assignment.md) diff --git a/3-transport/lessons/1-location-tracking/assignment.md b/3-transport/lessons/1-location-tracking/assignment.md new file mode 100644 index 00000000..e524e326 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/assignment.md @@ -0,0 +1,15 @@ +# Investigate other GPS data + +## Instructions + +The NMEA sentences that come from your GPS sensor have other data in addition to location. Investigate the additional data, and use it in your IoT device. + +For example - can you get the current date and time? If you are using a microcontroller, can you set the clock using GPS data in the same way you set is using NTP signals in the previous project? Can you get elevation (your height above sea level), or your current speed? + +If you are using a virtual IoT device, then you can get some of this data by sending MENA sentences generated using tools [nmeagen.org](https://www.nmeagen.org). + +## Rubric + +| Criteria | Exemplary | Adequate | Needs Improvement | +| -------- | --------- | -------- | ----------------- | +| Get more GPS data | Is able to get and use more GPS data, either as telemetry or to set up the IoT device | Is able to get more GPS data, but is unable to use it | Is unable to get more GPS data | diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/pi/gps-sensor/app.py b/3-transport/lessons/1-location-tracking/code-gps-decode/pi/gps-sensor/app.py new file mode 100644 index 00000000..6089dd01 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/pi/gps-sensor/app.py @@ -0,0 +1,43 @@ +import time +import serial +import pynmea2 +import json +from azure.iot.device import IoTHubDeviceClient, Message + +connection_string = "" + +serial = serial.Serial('/dev/ttyAMA0', 9600, timeout=1) +serial.reset_input_buffer() +serial.flush() + +device_client = IoTHubDeviceClient.create_from_connection_string(connection_string) + +print("Connecting") +device_client.connect() +print("Connected") + +def print_gps_data(line): + msg = pynmea2.parse(line) + if msg.sentence_type == 'GGA': + lat = pynmea2.dm_to_sd(msg.lat) + lon = pynmea2.dm_to_sd(msg.lon) + + if msg.lat_dir == 'S': + lat = lat * -1 + + if msg.lon_dir == 'W': + lon = lon * -1 + + message_json = { "gps" : { "lat":lat, "lon":lon } } + print("Sending telemetry", message_json) + message = Message(json.dumps(message_json)) + device_client.send_message(message) + +while True: + line = serial.readline().decode('utf-8') + + while len(line) > 0: + print_gps_data(line) + line = serial.readline().decode('utf-8') + + time.sleep(1) diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/virtual-device/gps-sensor/app.py b/3-transport/lessons/1-location-tracking/code-gps-decode/virtual-device/gps-sensor/app.py new file mode 100644 index 00000000..1bff864c --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/virtual-device/gps-sensor/app.py @@ -0,0 +1,44 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +import counterfit_shims_serial +import pynmea2 +import json +from azure.iot.device import IoTHubDeviceClient, Message + +connection_string = "" + +serial = counterfit_shims_serial.Serial('/dev/ttyAMA0') + +device_client = IoTHubDeviceClient.create_from_connection_string(connection_string) + +print("Connecting") +device_client.connect() +print("Connected") + +def send_gps_data(line): + msg = pynmea2.parse(line) + if msg.sentence_type == 'GGA': + lat = pynmea2.dm_to_sd(msg.lat) + lon = pynmea2.dm_to_sd(msg.lon) + + if msg.lat_dir == 'S': + lat = lat * -1 + + if msg.lon_dir == 'W': + lon = lon * -1 + + message_json = { "gps" : { "lat":lat, "lon":lon } } + print("Sending telemetry", message_json) + message = Message(json.dumps(message_json)) + device_client.send_message(message) + +while True: + line = serial.readline().decode('utf-8') + + while len(line) > 0: + send_gps_data(line) + line = serial.readline().decode('utf-8') + + time.sleep(60) \ No newline at end of file diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/.gitignore b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/.vscode/extensions.json b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/include/README b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/lib/README b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/platformio.ini b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/platformio.ini new file mode 100644 index 00000000..f371896d --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/platformio.ini @@ -0,0 +1,16 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino +lib_deps = + mikalhart/TinyGPSPlus @ 1.0.2 diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/src/main.cpp b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/src/main.cpp new file mode 100644 index 00000000..bd07113a --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/src/main.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +static Uart Serial3(&sercom3, PIN_WIRE_SCL, PIN_WIRE_SDA, SERCOM_RX_PAD_1, UART_TX_PAD_0); +TinyGPSPlus gps; + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + Serial3.begin(9600); + + while (!Serial3) + ; // Wait for Serial3 to be ready + + delay(1000); + + pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT); +} + +void print_gps_data() +{ + if (gps.encode(Serial3.read())) + { + if (gps.location.isValid()) + { + Serial.print(gps.location.lat(), 6); + Serial.print(F(",")); + Serial.print(gps.location.lng(), 6); + Serial.print(" - from "); + Serial.print(gps.satellites.value()); + Serial.println(" satellites"); + } + } +} + +void loop() +{ + while (Serial3.available() > 0) + { + print_gps_data(); + } + + delay(1000); +} + +void SERCOM3_0_Handler() +{ + Serial3.IrqHandler(); +} + +void SERCOM3_1_Handler() +{ + Serial3.IrqHandler(); +} + +void SERCOM3_2_Handler() +{ + Serial3.IrqHandler(); +} + +void SERCOM3_3_Handler() +{ + Serial3.IrqHandler(); +} \ No newline at end of file diff --git a/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/test/README b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/3-transport/lessons/1-location-tracking/code-gps/pi/gps-sensor/app.py b/3-transport/lessons/1-location-tracking/code-gps/pi/gps-sensor/app.py new file mode 100644 index 00000000..0dfad1e9 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/pi/gps-sensor/app.py @@ -0,0 +1,18 @@ +import time +import serial + +serial = serial.Serial('/dev/ttyAMA0', 9600, timeout=1) +serial.reset_input_buffer() +serial.flush() + +def print_gps_data(): + print(line.rstrip()) + +while True: + line = serial.readline().decode('utf-8') + + while len(line) > 0: + print_gps_data() + line = serial.readline().decode('utf-8') + + time.sleep(1) diff --git a/3-transport/lessons/1-location-tracking/code-gps/virtual-device/gps-sensor/app.py b/3-transport/lessons/1-location-tracking/code-gps/virtual-device/gps-sensor/app.py new file mode 100644 index 00000000..811ffded --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/virtual-device/gps-sensor/app.py @@ -0,0 +1,19 @@ +from counterfit_connection import CounterFitConnection +CounterFitConnection.init('127.0.0.1', 5000) + +import time +import counterfit_shims_serial + +serial = counterfit_shims_serial.Serial('/dev/ttyAMA0') + +def print_gps_data(line): + print(line.rstrip()) + +while True: + line = serial.readline().decode('utf-8') + + while len(line) > 0: + print_gps_data(line) + line = serial.readline().decode('utf-8') + + time.sleep(1) \ No newline at end of file diff --git a/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/.gitignore b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/.vscode/extensions.json b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/.vscode/extensions.json new file mode 100644 index 00000000..0f0d7401 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/include/README b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/include/README new file mode 100644 index 00000000..194dcd43 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/lib/README b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/lib/README new file mode 100644 index 00000000..6debab1e --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/platformio.ini b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/platformio.ini new file mode 100644 index 00000000..4e03e890 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:seeed_wio_terminal] +platform = atmelsam +board = seeed_wio_terminal +framework = arduino diff --git a/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/src/main.cpp b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/src/main.cpp new file mode 100644 index 00000000..7728ea24 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/src/main.cpp @@ -0,0 +1,58 @@ +#include +#include + +static Uart Serial3(&sercom3, PIN_WIRE_SCL, PIN_WIRE_SDA, SERCOM_RX_PAD_1, UART_TX_PAD_0); + +void setup() +{ + Serial.begin(9600); + + while (!Serial) + ; // Wait for Serial to be ready + + delay(1000); + + Serial3.begin(9600); + + while (!Serial3) + ; // Wait for Serial3 to be ready + + delay(1000); + + pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT); +} + +void print_gps_data() +{ + Serial.println(Serial3.readStringUntil('\n')); +} + +void loop() +{ + while (Serial3.available() > 0) + { + print_gps_data(); + } + + delay(1000); +} + +void SERCOM3_0_Handler() +{ + Serial3.IrqHandler(); +} + +void SERCOM3_1_Handler() +{ + Serial3.IrqHandler(); +} + +void SERCOM3_2_Handler() +{ + Serial3.IrqHandler(); +} + +void SERCOM3_3_Handler() +{ + Serial3.IrqHandler(); +} \ No newline at end of file diff --git a/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/test/README b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/test/README new file mode 100644 index 00000000..b94d0890 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/code-gps/wio-terminal/gps-sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/3-transport/lessons/1-location-tracking/pi-gps-sensor.md b/3-transport/lessons/1-location-tracking/pi-gps-sensor.md new file mode 100644 index 00000000..2e48b6ea --- /dev/null +++ b/3-transport/lessons/1-location-tracking/pi-gps-sensor.md @@ -0,0 +1,161 @@ +# Read GPS data - Raspberry Pi + +In this part of the lesson, you will add a GPS sensor to your Raspberry Pi, and read values from it. + +## Hardware + +The Raspberry Pi needs a GPS sensor. + +The sensor you'll use is a [Grove GPS Air530 sensor](https://www.seeedstudio.com/Grove-GPS-Air530-p-4584.html). This sensor can connect to multiple GPS systems for a fast, accurate fix. The sensor is made of 2 parts - the core electronics of the sensor, and an external antenna connected by a thin wire to pick up the radio waves from the satellites. + +This is a UART sensor, so sends GPS data over UART. + +### Connect the GPS sensor + +The Grove GPS sensor can be connected to the Raspberry Pi. + +#### Task - connect the GPS sensor + +Connect the GPS sensor. + +![A grove GPS sensor](../../../images/grove-gps-sensor.png) + +1. Insert one end of a Grove cable into the socket on the GPS sensor. It will only go in one way round. + +1. With the Raspberry Pi powered off, connect the other end of the Grove cable to the UART socket marked **UART** on the Grove Base hat attached to the Pi. This socket is on the middle row, on the side nearest the SD Card slot, the other end from the USB ports and ethernet socket. + +![The grove GPS sensor connected to the UART socket](../../../images/pi-gps-sensor.png) + +1. Position the GPS sensor so that the attached antenna has visibility to the sky - ideally next to an open window or outside. It's easier to get a clearer signal with nothing in the way of the antenna. + +## Program the GPS sensor + +The Raspberry Pi can now be programmed to use the attached GPS sensor. + +### Task - program the GPS sensor + +Program the device. + +1. Power up the Pi and wait for it to boot + +1. The GPS sensor has 2 LEDs - a blue LED that flashes when data is transmitted, and a green LED that flashes every second when receiving data from satellites. Ensure the blue LED is flashing when you power up the Pi. After a few minutes the green LED will flash - if not, you may need to reposition the antenna. + +1. Launch VS Code, either directly on the Pi, or connect via the Remote SSH extension. + + > โš ๏ธ You can refer to [the instructions for setting up and launch VS Code in lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/pi.md). + +1. With newer versions of the Raspberry Pi that support Bluetooth, there is a conflict between the serial port used for Bluetooth, and the one used by the Grove UART port. To fix this, do the following: + + 1. From the VS Code terminal, edit the `/boot/config.txt` file using `nano`, a built in terminal text editor with the following command: + + ```sh + sudo nano /boot/config.txt + ``` + + > This file can't be edited by VS Code as you need to edit it with `sudo` permissions, an elevated permission. VS Code doesn't run this permission. + + 1. Use your cursor keys to navigate to the end of the file. then copy the code below and paste it on the end of the file: + + ```ini + dtoverlay=pi3-miniuart-bt + dtoverlay=pi3-disable-bt + enable_uart=1 + ``` + + You can paste using the normal keyboard shortcuts for your device (`Ctrl+v` on Windows, Linux or Raspberry Pi OS, `Cmd+v` on macOS). + + 1. Save this file and exit nano by pressing `Ctrl+x`. Press `y` when asked if you want to save the modified buffer, then press `enter` to confirm you want to overwrite `/boot/config.txt`. + + > If you make a mistake, you can exit without saving, then repeat these steps. + + 1. Edit the `/boot/cmdline.txt` file in nano with the following command: + + ```sh + sudo nano /boot/cmdline.txt + ``` + + 1. This file has a number of key/value pairs separated by spaces. Remove any key/value pairs for the key `console`. They will probably look something like this: + + ```output + console=serial0,115200 console=tty1 + ``` + + You can navigate to these entries using the cursor keys, then delete using the normal `del` or `backspace` keys. + + For example, if your original file looks like this: + + ```output + console=serial0,115200 console=tty1 root=PARTUUID=058e2867-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait + ``` + + The new version will be: + + ```output + root=PARTUUID=058e2867-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait + ``` + + 1. Follow the steps above to save this file and exit nano + + 1. Reboot your Pi, then reconnect in VS Code once the Pi has rebooted. + +1. From the terminal, create a new folder in the `pi` users home directory called `gps-sensor`. Create a file in this folder called `app.py`: + +1. Open this folder in VS Code + +1. The GPS module sends UART data over a serial port. Install the `pyserial` Pip package to communicate with the serial port from your Python code: + + ```sh + pip3 install pip install pyserial + ``` + +1. Add the following code to your `app.py` file: + + ```python + import time + import serial + + serial = serial.Serial('/dev/ttyAMA0', 9600, timeout=1) + serial.reset_input_buffer() + serial.flush() + + def print_gps_data(line): + print(line.rstrip()) + + while True: + line = serial.readline().decode('utf-8') + + while len(line) > 0: + print_gps_data(line) + line = serial.readline().decode('utf-8') + + time.sleep(1) + ``` + + This code imports the `serial` module from the `pyserial` Pip package. It then connects to the `/dev/ttyAMA0` serial port - this is the address of the serial port that the Grove Pi Base Hat uses for its UART port. It then clears any existing data from this serial connection. + + Next a function called `print_gps_data` is defined that prints out the line passed to it to the console. + + Next the code loops forever, reading as many lines of text as it can from the serial port in each loop. It calls the `print_gps_data` function for each line. + + After all the data has been read, the loop sleeps for 1 second, then tries again. + +1. Run this code. You will see the raw output from the GPS sensor, something like the following: + + ```output + $GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67 + $GPGSA,A,1,,,,,,,,,,,,,,,*1E + $BDGSA,A,1,,,,,,,,,,,,,,,*0F + $GPGSV,1,1,00*79 + $BDGSV,1,1,00*68 + ``` + + > If you get one of the following errors when stopping and restarting your code, kill the VS Code terminal, then launch a new one and try again. + + ```output + UnicodeDecodeError: 'utf-8' codec can't decode byte 0x93 in position 0: invalid start byte + UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 0: invalid continuation byte + ``` + +> ๐Ÿ’ You can find this code in the [code-gps/pi](code-gps/pi) folder. + +๐Ÿ˜€ Your GPS sensor program was a success! diff --git a/3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md b/3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md new file mode 100644 index 00000000..39954623 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md @@ -0,0 +1,61 @@ +# Decode GPS data - Virtual IoT Hardware and Raspberry Pi + +In this part of the lesson, you will decode the NMEA messages read from the GPS sensor by the Raspberry Pi or Virtual IoT Device, and extract the latitude and longitude. + +## Decode GPS data + +Once the raw NMEA data has been read from the serial port, it can be decoded using an open source NMEA library. + +### Task - decode GPS data + +Program the device to decode the GPS data. + +1. Open the `gps-sensor` app project if it's not already open + +1. Install the `pynmea2` Pip package. This package has code for decoding NMEA messages. + + ```sh + pip3 install pynmea2 + ``` + +1. Add the following code to the imports in the `app.py` file to import the `pynmea2` module: + + ```python + import pynmea2 + ``` + +1. Replace the contents of the `print_gps_data` function with the following: + + ```python + msg = pynmea2.parse(line) + if msg.sentence_type == 'GGA': + lat = pynmea2.dm_to_sd(msg.lat) + lon = pynmea2.dm_to_sd(msg.lon) + + if msg.lat_dir == 'S': + lat = lat * -1 + + if msg.lon_dir == 'W': + lon = lon * -1 + + print(f'{lat},{lon} - from {msg.num_sats} satellites') + ``` + + This code will use the `pynmea2` library to parse the line read from the UART serial port. + + If the sentence type of the message is `GGA`, then this is a position fix message, and is processed. The latitude and longitude values are read from the message and converted to decimal degrees from the NMEA `(d)ddmm.mmmm` format. The `dm_to_sd` function does this conversion. + + The direction of the latitude is then checked, and if the latitude is south, then the value is converted to a negative number. Same with the longitude, if it is west then it is converted to a negative number. + + Finally the coordinates are printed to the console, along with the number of satellites used to get the location. + +1. Run the code. If you are using a virtual IoT device, then make sure the CounterFit app is running and the GPS data is being sent. + + ```output + pi@raspberrypi:~/gps-sensor $ python3 app.py + 47.6423109,-122.1390293 - from 3 satellites + ``` + +> ๐Ÿ’ You can find this code in the [code-gps-decode/virtual-device](code-gps-decode/virtual-device) folder, or the [code-gps-decode/pi](code-gps-decode/pi) folder. + +๐Ÿ˜€ Your GPS sensor program with data decoding was a success! diff --git a/3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md b/3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md new file mode 100644 index 00000000..81e068b8 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md @@ -0,0 +1,130 @@ +# Read GPS data - Virtual IoT Hardware + +In this part of the lesson, you will add a GPS sensor to your virtual IoT device, and read values from it. + +## Virtual Hardware + +The virtual IoT device will use a simulated GPS sensor that is accessible over UART via a serial port. + +A physical GPS sensor will have an antenna to pick up radio waves from GPS satellites, and convert the GPS signals into GPS data. The virtual version simulates this by allowing you to either set a latitude and longitude, send raw NMEA sentences, or to upload a GPX file with multiple locations that can be returned sequentially. + +> ๐ŸŽ“ NMEA sentences will be covered later in this lesson + +### Add the sensor to CounterFit + +To use a virtual GPS sensor, you need to add one to the CounterFit app + +#### Task + +Add the GPS sensor to the CounterFit app. + +1. Create a new Python app on your computer in a folder called `gps-sensor` with a single file called `app.py` and a Python virtual environment, and add the CounterFit pip packages. + + > โš ๏ธ You can refer to [the instructions for creating and setting up a CounterFit Python project in lesson 1 if needed](../../../1-getting-started/lessons/1-introduction-to-iot/virtual-device.md). + +1. Install an additional Pip package to install a CounterFit shim that can talk to UART based sensors over a serial connection. Make sure you are installing this from a terminal with the virtual environment activated. + + ```sh + pip install counterfit-shims-serial + ``` + +1. Make sure the CounterFit web app is running + +1. Create a GPS sensor: + + 1. In the *Create sensor* box in the *Sensors* pane, drop down the *Sensor type* box and select *UART GPS*. + + 1. Leave the *Port* set to */dev/ttyAMA0* + + 1. Select the **Add** button to create the humidity sensor on port `/dev/ttyAMA0` + + ![The GPS sensor settings](../../../images/counterfit-create-gps-sensor.png) + + The GPS sensor will be created and appear in the sensors list. + + ![The GPS sensor created](../../../images/counterfit-gps-sensor.png) + +## Program the GPS sensor + +The Virtual IoT device can now be programmed to use the virtual GPS sensor. + +### Task - program the GPS sensor + +Program the GPS sensor app. + +1. Make sure the `gps-sensor` app is open in VS Code + +1. Open the `app.py` file + +1. Add the following code to the top of `app.py` to connect the app to CounterFit: + + ```python + from counterfit_connection import CounterFitConnection + CounterFitConnection.init('127.0.0.1', 5000) + ``` + +1. Add the following code below this to import some needed libraries, including the library for the CounterFit serial port: + + ```python + import time + import counterfit_shims_serial + + serial = counterfit_shims_serial.Serial('/dev/ttyAMA0') + ``` + + This code imports the `serial` module from the `counterfit_shims_serial` Pip package. It then connects to the `/dev/ttyAMA0` serial port - this is the address of the serial port that the virtual GPS sensor uses for its UART port. + +1. Add the following code below this to read from the serial port and print the values to the console: + + ```python + def print_gps_data(line): + print(line.rstrip()) + + while True: + line = serial.readline().decode('utf-8') + + while len(line) > 0: + print_gps_data(line) + line = serial.readline().decode('utf-8') + + time.sleep(1) + ``` + + A function called `print_gps_data` is defined that prints out the line passed to it to the console. + + Next the code loops forever, reading as many lines of text as it can from the serial port in each loop. It calls the `print_gps_data` function for each line. + + After all the data has been read, the loop sleeps for 1 second, then tries again. + +1. Run this code, ensuring you are using a different terminal to the one that the CounterFit app is running it, so that the CounterFit app remains running. + +1. From the CounterFit app, change the value of the gps sensor. You can do this in one of thess ways: + + * Set the **Source** to `Lat/Lon`, and set an explicit latitude, longitude and number of satellites used to get the GPS fix. This value will be sent only once, so check the **Repeat** box to have the data repeat every second. + + ![The GPS sensor with lat lon selected](../../../images/counterfit-gps-sensor-latlon.png) + + * Set the **Source** to `NMEA` and add some NMEA sentences into the text box. All these values will be sent, with a delay of 1 second before each new GGA (position fix) sentence can be read. + + ![The GPS sensor with NMEA sentences set](../../../images/counterfit-gps-sensor-nmea.png) + + You can use a tool like [nmeagen.org](https://www.nmeagen.org) to generate these sentences by drawing on a map. These values will be sent only once, so check the **Repeat** box to have the data repeat one second after it has all been sent. + + * Set the **Source** to GPX file, and upload a GPX file with track locations. You can download GPX files from a number of popular mapping and hiking sites, such as [AllTrails](https://www.alltrails.com/). These files contain multiple GPS locations as a trail, and the GPS sensor will return each new location at 1 second intervals. + + ![The GPS sensor with a GPX file set](../../../images/counterfit-gps-sensor-gpxfile.png) + + These values will be sent only once, so check the **Repeat** box to have the data repeat one second after it has all been sent. + + Once you have configured the GPS settings, select the **Set** button to commit these values to the sensor. + +1. You will see the raw output from the GPS sensor, something like the following: + + ```output + $GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67 + $GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67 + ``` + +> ๐Ÿ’ You can find this code in the [code-gps/virtual-device](code-gps/virtual-device) folder. + +๐Ÿ˜€ Your GPS sensor program was a success! diff --git a/3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md b/3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md new file mode 100644 index 00000000..cf9c8a5b --- /dev/null +++ b/3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md @@ -0,0 +1,69 @@ +# Decode GPS data - Wio Terminal + +In this part of the lesson, you will decode the NMEA messages read from the GPS sensor by the Wio Terminal, and extract the latitude and longitude. + +## Decode GPS data + +Once the raw NMEA data has been read from the serial port, it can be decoded using an open source NMEA library. + +### Task - decode GPS data + +Program the device to decode the GPS data. + +1. Open the `gps-sensor` app project if it's not already open + +1. Add a library dependency for the [TinyGPSPlus](https://github.com/mikalhart/TinyGPSPlus) library to the projects `platformio.ini` file. This library has code for decoding NMEA data. + + ```ini + lib_deps = + mikalhart/TinyGPSPlus @ 1.0.2 + ``` + +1. In `main.cpp`, add an include directive for the TinyGPSPlus library: + + ```cpp + #include + ``` + +1. Below the declaration of `Serial3`, declare a TinyGPSPlus object to process the NMEA sentences: + + ```cpp + TinyGPSPlus gps; + ``` + +1. Change the contents of the `print_gps_data` function to be the following: + + ```cpp + if (gps.encode(Serial3.read())) + { + if (gps.location.isValid()) + { + Serial.print(gps.location.lat(), 6); + Serial.print(F(",")); + Serial.print(gps.location.lng(), 6); + Serial.print(" - from "); + Serial.print(gps.satellites.value()); + Serial.println(" satellites"); + } + } + ``` + + This code reads the next character from the UART serial port into the `gps` NMEA decoder. After each character, it will check to see if the decoder has read a valid sentence, then check to see if it has read a valid location. If the location is valid, it send it to the serial monitor, along with the number of satellites that contributed to this fix. + +1. Build and upload the code to the Wio Terminal. + +1. Once uploaded, you can monitor the GPS location data using the serial monitor. + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + 47.6423109,-122.1390293 - from 3 satellites + ``` + +> ๐Ÿ’ You can find this code in the [code-gps-decode/wio-terminal](code-gps-decode/wio-terminal) folder. + +๐Ÿ˜€ Your GPS sensor program with data decoding was a success! diff --git a/3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md b/3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md new file mode 100644 index 00000000..e56dd4b7 --- /dev/null +++ b/3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md @@ -0,0 +1,140 @@ +# Read GPS data - Wio Terminal + +In this part of the lesson, you will add a GPS sensor to your Wio Terminal, and read values from it. + +## Hardware + +The Wio Terminal needs a GPS sensor. + +The sensor you'll use is a [Grove GPS Air530 sensor](https://www.seeedstudio.com/Grove-GPS-Air530-p-4584.html). This sensor can connect to multiple GPS systems for a fast, accurate fix. The sensor is made of 2 parts - the core electronics of the sensor, and an external antenna connected by a thin wire to pick up the radio waves from the satellites. + +This is a UART sensor, so sends GPS data over UART. + +### Connect the GPS sensor + +The Grove GPS sensor can be connected to the Wio Terminal. + +#### Task - connect the GPS sensor + +Connect the GPS sensor. + +![A grove GPS sensor](../../../images/grove-gps-sensor.png) + +1. Insert one end of a Grove cable into the socket on the GPS sensor. It will only go in one way round. + +1. With the Wio Terminal disconnected from your computer or other power supply, connect the other end of the Grove cable to the left-hand side Grove socket on the Wio Terminal as you look at the screen. This is the socket closest to from the power button. + +![The grove GPS sensor connected to the left hand socket](../../../images/wio-gps-sensor.png) + +1. Position the GPS sensor so that the attached antenna has visibility to the sky - ideally next to an open window or outside. It's easier to get a clearer signal with nothing in the way of the antenna. + +1. You can now connect the Wio Terminal to your computer. + +1. The GPS sensor has 2 LEDs - a blue LED that flashes when data is transmitted, and a green LED that flashes every second when receiving data from satellites. Ensure the blue LED is flashing when you power up the Pi. After a few minutes the green LED will flash - if not, you may need to reposition the antenna. + +## Program the GPS sensor + +The Wio Terminal can now be programmed to use the attached GPS sensor. + +### Task - program the GPS sensor + +Program the device. + +1. Create a brand new Wio Terminal project using PlatformIO. Call this project `gps-sensor`. Add code in the `setup` function to configure the serial port. + +1. Add the following include directive to the top of the `main.cpp` file. This includes a header file with functions to configure the left-hand Grove port for UART. + + ```cpp + #include + ``` + +1. Below this, add the following line of code to declare a serial port connection to the UART port: + + ```cpp + static Uart Serial3(&sercom3, PIN_WIRE_SCL, PIN_WIRE_SDA, SERCOM_RX_PAD_1, UART_TX_PAD_0); + ``` + +1. You need to add some code to redirect some internal signal handlers to this serial port. Add the following code below the `Serial3` declaration: + + ```cpp + void SERCOM3_0_Handler() + { + Serial3.IrqHandler(); + } + + void SERCOM3_1_Handler() + { + Serial3.IrqHandler(); + } + + void SERCOM3_2_Handler() + { + Serial3.IrqHandler(); + } + + void SERCOM3_3_Handler() + { + Serial3.IrqHandler(); + } + ``` + +1. In the `setup` function below where the `Serial` port is configured, configure the UART serial port with the following code: + + ```cpp + Serial3.begin(9600); + + while (!Serial3) + ; // Wait for Serial3 to be ready + + delay(1000); + ``` + +1. Below this code in the `setup` function, add the following code to connect the Grove pin to the serial port: + + ```cpp + pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT); + ``` + +1. Add the following function before the `loop` function to send the GPS data to the serial monitor: + + ```cpp + void print_gps_data() + { + Serial.println(Serial3.readStringUntil('\n')); + } + ``` + +1. In the `loop` function, add the following code to read from the UART serial port and print the output to the serial monitor: + + ```cpp + while (Serial3.available() > 0) + { + print_gps_data(); + } + + delay(1000); + ``` + + This code reads from the UART serial port. The `readStringUntil` function reads up until a terminator character, in this case a new line. This will read a whole NMEA sentence (NMEA sentences are terminated with a new line character). All the while data can be read from the UART serial port, it is read and sent to the serial monitor via the `print_gps_data` function. Once no more data can be read, the `loop` delays for 1 second (1,000ms). + +1. Build and upload the code to the Wio Terminal. + +1. Once uploaded, you can monitor the GPS data using the serial monitor. + + ```output + > Executing task: platformio device monitor < + + --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time + --- More details at http://bit.ly/pio-monitor-filters + --- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 --- + --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + $GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67 + $GPGSA,A,1,,,,,,,,,,,,,,,*1E + $BDGSA,A,1,,,,,,,,,,,,,,,*0F + $GPGSV,1,1,00*79 + $BDGSV,1,1,00*68 + ``` + +> ๐Ÿ’ You can find this code in the [code-gps/wio-terminal](code-gps/wio-terminal) folder. + +๐Ÿ˜€ Your GPS sensor program was a success! diff --git a/3-transport/lessons/2-store-location-data/README.md b/3-transport/lessons/2-store-location-data/README.md new file mode 100644 index 00000000..fd2a00ab --- /dev/null +++ b/3-transport/lessons/2-store-location-data/README.md @@ -0,0 +1,438 @@ +# Store location data + +Add a sketchnote if possible/appropriate + +![Embed a video here if available](video-url) + +## Pre-lecture quiz + +[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/23) + +## Introduction + +In the last lesson, you learned how to use a GPS sensor to capture location data. To use this data to visualize the both the location of a truck laden with food, but also it's journey, it needs to be sent to an IoT service in the cloud, and then stored somewhere. + +In this lesson you will learn about the different ways to store IoT data, and learn how to store data from your IoT service using serverless code. + +In this lesson we'll cover: + +* [Structured and unstructured data](#structured-and-unstructured-data) +* [Send GPS data to an IoT Hub](#send-gps-data-to-an-iot-hub) +* [Handle GPS events using serverless code](#handle-gps-events-using-serverless-code) +* [Azure Storage Accounts](#azure-storage-accounts) +* [Connect your serverless code to storage](#connect-your-serverless-code-to-storage) + +## Structured and unstructured data + +Computer systems deal with data, and this data comes in all manner of different shapes and sizes. It can vary from single numbers, to large amounts of text, to videos and images, and to IoT data. Data can usually be divided into one of two categories - *structured* data and *unstructured* data. + +* **Structured data** is data with a well-defined, rigid structure that doesn't change and usually maps to tables of data with relationships. One example is a persons details including their name, date of birth and address. + +* **Unstructured data** is data without a well-defined, rigid structure, including data that can change structure frequently. One example is documents such as written documents or spreadsheets. + +โœ… Do some research: Can you think of some other examples of structured and unstructured data? + +> ๐Ÿ’ There is also semi-structured data that is structured but doesn't fit into fixed tables of data + +IoT data is usually considered to be unstructured data. + +Imagine you were adding IoT devices to a fleet of vehicles for a large commercial farm. You might want to use different devices for different types of vehicle. For example: + +* For farm vehicles like tractors you want GPS data to ensure they are working on the correct fields +* For delivery trucks transporting food to warehouses you want GPS data as well as speed and acceleration data to ensure the driver is driving safely, and drive identity and start/stop data to ensure drive compliance with local laws on working hours +* For refrigerated trucks you also want temperature data to ensure the food doesn't get too hot or cold and spoil in transit + +This data can change constantly. For example, if the IoT device is in a truck cab, then the data it sends may change as the trailer changes, for example only sending temperature data when a refrigerated trailer is used. + +This data varies from vehicle to vehicle, but it all gets sent to the same IoT service for processing. The IoT service needs to be able to process this unstructured data, storing it in a way that allows it to be searched or analyzed, but works with different structures to this data. + +### SQL vs NoSQL storage + +Databases are services that allow you to store and query data. Database come in two types - SQL and NoSQL + +#### SQL databases + +The first databases were Relational Database Management Systems (RDBMS), or relational database. These are also known as SQL databases after the Structured Query Language (SQL) used to interact with them to add, remove, update or query data. These database consist of a schema - a well-defined set of tables of data, similar to a spreadsheet. Each table has multiple named columns. When you insert data, you add a row to the table, putting values into each of the columns. This keeps the data in a very rigid structure - although you can leave columns empty, if you want to add a new column you have to do this on the database, populating values for the existing rows. These databases are relational - in that one table can have a relationship to another. + +![A relational database with the ID of the User table relating to the user ID column of the purchases table, and the ID of the products table relating to the product ID of the purchases table](../../../images/sql-database.png) + +For example, if you stored a users personal details in a table, you would have some kind of internal unique ID per user that is used in a row in a table that contains the users name and address. If you then wanted to store other details about that user, such as their purchases, in another table, you would have one column in the new table for that users ID. When you look up a user, you can use their ID to get their personal details from one table, and their purchases from another. + +SQL databases are ideal for storing structured data, and for when you want to ensure the data matches your schema. Some well known SQL databases are Microsoft SQL Server, MySQL, and PostgreSQL. + +โœ… If you haven't used SQL before, take a moment to read up on it on the [SQL page on Wikipedia](https://wikipedia.org/wiki/SQL). + +#### NoSQL database + +NoSQL databases are so called because they don't have the same rigid structure of SQL databases. There are also known as document databases as they can store unstructured data such as documents. + +> ๐Ÿ’ Despite their name, some NoSQL databases allow you to use SQL to query the data. + +![Documents in folders in a NoSQL database](../../../images/noqsl-database.png) + +NoSQL database do not have a pre-defined schema that limits how data is stored, instead you can insert any unstructured data, usually using JSON documents. These documents can be organized into folders, similar to files on your computer. Each document can have different fields from other documents - for example if you were storing IoT data from your farm vehicles, some may have fields for accelerometer and speed data, others may have fields for the temperature in the trailer. If you were to add a new truck type, such as one with built in scales to track the weight of produce carried, then your IoT device could add this new field and it could be stored without any changes to the database. + +Some well known NoSQL databases include Azure CosmosDB, MongoDB, and CouchDB. + +In this lesson, you will be using NoSQL storage to store IoT data. + +## Send GPS data to an IoT Hub + +In the last lesson you captured GPS data from a GPS sensor connected to your IoT device. To store this IoT data in the cloud, you need to send it to an IoT service. Once again, you will be using Azure IoT Hub, the same IoT cloud service you used in the previous project. + +![Sending GPS telemetry from an IoT device to IoT Hub](../../../images/gps-telemetry-iot-hub.png) + +***Sending GPS telemetry from an IoT device to IoT Hub. GPS by mim studio / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +### Task - send GPS data to an IoT Hub + +1. Create a new IoT Hub using the free tier. + + > โš ๏ธ You can refer to [the instructions for creating an IoT Hub from project 2, lesson 4 if needed](../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md#create-an-iot-service-in-the-cloud). + + Remember to create a new Resource Group. Name the new Resource Group `gps-sensor`, and the new IoT Hub a unique name based on `gps-sensor`, such as `gps-sensor-`. + + > ๐Ÿ’ If you still have your IoT Hub from the previous project, you can re-use it. Remember to use the name of this IoT Hub and the Resource Group it is in when creating other services. + +1. Add a new device to the IoT Hub. Call this device `gps-sensor`. Grab the connection string for the device. + +1. Update your device code to send the GPS data to the new IoT Hub using the device connection string from the previous step. + + > โš ๏ธ You can refer to [the instructions for connecting your device to an IoT from project 2, lesson 4 if needed](../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md#connect-your-device-to-the-iot-service). + +1. When you send the GPS data, do it as JSON in the following format: + + ```json + { + "gps" : + { + "lat" : , + "lon" : + } + } + ``` + +1. Send GPS data every minute so you don't use up your daily message allocation. + +If you are using the Wio Terminal, remember to add all the necessary libraries, and set the time using an NTP server. Your code will also need to ensure that it has read all the data from the serial port before sending the GPS location, using the existing code from the last lesson. Use the following code to construct the JSON document: + +```cpp +DynamicJsonDocument doc(1024); +doc["gps"]["lat"] = gps.location.lat(); +doc["gps"]["lon"] = gps.location.lng(); +``` + +If you are using a Virtual IoT device, remember to install all the needed libraries using a virtual environment. + +For both the Raspberry Pi and Virtual IoT device, use the existing code from the last lesson to get the latitude and longitude values, then send them in the correct JSON format with the following code: + +```python +message_json = { "gps" : { "lat":lat, "lon":lon } } +print("Sending telemetry", message_json) +message = Message(json.dumps(message_json)) +``` + +> ๐Ÿ’ You can find this code in the [code/wio-terminal](code/wio-terminal), [code/pi](code/pi) or [code/virtual-device](code/virtual-device) folder. + +Run your device code and ensure messages are flowing into IoT Hub using the `az iot hub monitor-events` CLI command. + +## Handle GPS events using serverless code + +Once data is flowing into your IoT Hub, you can write some serverless code to listen for events published to the Event-Hub compatible endpoint. + +![Sending GPS telemetry from an IoT device to IoT Hub, then to Azure Functions via an event hub trigger](../../../images/gps-telemetry-iot-hub-functions.png) + +***Sending GPS telemetry from an IoT device to IoT Hub, then to Azure Functions via an event hub trigger. GPS by mim studio / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +### Task - handle GPS events using serverless code + +1. Create an Azure Functions app using the Azure Functions CLI. Use the Python runtime, and create it in a folder called `gps-trigger`, and use the same name for the Functions App project name. Make sure you create a virtual environment to use for this. + + > โš ๏ธ You can refer to [the instructions for creating an Azure Functions Project from project 2, lesson 5 if needed](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#create-a-serverless-application). + +1. Add an IoT Hub event trigger that uses the IoT Hub's Event Hub compatible endpoint. + + > โš ๏ธ You can refer to [the instructions for creating an IoT Hub event trigger from project 2, lesson 5 if needed](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#create-an-iot-hub-event-trigger). + +1. Set the Event Hub compatible endpoint connection string in the `local.settings.json` file, and use the key for that entry in the `function.json` file. + +1. Use the Azurite app as a local storage emulator + +Run your functions app to ensure it is receiving events from your GPS device. Make sure your IoT device is also running and sending GPS data. + +```output +Python EventHub trigger processed an event: {"gps": {"lat": 47.73481, "lon": -122.25701}} +``` + +## Azure Storage Accounts + +![The Azure Storage logo](../../../images/azure-storage-logo.png) + +Azure Storage Accounts is a general purpose storage service that can store data in a variety of different ways. You can store data as blobs, in queues, in tables, or as files, and all at the same time. + +### Blob storage + +The word *Blob* means binary large objects, but has become the term for any unstructured data. You can store any data in blob storage, from JSON documents containing IoT data, to image and movie files. Blob storage has the concept of *containers*, named buckets that you can store data in, similar to tables in a relational database. These containers can have one or more folders to store blobs, and each folder can contain other folders, similar to how files are stored on your computer hard disk. + +You will use blob storage in this lesson to store IoT data. + +โœ… Do some research: Read up on [Azure Blob Storage](https://docs.microsoft.com/azure/storage/blobs/storage-blobs-overview?WT.mc_id=academic-17441-jabenn) + +### Table storage + +Table storage allows you to store semi-structured data. Table storage is actually a NoSQL database, so doesn't require a defined set of tables up front, but is designed to store data in one or more tables, with unique keys to define each row. + +โœ… Do some research: Read up on [Azure Table Storage](https://docs.microsoft.com/azure/storage/tables/table-storage-overview?WT.mc_id=academic-17441-jabenn) + +### Queue storage + +Queue storage allows you to store messages of up to 64KB in size in a queue. You can add messages to the back of the queue, and read them off the front. Queues store messages indefinitely as long as there is still storage space, so allows messages to be stored long term. then read off when needed. For example, if you wanted to run a monthly job to process GPS data you could add it to a queue every day for a month, then at the end of the month process all the messages off the queue. + +โœ… Do some research: Read up on [Azure Queue Storage](https://docs.microsoft.com/azure/storage/queues/storage-queues-introduction?WT.mc_id=academic-17441-jabenn) + +### File storage + +File storage is storage of files in the cloud, and any apps or devices can connect using industry standard protocols. You can write files to file storage, then mount it as a drive on your PC or Mac. + +โœ… Do some research: Read up on [Azure File Storage](https://docs.microsoft.com/azure/storage/files/storage-files-introduction?WT.mc_id=academic-17441-jabenn) + +## Connect your serverless code to storage + +Your function app now needs to connect to blob storage to store the messages from the IoT Hub. There's 2 ways to do this: + +* Inside the function code, connect to blob storage using the blob storage Python SDK and write the data as blobs +* Use an output function binding to bind the return value of the function to blob storage and have the blob saved automatically + +In this lesson, you will use the Python SDK to see how to interact with blob storage. + +![Sending GPS telemetry from an IoT device to IoT Hub, then to Azure Functions via an event hub trigger, then saving it to blob storage](../../../images/save-telemetry-to-storage-from-functions.png) + +***Sending GPS telemetry from an IoT device to IoT Hub, then to Azure Functions via an event hub trigger, then saving it to blob storage. GPS by mim studio / Microcontroller by Template - all from the [Noun Project](https://thenounproject.com)*** + +The data will be saved as a JSON blob with the following format: + +```json +{ + "device_id": , + "timestamp" :