Problem statement
First of all , i am plant lover person , i have so many plants at my home on terrace , also many at my old village home.
So the Problem is when i am away from my home on vacation or at my home which is far away from my village home where we rarely goes in to twice or thrice in month , planting water daily is difficult due to it is far away, but at the same time i can not let my plant dry and run out of water and eventually death of it due to water need.
Solution
Using Microsoft Azure Function , Azure Table ,Xamarin Cross Platform Mobile App (Uwp , Ios , Android ) And Arduino.
So solution is to plant my water i will use Arduino with Moisture sensor which will send data of moisture to my Azure Function which is Http Trigger function , which will have rich data in graph with the moisture values as well as history of the particular day when motor starts and ends .
Arduino has motor with connected to my water tank or any water source which can deliver the need of water to the plant for month or days
Based on the Mode Automatic or Manual we can perform watering of the plant , in the Auto Mode Arduino will start pumping water to perform watering the plant when soil moisture level goes beyond the particular plant's water need and stops when it has enough moisture , in Manual Mode we can control watering from anywhere remotely via Mobile app or on PC with UWP App
so it solves the problem of physical presence at particular location to water the plant , it's all Mobile app or your Windows 10 PC can do that job 😃😀🍸
Let's Look into Technical details and Implementation of Solution
You need following software/ Azure Account , please find details below
Azure Account :- get azure account by clicking on following link
https://azure.microsoft.com/en-us/free/
There is free credit for students and 200 usd credit if you want to get started to Azure
https://azure.microsoft.com/en-us/free/students/
Visual Studio 2019 Community
https://visualstudio.microsoft.com/downloads/
Community edition is free for student and open source contributor for non commercial use.
Visual Studio Code (Optional if you have Visual Studio 2019 for Azure Function Development)
https://code.visualstudio.com/download
Azure Function, Xamarin Mobile Development for visual studio :- Please install Xamarin , azure function and .net Core from check box while installing visual studio , if you have already installed and missing Xamarin or azure function please use Modify option in visual studio, please check below image for required part of installation
Water My Plant has 2 projects as below
Azure Function with HTTP Trigger
Xamarin App
1. WaterThePlant Azure Function with HTTP Trigger :
if you are not aware what is azure function is all about then visit link below from Microsoft Azure and Docs
https://azure.microsoft.com/en-in/services/functions/
https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview
it stats as below
"Develop more efficiently with Functions, an event-driven serverless compute platform that can also solve complex orchestration problems. Build and debug locally without additional setup, deploy and operate at scale in the cloud and integrate services using triggers and bindings."
In general server less Azure Function developer can focus on the business logic and does not need to worry about the underlying server infrastructure , there is ready server to execute code managed by Azure.
so arduino will make request to our azure function with moisture level and motor state .
Azure Function will insert data in to Azure table also it will maintain state of the motor status and few other parameters, based on that it will get values which will update the arduino to start and stop watering the plant.
Start visual Studio 2019 , New Project then in search box enter Azure Function as below snap
Click on next enter name as watermyplant , select http trigger as shown in below snap
right hand side of it we can see storage emulator click on browse if you have azure storage account (in my case i have created azure storage account so i will select from my existing list , you can create new storage account or use storage emulator to test it locally ) and as default keep authorization level as function so we need to pass the function key as authentication to run our function in azure as valid user then click on create.
or if you do not have storage account click on + icon and add storage account
Default Function 1 will be created replace it with below code
WaterThePlant.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
namespace WaterThePlant
{
public static class WaterThePlant
{
public const string MOTOR_ON = "MOTOR_ON";
public const string MOTOR_OFF = "MOTOR_OFF";
public const string waterdetails = "waterdetails";
public static bool AutoMode = true;
public static string Manual = "Manual";
public static string Auto = "Auto";
public const string seprator = "#";
private const string TableName = "PlantWateringDeatails";
public static string CurrentMotorState { get; set; }
public static int currentMoisturelevel { get; set; }
private static TimeZoneInfo INDIAN_ZONE = TimeZoneInfo.FindSystemTimeZoneById("India Standard Time");
[FunctionName("WaterThePlant")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
GETRequestData getdata = new GETRequestData();
if (int.TryParse(req.Query["moisturelevel"], out int mstlvl))
{
getdata.moisturelevel = mstlvl;
}
if (bool.TryParse(req.Query["automode"], out bool atmd))
{
getdata.automode = atmd;
AutoMode = atmd;
}
if (bool.TryParse(req.Query["getconfig"], out bool gtcnfg))
{
getdata.getconfig = gtcnfg;
}
if (bool.TryParse(req.Query["getHistory"], out bool gthty))
{
getdata.getHistory = gthty;
}
if (bool.TryParse(req.Query["reportMoistureLevel"], out bool rptmstlvl))
{
getdata.reportMoistureLevel = rptmstlvl;
}
if (int.TryParse(req.Query["motorstate"], out int mtrstt))
{
getdata.motorstate = mtrstt;
}
if (getdata.getconfig)
{
List<PlantWateringDeatails> wateringdetailsfortheday = await GetPlantWateringDeatailsAsync();
return new OkObjectResult($"{CurrentMotorState}{seprator}{AutoMode}{seprator}{currentMoisturelevel}{seprator}{JsonConvert.SerializeObject(wateringdetailsfortheday)}");
}
else if (getdata.getHistory)
{
List<PlantWateringDeatails> wateringdetailsfortheday = await GetPlantWateringDeatailsAsync();
return new OkObjectResult($"{JsonConvert.SerializeObject(wateringdetailsfortheday)}");
}
else
{
if (getdata.moisturelevel != 0)
{
currentMoisturelevel = getdata.moisturelevel;
}
switch (getdata.motorstate)
{
case 0:
CurrentMotorState = MOTOR_OFF;
break;
case 1:
CurrentMotorState = MOTOR_ON;
break;
}
if (AutoMode)
{
if (getdata.reportMoistureLevel)
{
await InsertWateringDeatilsTOAzureTable(currentMoisturelevel, $"{CurrentMotorState} due to moisture level is {currentMoisturelevel}", log, true);
}
else
{
await InsertWateringDeatilsTOAzureTable(currentMoisturelevel, $"{CurrentMotorState} due to moisture level is {currentMoisturelevel}", log);
}
return new OkObjectResult($"{Auto}{seprator}{CurrentMotorState}{seprator}{currentMoisturelevel}");
}
else
{
if (getdata.reportMoistureLevel)
{
await InsertWateringDeatilsTOAzureTable(currentMoisturelevel, $"{CurrentMotorState} due to moisture level is {currentMoisturelevel}", log, true);
}
else
{
await InsertWateringDeatilsTOAzureTable(currentMoisturelevel, $"{CurrentMotorState} due to moisture level is {currentMoisturelevel}", log);
}
return new OkObjectResult($"{Manual}{seprator}{CurrentMotorState}{seprator}{currentMoisturelevel}");
}
}
}
public static async Task<bool> InsertWateringDeatilsTOAzureTable(int moisturelevel, string message, ILogger log, bool ReportMoistureLevel = false)
{
try
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("enter correct connection string value here");
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference(TableName);
//can be used in situation if you are not sure table exist , in my i created table so i do not need to check this
//await table.CreateIfNotExistsAsync();
PlantWateringDeatails details;
if (ReportMoistureLevel)
{
details = new PlantWateringDeatails($"waterdetails{DateTime.Now:dd-MM-yyyy}", $"myplant{DateTime.Now:dd-MM-yyyy-HH-mm-ss}");
}
else
{
details = new PlantWateringDeatails(waterdetails, $"myplant{DateTime.Now:dd-MM-yyyy-HH-mm-ss}");
}
details.PlantingeTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, INDIAN_ZONE);
details.Message = message;
details.MoisuteLevel = moisturelevel;
TableOperation insertOperation = TableOperation.Insert(details);
var insertoperationresult = await table.ExecuteAsync(insertOperation);
var sts = insertoperationresult.HttpStatusCode;
return true;
}
catch (Exception ex)
{
log.LogError(ex.ToString());
return default;
}
}
public static async Task<List<PlantWateringDeatails>> GetPlantWateringDeatailsAsync(bool GetHistory = false)
{
try
{
List<PlantWateringDeatails> PlantWateringDeatailsrecords = new List<PlantWateringDeatails>();
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("enter correct connection string value here");
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable _linkTable = tableClient.GetTableReference(nameof(PlantWateringDeatails));
TableQuery<PlantWateringDeatails> query;
if (GetHistory)
{
query = new TableQuery<PlantWateringDeatails>().Take(25).Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, $"waterdetails"));
}
else
{
query = new TableQuery<PlantWateringDeatails>().Take(12).Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, $"waterdetails{DateTime.Now:dd-MM-yyyy}"));
}
TableContinuationToken token = null;
do
{
TableQuerySegment<PlantWateringDeatails> resultSegment = await _linkTable.ExecuteQuerySegmentedAsync(query, token).ConfigureAwait(false);
token = resultSegment.ContinuationToken;
foreach (var entity in resultSegment.Results)
{
PlantWateringDeatails _summary = new PlantWateringDeatails
{
Message = entity.Message,
MoisuteLevel = entity.MoisuteLevel,
PlantingeTime = entity.PlantingeTime
};
PlantWateringDeatailsrecords.Add(_summary);
}
} while (token != null);
return PlantWateringDeatailsrecords;
}
catch (Exception exp)
{
Debug.Write(exp);
return default;
}
}
}
}
PlantWateringDeatails.cs
using System;
using Microsoft.WindowsAzure.Storage.Table;
namespace WaterThePlant
{
public class PlantWateringDeatails : TableEntity
{
public PlantWateringDeatails()
{
}
public PlantWateringDeatails(string skey, string srow)
{
this.PartitionKey = skey;
this.RowKey = srow;
}
public DateTime PlantingeTime { get; set; }
public int MoisuteLevel { get; set; }
public string Message { get; set; }
}
}
GETRequestData.cs
namespace WaterThePlant
{
public class GETRequestData
{
public int moisturelevel { get; set; }
public bool automode { get; set; } = true;
public int motorstate { get; set; }
public bool getconfig { get; set; }
public bool reportMoistureLevel { get; set; }
public bool getHistory { get; set; }
}
}
this code uses nuget package which can be installed as shown below snap
search for below nuget packages and install for azure function project.
Microsoft.WindowsAzure.Storage
Microsoft.WindowsAzure.Storage.Table
That's all for azure function by adding this code in to the project we will have Azure Function ready to serve our need.
2.Xamarin App WaterMyPlant (UWP , Android , IOS)
if you are new to Xamarin , click below link for the getting started and learn more about it.
https://docs.microsoft.com/en-us/xamarin/get-started/what-is-xamarin
https://docs.microsoft.com/en-us/xamarin/get-started/
Right click on solution explorer click on new project as shown in below snap
Select Xamarin Forms Mobile App
click on next and enter project name as WaterMyPlant click on next , it will show the template type we are going to select Tabbed one but you can choose based o your need, then click on create.
Expand the views folder and delete all existing views created by default
and right click views folder and add new content page shown as below
Replace the content with below code
MyPlantPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WaterMyPlant.Views.MyPlantPage"
xmlns:forms="clr-namespace:Microcharts.Forms;assembly=Microcharts.Forms"
xmlns:vm="clr-namespace:WaterMyPlant.ViewModels"
Title="{Binding Title}" >
<ContentPage.BindingContext>
<vm:MyPlantPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Reload" Command="{Binding ReloadCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="Accent">#66c1ac</Color>
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackLayout BackgroundColor="{StaticResource Accent}" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<StackLayout Orientation="Horizontal" Padding="10" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Label Text="Motor Status" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"></Label>
<Label Text="{Binding CurrentMotorState}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" FontAttributes="Bold" ></Label>
<Label Text="Moisture Level" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"></Label>
<Label Text="{Binding CurrentMoisturelevel}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" FontAttributes="Bold" ></Label>
</StackLayout>
</StackLayout>
<ScrollView Grid.Row="1">
<StackLayout Orientation="Vertical" Padding="10" Spacing="10">
<Label Text="Auto Watering Mode" HorizontalOptions="FillAndExpand"></Label>
<Switch IsToggled="{Binding Automodeenabled}" />
<StackLayout IsVisible="{Binding ShowManualMode}" Orientation="Horizontal">
<Button Text="Start Motor" x:Name="btnsave"
Command="{Binding StartMotorCommand}"
BackgroundColor="{StaticResource Accent}"
TextColor="White" HorizontalOptions="FillAndExpand"></Button>
<Button Text="Stop Motor" x:Name="StopMotor"
Command="{Binding StopMotorCommand}"
BackgroundColor="{StaticResource Accent}"
TextColor="White" HorizontalOptions="FillAndExpand"></Button>
</StackLayout>
<forms:ChartView x:Name="MyLineChart" Chart="{Binding lineChart}" HeightRequest="250" />
</StackLayout>
</ScrollView>
</Grid>
</ContentPage>
Add another content page named as HistoryPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WaterMyPlant.Views.HistoryPage"
Title="{Binding Title}"
xmlns:local="clr-namespace:WaterMyPlant.ViewModels"
xmlns:model="clr-namespace:WaterMyPlant.Models"
x:Name="BrowseItemsPage">
<!--<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddItemCommand}" />
</ContentPage.ToolbarItems>-->
<!--
x:DataType enables compiled bindings for better performance and compile time validation of binding expressions.
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/data-binding/compiled-bindings
-->
<RefreshView x:DataType="local:HistoryViewModel" Command="{Binding LoadItemsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<CollectionView x:Name="ItemsListView"
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:PlantWateringDeatails">
<Label Text="{Binding Message}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Label Text="{Binding PlantingeTime}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</ContentPage>
HistoryPage.xaml.cs, please see below code which has view model binding
BindingContext = _viewModel = new HistoryViewModel();
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WaterMyPlant.Models;
using WaterMyPlant.ViewModels;
using WaterMyPlant.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace WaterMyPlant.Views
{
public partial class HistoryPage : ContentPage
{
HistoryViewModel _viewModel;
public HistoryPage()
{
InitializeComponent();
BindingContext = _viewModel = new HistoryViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
}
Now go to viewmodels folder and right click on folder and add new class with MyPlantPageViewModel.cs name and replace the below content
also add nuget packages listed below , from the refrence snap to add nuget package
Microcharts
Newtonsoft.Json
RestSharp
SkiaSharp
using Microcharts;
using Newtonsoft.Json;
using RestSharp;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using WaterMyPlant.Models;
using Xamarin.Forms;
namespace WaterMyPlant.ViewModels
{
public class MyPlantPageViewModel : BaseViewModel
{
public const string functionurl = "https://enter correct url here of azure function.azurewebsites.net/api/WaterThePlant?code=enter correct code here";
private string currentMotorState;
public string CurrentMotorState
{
get => currentMotorState;
set => SetProperty(ref currentMotorState, value);
}
private int currentMoisturelevel;
public int CurrentMoisturelevel
{
get => currentMoisturelevel;
set => SetProperty(ref currentMoisturelevel, value);
}
private bool automodeenabled;
public bool Automodeenabled
{
get => automodeenabled;
set
{
SetProperty(ref automodeenabled, value);
ShowManualMode = !automodeenabled;
if (automodeenabled)
{
SetConfigAutoMode(automodeenabled);
}
}
}
private bool showmanualmode;
public bool ShowManualMode
{
get => showmanualmode;
set => SetProperty(ref showmanualmode, value);
}
private List<ChartEntry> entry;
public List<ChartEntry> ChartEntry
{
get => entry;
set => SetProperty(ref entry, value);
}
public List<string> chartcolor { get; set; } = new List<string>();
private LineChart chart;
public LineChart lineChart
{
get => chart;
set => SetProperty(ref chart, value);
}
internal const string Separator = "#";
internal const string getconfig = "getconfig=true";
public const string MOTOR_ON = "MOTOR_ON";
public const string MOTOR_OFF = "MOTOR_OFF";
public const string waterdetails = "waterdetails";
public static bool AutoMode = true;
public static string Manual = "Manual";
public static string Auto = "Auto";
public MyPlantPageViewModel()
{
Title = "Water MyPlant";
Task.Run(async () => await GetCurrentConfigAsync(getconfig).ConfigureAwait(false));
StartMotorCommand = new Command(async () => await PerformPostRequestAsync(false, MOTOR_ON));
StopMotorCommand = new Command(async () => await PerformPostRequestAsync(false, MOTOR_OFF));
ReloadCommand = new Command(async () => await GetCurrentConfigAsync(getconfig).ConfigureAwait(false));
ModeChangedCommand = new Command(() => { ShowManualMode = !Automodeenabled; });
}
private async Task GetCurrentConfigAsync(string querystringandvalue)
{
try
{
lineChart = null;
ChartEntry?.Clear();
var client = new RestClient($"{functionurl}&{querystringandvalue}");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
IRestResponse response = await client.ExecuteAsync(request);
var data = response.Content.Split(new string[] { Separator }, StringSplitOptions.None);
CurrentMotorState = data.ElementAt(0).Replace("\"", "");
List<PlantWateringDeatails> wateringDeatails = null;
if (bool.TryParse(data.ElementAt(1), out bool val))
{
Automodeenabled = val;
showmanualmode = !val;
}
if (int.TryParse(data.ElementAt(2).Replace("\"", ""), out int moistval))
{
CurrentMoisturelevel = moistval;
}
if (!string.IsNullOrEmpty(data.ElementAt(3)))
{
var getjsondata = data.ElementAtOrDefault(3).Replace("\\", "").Replace("]\"", "]");
wateringDeatails = JsonConvert.DeserializeObject<List<PlantWateringDeatails>>(getjsondata);
}
if (ChartEntry == null)
{
ChartEntry = new List<ChartEntry>();
entry = new List<ChartEntry>();
}
AddColorToList();
for (int i = 0; i < wateringDeatails.Count; i++)
{
entry.Add(new ChartEntry(wateringDeatails[i].MoisuteLevel)
{
Label = wateringDeatails[i].PlantingeTime.Hour.ToString(),
ValueLabel = $"{wateringDeatails[i].MoisuteLevel}",
Color = SKColor.Parse(GetCurrentColor(i)),
});
}
lineChart = new LineChart { Entries = ChartEntry };
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
private void SetConfigAutoMode(bool automode)
{
var client = new RestClient($"{functionurl}&{nameof(automode)}={automode}");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
}
public ICommand ModeChangedCommand { get; }
public ICommand StartMotorCommand { get; }
public ICommand StopMotorCommand { get; }
public ICommand ReloadCommand { get; }
public async Task PerformPostRequestAsync(bool automode, string motorstate)
{
try
{
int mtrval;
if (motorstate == MOTOR_ON)
{
mtrval = 1;
}
else
{
mtrval = 0;
}
var client = new RestClient($"{functionurl}&{nameof(automode)}={automode}&{nameof(motorstate)}={mtrval}");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
IRestResponse response = await client.ExecuteAsync(request);
await GetCurrentConfigAsync(getconfig);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void AddColorToList()
{
chartcolor.AddRange(new string[] {"#FF0033",
"#FF8000",
"#FFE600",
"#FFE400",
"#FFA100",
"#1AB34D",
"#1A66FF",
"#801AB3",
"#991AFC",
"#1A69FF",
"#FE0243",
"#823AB3" });
}
public string GetCurrentColor(int i)
{
return chartcolor.ElementAtOrDefault(i);
}
}
}
HistoryViewModel.xaml.cs
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using WaterMyPlant.Models;
using WaterMyPlant.Views;
using Xamarin.Forms;
namespace WaterMyPlant.ViewModels
{
public class HistoryViewModel : BaseViewModel
{
List<PlantWateringDeatails> items;
public ObservableCollection<PlantWateringDeatails> Items { get; }
public Command LoadItemsCommand { get; }
public HistoryViewModel()
{
Title = "History";
Items = new ObservableCollection<PlantWateringDeatails>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Items.Clear();
items = await GetHistoryAsync().ConfigureAwait(false);
if (items != null)
{
foreach (var item in items)
{
Items.Add(item);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
private async Task<List<PlantWateringDeatails>> GetHistoryAsync()
{
try
{
var client = new RestClient($"{MyPlantPageViewModel.functionurl}&gethistory=true");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
IRestResponse response = await client.ExecuteAsync(request);
var apidata = response.Content.Replace("\\", "").Replace("]\"", "]").Replace("\"[", "[");
var data = JsonConvert.DeserializeObject<List<PlantWateringDeatails>>(apidata);
items = data;
return items;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
return null;
}
}
public void OnAppearing()
{
IsBusy = true;
}
}
}
GETRequestData.cs
namespace WaterMyPlant.ViewModels
{
public class GETRequestData
{
public string moisturelevel { get; set; }
public bool automode { get; set; }
public string motorstate { get; set; }
}
}
BaseViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using WaterMyPlant.Models;
using WaterMyPlant.Services;
using Xamarin.Forms;
namespace WaterMyPlant.ViewModels
{
public class BaseViewModel : INotifyPropertyChanged
{
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Go to Models folder and add PlantWateringDeatails.cs
and add refrence to below nuget package
Microsoft.WindowsAzure.Storage.Table
using Microsoft.WindowsAzure.Storage.Table;
using System;
namespace WaterMyPlant.Models
{
public class PlantWateringDeatails:TableEntity
{
public int MoisuteLevel { get; set; }
public DateTime PlantingeTime { get; set; }
public string Message { get; set; }
}
}
AppShell.xaml
<?xml version="1.0" encoding="UTF-8"?>
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WaterMyPlant.Views"
Title="WaterMyPlant"
x:Class="WaterMyPlant.AppShell">
<!--
The overall app visual hierarchy is defined here, along with navigation.
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/
-->
<Shell.Resources>
<ResourceDictionary>
<Style x:Key="BaseStyle" TargetType="Element">
<Setter Property="Shell.BackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="Shell.ForegroundColor" Value="White" />
<Setter Property="Shell.TitleColor" Value="White" />
<Setter Property="Shell.DisabledColor" Value="#B4FFFFFF" />
<Setter Property="Shell.UnselectedColor" Value="#95FFFFFF" />
<Setter Property="Shell.TabBarBackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="Shell.TabBarForegroundColor" Value="White"/>
<Setter Property="Shell.TabBarUnselectedColor" Value="#95FFFFFF"/>
<Setter Property="Shell.TabBarTitleColor" Value="White"/>
</Style>
<Style TargetType="TabBar" BasedOn="{StaticResource BaseStyle}" />
<Style TargetType="FlyoutItem" BasedOn="{StaticResource BaseStyle}" />
</ResourceDictionary>
</Shell.Resources>
<TabBar>
<ShellContent Title="MyPlant" Icon="icon_about.png" Route="MyPlantPage" ContentTemplate="{DataTemplate local:MyPlantPage}" />
<ShellContent Title="History" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:HistoryPage}" />
</TabBar>
<!--
If you would like to navigate to this content you can do so by calling
await Shell.Current.GoToAsync("//LoginPage");
-->
</Shell>
Now you need to configure Arduino with motor pump , soil moisture sensor and required power supply as below circuit diagram .
also you need IDE For writing the code and flashing the rom with the below code, you need to deploy the code once you enter the code below
https://www.arduino.cc/en/software
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <SoftwareSerial.h>
#include <WiFiClientSecure.h>
char ssid[] = "enter ssid";
char password[] = "enter pass";
String API_CALL="http://your function url.azurewebsites.net/api/WaterThePlant?code=enter correct auth code";
const int SOIL_PIN=A0;
const int LED_PIN=D2;
const int MOTOR_PIN=D3;
const int MOTOR_THREDSHOLD_PER=50;
const int MAX_MOTOR_THREDSHOLD_PER=60;
int sensorValue;
int sensorPer;
int isMotorStarted=0;
String mode="Auto";
String motorMode="";
SoftwareSerial sender(D5,D6);
boolean isWifiLoded=false;
void setupWifi(){
WiFi.mode(WIFI_OFF); //Prevents reconnection issue (taking too long to connect)
delay(1000);
WiFi.mode(WIFI_STA); //This line hides the viewing of ESP as wifi hotspot
WiFi.begin(ssid, password); //Connect to your WiFi router
Serial.println("");
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.print(".");
}
//If connection successful show IP address in serial monitor
Serial.println("");
Serial.println("Connected to Network/SSID");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
String networkCall(int sensorPer){
HTTPClient http;
String postData;
postData = "&moisturelevel="+String(sensorPer)+"&motorstate="+String(isMotorStarted); //reportMoistureLevel=true
http.begin(API_CALL+postData); //Specify request destination ,"8b 00 83 0b cc f2 46 f7 96 d3 8e b5 e1 2f cf bd 1d 3a 61 50"
http.addHeader("Content-Type", "application/x-www-form-urlencoded"); //Specify content-type header
Serial.println("Request PayLoad "+postData);
//int httpCode = http.POST(postData); //Send the request
int httpCode = http.GET();
String payload = http.getString(); //Get the response payload
Serial.println("Response Code "); //Print HTTP return code
Serial.println(httpCode);
Serial.println("Response :");
Serial.println(payload);
http.end(); //Close connection
return payload;
}
void setup() {
Serial.begin(115200);
sender.begin(9600);
setupWifi();
pinMode(SOIL_PIN,INPUT);
pinMode(LED_PIN,OUTPUT);
pinMode(MOTOR_PIN,OUTPUT);
}
void startMotor(){
digitalWrite(LED_PIN,HIGH);
digitalWrite(MOTOR_PIN,LOW);
sender.println("motor=1");
Serial.println("Sending : Start Motor");
isMotorStarted=1;
}
void stopMotor(){
digitalWrite(LED_PIN,LOW);
digitalWrite(MOTOR_PIN,HIGH);
sender.println("motor=0");
Serial.println("Sending : Stop Motor");
isMotorStarted=0;
}
String getValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length()-1;
for(int i=0; i<=maxIndex && found<=index; i++){
if(data.charAt(i)==separator || i==maxIndex){
found++;
strIndex[0] = strIndex[1]+1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}
void loop() {
sensorValue=analogRead(SOIL_PIN);
sensorPer=map(sensorValue,1000,270,0,100);
Serial.println("Sensor Value : ");
Serial.println(sensorValue);
Serial.println("Sensor Percentage(%) : ");
Serial.println(sensorPer);
String resp=networkCall(sensorPer);
mode=getValue(resp,'#',0);
if(mode == "Auto"){
if(sensorPer<MOTOR_THREDSHOLD_PER){
Serial.println("Auto Mode : Start Motor as below threadsold");
startMotor();
}else if(sensorPer>MOTOR_THREDSHOLD_PER && sensorPer <=MAX_MOTOR_THREDSHOLD_PER){
Serial.println("Auto Mode : Start Motor as between threadsold");
startMotor();
}else{
Serial.println("Auto Mode : Stop Motor as enough moisture");
stopMotor();
}
}else if(mode=="Manual"){
motorMode=getValue(resp,'#',1);
if(motorMode=="MOTOR_ON"){
Serial.println("Manual Mode : Start Motor");
startMotor();
}else if(motorMode=="MOTOR_OFF"){
Serial.println("Manual Mode : Stop Motor");
stopMotor();
}
}
delay(2000);
}
Working App is available on Github via my public repo , you just need to set correct connection string values , please find it below, please find zip file(issue with commit so added zip file) and extract all code.
https://github.com/Hiteksha/watermyplant
I have created the video of the app working in action please have a look
Please also look at below snap of App running on my Android phone, My Iphone and my windows 10 pc
Xamarin App running on my Android Phone
Xamarin App running on my Iphone
Top comments (0)