CropCare's goal is to combat the growing impact urbanization has on the safety of the farming & agriculture world. Our application provides users (farm Technicians or Owners) with a centralized control & monitoring center for everything that is going on in their IoT container farms. The mobile application sends and receives data through Microsoft Azure which acts as an intermediary between our mobile application and the project back-end. The project back-end is developed in Python and hosted/ran off of a Rasperry Pi reTerminal that is repeatedly getting readings and states from the hardware it is connected with, ultimately sending it off to Microsoft Azure to then be received on the Mobile Application interface.
Developer | Contribution |
---|---|
Cristiano Fazi | Full-stack developer & team lead |
Kevin Baggott | Back-end & hardware developer |
Carson Spriggs-Audet | Full-stack developer & designer |
Important
It is important to know that there were no distinct set roles, the contributions above are what each person primarily focused on, but each team member evenly contributed in various ways.
In relation to the courses this project was made for, below are the technologies used;
Application Development 3:
- Development in .NET Maui
- Visual Studio 2022 Community Edition
Connected Objects:
- Python
- Azure IoT Tools
- Raspberry Pi
- Visual Studio Code
External:
- TinkerCAD for creating the model
- Google's model-viewer for displaying the exported CAD file
- GitHub Pages for hosting the 3D Model
![3D Model](https://private-user-images.githubusercontent.com/92652800/333997868-94c7410c-1eea-4078-bdac-dc9dc733ff1c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjU2NjI5MzUsIm5iZiI6MTcyNTY2MjYzNSwicGF0aCI6Ii85MjY1MjgwMC8zMzM5OTc4NjgtOTRjNzQxMGMtMWVlYS00MDc4LWJkYWMtZGM5ZGM3MzNmZjFjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MDYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTA2VDIyNDM1NVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTA1NGQxZGMzNTUyNjNlOTcwOTk5N2ZhZmNjNDIxYzRkNjk1NGNkYzUwZTM5Y2Q4MzZlMDE2NGY0YWQ5ZDRhYzImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.OjhJQHpUZRTCFx-wP_cYnUlPJHj39reAErlHqkjzqXE)
Tip
If you want to interact with the 3D model, please click the image above or you can follow this link.
If you are interested in the design of our app and code structure (i.e. our "blueprint" before we began developing), please visit this link, you will be met with a Google Doc with our detailed strategy.
If you are interested in viewing more details/more expansive versions of the information presented in this README (3D models, UML diagrams, wireframes), navigate to the "./Documents" path.
Below is an image displaying the functionality of each page within our mobile app (in light mode).
You need the following technology - .NET MAUI & an Android Emulator - A Raspberry Pi & a base hat - Various sensors & actuators (all noted below)
For .NET MAUI setup, you need to modify the appsettings.json to have the correct keys. Your appsettings.json should go in the project top level project directory. It should look like this:
{
"Settings": {
"FirebaseAuthorizedDomain": "my-firebase-app-d60e2.firebaseapp.com",
"FireBaseApiKey": "awdawdFWADSf32134dadwawda",
"FireBaseDatabaseURL": "https://my-firebase-app-d60e2-default-rtdb.firebaseio.com/",
"GoogleMapsAPIKey": "gdtrhrdffeff3fsdffasefsegjntsbgfawddwa",
"EventHubConnectionString": "Endpoint=sb:https://iothub-ns-my-app-59465961-6d1bd0200e.servicebus.windows.net/;SharedAccessKeyName=iothubowner;SharedAccessKey=0es2007K2ch9jfd6n9rDBX1kEIX8HkAc9AIoTHoehx0=;EntityPath=my-app",
"EventHubName": "my-app",
"IotHubConnectionString": "HostName=my-app.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=0es2007K2ch9jfd6n9rDBX1kEIX8HkAc9AIoTHoehx0="
}
}
In order to get a google maps api key, follow the intstructions here: Use API Keys
For the azure setup, you must create an iothub resource. To get the EventHubConnectionString and EventHubName you must go to the Build-in endpoints tab and you will see them there. As for the iothubconnection string you must go to the Shared access policies tab, click on iothubowner and copy the primary connection string from there. There firebase API keys are all obtained from the firebase console.
Once azure is set up, you must create a device, click on it and copy its primary key or connection string and put it in a .env in farm/ folder in this repo. You can use .env.example as an example of how your .env should look. In order to properly get readings from iot hub from within the app, when you create a farm you must enter the device_id that matches the correct device in Azure iot hub:
Follow the port guide below to sucessfully utilize the farm setup.
Sensor/Actuator | Port on Grove Base Hat | Port Type | Unit |
---|---|---|---|
Soil Moisture Sensor | 0 | PIN | ฮฉ |
Temperature/Humidity Sensor | 26 | PIN | Temp: ยฐC, Humi: % HR |
Water Level Sensor | 5 | PIN | water level |
Fan | 12 | PIN | N/A |
LED | 18 | PIN | N/A |
Follow the port guide below to sucessfully utilize the farm setup.
Sensor/Actuator | Port on Grove Base Hat | Port Type | Unit |
---|---|---|---|
Loudness Sensor | 2 | PIN | unitless |
Luminosity Sensor | N/A | BUS | nm |
Motion Sensor | 22 | PIN | N/A |
Vibration Sensor | N/A | PIN | N/A |
Servo | 16 | PIN | N/A |
Door Sensor | 24 | PIN | N/A |
Follow the port guide below to sucessfully utilize the farm setup.
Sensor/Actuator | Port on Grove Base Hat | Port Type | Unit |
---|---|---|---|
Accelerometer | N/A | BUS | ยฐ |
Buzzer | N/A | BUS | N/A |
GPS | /dev/ttyS0 | UART | N/A |
Direct Methods are used to control each actuator. We felt that it made the most sense since we control them with a method in our code to just invoke the same method with the parameters specified in the payload.
The target field must be set to fan and the value can be either on or off.
az iot hub invoke-device-method --hub-name cropcare --device-id {device_id} --method-name control_actuator --method-payload '{"target":"fan", "value": "on"}'
The target field must be set to buzzer and the value can be either on or off.
az iot hub invoke-device-method --hub-name cropcare --device-id {device_id} --method-name control_actuator --method-payload '{"target":"buzzer", "value": "on"}'
The target field must be set to servo and the value can be a float from -1 to 1 inclusively.
az iot hub invoke-device-method --hub-name cropcare --device-id {device_id} --method-name control_actuator --method-payload '{"target":"servo", "value": "1"}'
The target field must be set to led and the value can be either on or off.
az iot hub invoke-device-method --hub-name cropcare --device-id {device_id} --method-name control_actuator --method-payload '{"target":"led", "value": "on"}'
We can also get the state of any actuator we want without changing them by using the following command:
az iot hub invoke-device-method --hub-name cropcare --device-id {device_id} --method-name get_single_actuator_state --method-payload '{"target":"fan"}'
This will return a message resembling:
{ "payload": { "target": "fan", "value": "ON" }, "status": 200 }
To set telemetryInterval to 5 seconds:
az iot hub device-twin update -n cropcare -d {device_id} --desired '{"telemetryInterval": 5}'
Longitude and Latitude to determine GPS location:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'LONGITUDE', 'value': 152.408976, 'unit': 'ยฐ'}"
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'LATITUDE', 'value': 152.408976, 'unit': 'ยฐ'}"
Temperature of the container:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'TEMPERATURE', 'value': 40.34324, 'unit': 'ยฐC'}"
Humidity inside the container:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'HUMIDITY', 'value': 20.231, 'unit': '% HR'}"
Luminosity for detecting light levels:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'LUMINOSITY', 'value': 433, 'unit': 'lux'}"
Loudness inside the container:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'LOUDNESS', 'value': 200, 'unit': 'Db'}"
Moisture of the soil:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'MOISTURE', 'value': 232.34311, 'unit': 'ฮฉ'}"
Waterlevel:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'WATERLEVEL', 'value': 221, 'unit': 'ฮฉ'}"
Motion inside the container:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'MOTION', 'value': True, 'unit': ''}"
Vibration in the container:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'VIBRATION', 'value': False, 'unit': ''}"
Magnet for detecting if the door is closed:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'MAGNET', 'value': True, 'unit': ''}"
Pitch of the container:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'PITCH', 'value': 12.322, 'unit': 'ยฐ'}"
Roll of the container:
az iot device send-d2c-message -n {iothub_name} -d {device_id} --data "{'reading_type': 'ROLL', 'value': 56.7445, 'unit': 'ยฐ'}"
-
Monitoring
- Measure relative water levels
- Measure soil moisture levels
- Read fan state (on/off)
- Read light state (on/off)
- Collect container GPS Location
- Read container pitch and roll angles
- Read vibration levels
- Read buzzer state (On/Off)
- Read noise levels
- Read luminosity levels
- Read motion sensor state (motion detected)
- Read door-lock state (servo open/closed)
- Read door state
-
Remote Controls
- Control buzzer state (On/Off)
- Control door-lock state (servo open/closed)
- Control fan state (on/off)
- Control light state (on/off)
-
Azure IoT Setup
- Able to create containers
- Create azure keyvault to store connection strings
-
IoT Access
- Collect Telemetry data from Azure IoT hub
- Allow for D2C commands to alter computer unit (Raspberry Pi reTerminal)'s state
-
User Authentication
- Build a user login/signup page
- Create a user login system
- Create a user sign-up system (Considering the two account types)
- Implement a logout button
- Ensure app differentiates between account types
-
Supporting Infrastructure
- Create a flyout menu
- Create a logo for the application and company
- Access environment variables
-
Overview
- Owners are able to instantiate new farms
- Owners & Technicians are able to enter farms assosciated with their user ID
-
Dashboard
- Users are able to see 3 general health statuses (Plant, Security, GPS)
- Owners have the following cards; Plant, Security, GPS, Modify Technicians, Settings
- Technicians have the following cards; Plant & Security
-
Plant Monitoring Page
- Display all the data relating to the plants, such as moisture, and water levels through an interactive page
- Display health statuses according to current data
-
Security Page
- Display container state information
- Allow users to control systems of the container farm, such as the door
-
GPS Monitoring Page
- Fleet manager should see a map on their farm page with the actual farm location
-
Modify Technicians Page
- The owner of a farm is able to modify the technicians who are assigned to a farm
-
Farm Settings Page
- Owners can; change farm name, change twin patch interval, and the icon of their farm
-
Settings Page
- Design Settings Page
- Allow user to change account details
- Allow users to change colour theme
classDiagram
class User {
- List<string> farmKeys
+ PropertyChangedEventHandler PropertyChanged
+ string Key
+ string Email
+ string Name
+ bool IsOwner
+ List<string> FarmKeys
+ bool IsAssigned
+ User(string email, string name, bool isOwner)
}
class Farm {
+ PropertyChangedEventHandler PropertyChanged
+ string Key
+ string Name
+ string DeviceId
+ EventHubConsumerClient Consumer
- PlantController PlantController
- SecurityController SecurityController
- GeolocationController GeolocationController
+ Farm(string farmName, string deviceId)
}
class UserToFarm {
+ string Key
+ string UserId
+ string FarmId
+ UserToFarm(string userId, string farmId)
}
User "1" --> "0..*" UserToFarm
Farm "1" --> "0..*" UserToFarm
classDiagram
class BaseController {
- string[] _readingTypes
+ Dictionary<string, ObservableCollection<Reading>> Readings
+ bool ValidateReading(Reading reading)
+ virtual void AddReading(Reading reading)
+ BaseController(string[] readingTypes)
}
class PlantController {
- static readonly string[] _readingTypes
+ PropertyChangedEventHandler PropertyChanged
+ Reading Temperature
+ Reading Humidity
+ Reading Moisture
+ Reading WaterLevel
+ void ToggleFan()
+ void ToggleLed()
+ void AddReading(Reading reading)
+ PlantController()
+ string UpdateReadingHealthLabel(string sensorReading, char unitSymbol, double highThreshold, double lowThreshold)
+ string UpdateStateHealthLabel(string actuatorState)
}
class SecurityController {
- static readonly string[] _readingTypes
+ PropertyChangedEventHandler PropertyChanged
+ Reading Loudness
+ Reading Motion
+ Reading Vibration
+ Reading Luminosity
+ void ToggleDoorLock()
+ void ToggleDoorOpen()
+ void AddReading(Reading reading)
+ SecurityController()
+ string UpdateReadingHealthLabel(string sensorReading, char unitSymbol, double highThreshold, double lowThreshold)
+ string UpdateStateHealthLabel(string actuatorState)
}
class GeolocationController {
- static readonly string[] _readingTypes
+ PropertyChangedEventHandler PropertyChanged
+ Reading Latitude
+ Reading Longitude
+ Reading Pitch
+ Reading Roll
+ void ToggleBuzzer()
+ void AddReading(Reading reading)
+ GeolocationController()
}
BaseController <|-- PlantController
BaseController <|-- SecurityController
BaseController <|-- GeolocationController