DEV Community

Hiteksha
Hiteksha

Posted on • Updated on

💦Water My Plant🌱🌱 Smart Irrigation System

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

alt text

alt text

alt text

Water My Plant has 2 projects as below

  1. Azure Function with HTTP Trigger

  2. 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

alt text

Click on next enter name as watermyplant , select http trigger as shown in below snap

alt text

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.

alt text

or if you do not have storage account click on + icon and add storage account

alt text

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;
            }
        }




    }

}


Enter fullscreen mode Exit fullscreen mode

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; }

    }

}


Enter fullscreen mode Exit fullscreen mode

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; }
    }

}


Enter fullscreen mode Exit fullscreen mode

this code uses nuget package which can be installed as shown below snap
alt text

alt text

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

alt text

Select Xamarin Forms Mobile App
alt text

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.

alt text

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

alt text

enter the name as MyPlantPage
alt text

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>

Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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);
        }



    }
}


Enter fullscreen mode Exit fullscreen mode

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;

        }

    }
}

Enter fullscreen mode Exit fullscreen mode

GETRequestData.cs


namespace WaterMyPlant.ViewModels
{
    public class GETRequestData
    {
        public string moisturelevel { get; set; }

        public bool automode { get; set; }

        public string motorstate { get; set; }
    }
}

Enter fullscreen mode Exit fullscreen mode

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
    }
}


Enter fullscreen mode Exit fullscreen mode

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; }
    }
}

Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

Now you need to configure Arduino with motor pump , soil moisture sensor and required power supply as below circuit diagram .

Alt Text

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);

}



Enter fullscreen mode Exit fullscreen mode

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

https://youtu.be/8bnkAsXoLRE

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
alt text

Xamarin App running on my Iphone
alt text

Thank you .
Xamarin App running on my Windos 10 PC
alt text

alt text

Top comments (0)