aws-project
aws-project

AWS PROJECT | AgriAlert — Smart Weather Alert System for Farmers

Overview

AgriAlert is a real-time weather alert system for farmers. It lets farmers register with their details and receive automated email alerts based on live weather data using the OpenWeatherMap API.

Objectives

  • Enable farmers to register with name, email, location, and crop details.
  • Use OpenWeatherMap API to fetch live weather updates.
  • Detect harmful weather and send real-time alerts via email using Amazon SNS.
  • Automate daily weather checks using AWS EventBridge.
  • Build a user-friendly frontend using HTML + Bootstrap + Axios.

Setup Instructions

1. DynamoDB Table

Table Name: Farmers

Partition Key: email (String)

Other fields will be added automatically: namelocationcropTypefarmArea

Steps to Create DynamoDB Table:

  1. Navigate to DynamoDB Console: Open the AWS Management Console and search for “DynamoDB”.
  2. Create Table: Click on “Tables” in the left navigation pane, then click “Create table”.
  3. Configure Table Settings:
    • Table name: Enter Farmers
    • Partition key: Enter email
    • Data type: Select String
    • Leave other settings as default (e.g., default settings for read/write capacity, but you can choose On-demand for simpler management).
  4. Create: Click “Create table”.

2. Register Farmer Lambda Function

Lambda Name: register_farmer Runtime: Python 3.12

This Lambda function handles farmer registration, stores their details in DynamoDB, and subscribes them to an SNS topic for weather alerts.

Lambda Code:

Python

import json
import boto3
import os
from decimal import Decimal

dynamodb = boto3.resource('dynamodb')
sns = boto3.client('sns')

TABLE_NAME = os.environ['TABLE_NAME']
TOPIC_ARN = os.environ['TOPIC_ARN']

def lambda_handler(event, context):
    try:
        body = json.loads(event['body'])

        name = body['name']
        email = body['email']
        location = body['location']
        cropType = body['cropType']
        farmArea = Decimal(str(body['farmArea']))

        # Store data in DynamoDB
        table = dynamodb.Table(TABLE_NAME)
        table.put_item(Item={
            'email': email,
            'name': name,
            'location': location,
            'cropType': cropType,
            'farmArea': farmArea
        })

        # Subscribe to SNS Topic
        sns.subscribe(
            TopicArn=TOPIC_ARN,
            Protocol='email',
            Endpoint=email
        )

        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            'body': json.dumps({'message': f" Registered! Please confirm your email ({email}) to receive alerts."})
        }

    except Exception as e:
        return {
            'statusCode': 500,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            'body': json.dumps({'error': str(e)})
        }

IAM Policy for register_farmer Lambda:

This policy grants the Lambda function permissions to put items into the Farmers DynamoDB table and subscribe endpoints to the AgriWeatherTopic SNS topic.

Name: RegisterFarmerPolicy (or similar descriptive name)

JSON

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem"
      ],
      "Resource": "arn:aws:dynamodb:ap-south-1:8257xxxxxx:table/Farmers"
    },
    {
      "Effect": "Allow",
      "Action": [
        "sns:Subscribe"
      ],
      "Resource": "arn:aws:sns:ap-south-1:8257xxxxxx:AgriWeatherTopic"
    }
  ]
}

Note: Replace 8257xxxxxx with your actual AWS Account ID. You can find your account ID in the AWS Management Console.

Environment Variables for register_farmer Lambda:

These environment variables allow the Lambda function to dynamically retrieve the DynamoDB table name and SNS topic ARN.

KeyValue
REGIONap-south-1
TABLE_NAMEFarmers
TOPIC_ARNarn:aws:sns:ap-south-1:8257xxxxxx:AgriWeatherTopic

Note: Replace 8257xxxxxx with your actual AWS Account ID.

Steps to Create register_farmer Lambda:

  1. Navigate to Lambda Console: Open the AWS Management Console and search for “Lambda”.
  2. Create Function: Click “Create function”.
  3. Configure Function Settings:
    • Author from scratch: Selected by default.
    • Function name: Enter register_farmer.
    • Runtime: Select Python 3.12.
    • Architecture: x86_64 (default).
    • Execution role: Choose “Create a new role with basic Lambda permissions”. After creation, attach the RegisterFarmerPolicy to this new role.
  4. Create function: Click “Create function”.
  5. Add Code: In the “Code” tab, replace the default code with the provided register_farmer Lambda code.
  6. Configure Environment Variables: In the “Configuration” tab, go to “Environment variables” and add the keys and values as specified above.
  7. Save: Click “Deploy” to save your changes.

3. Weather Alert Lambda Function

Lambda Name: weather_alert Runtime: Python 3.12

This Lambda function scans the DynamoDB table for farmer details, fetches weather data from OpenWeatherMap, and sends alerts via SNS for bad weather conditions.

Lambda Code:

Python

import boto3
import urllib.request
import json
import os

dynamodb = boto3.resource('dynamodb')
sns = boto3.client('sns')
table = dynamodb.Table('Farmers') # Directly using table name as it's consistent

WEATHER_API_KEY = os.environ['WEATHER_API_KEY']
TOPIC_ARN = os.environ['TOPIC_ARN']

def lambda_handler(event, context):
    try:
        # 1. Scan DynamoDB Table
        response = table.scan()
        farmers = response['Items']

        # 2. Loop through each farmer and get weather data
        for farmer in farmers:
            location = farmer['location']
            name = farmer['name']
            crop = farmer['cropType']

            # Fetch weather from OpenWeather API
            # It's highly recommended to use Secrets Manager for API keys in production
            weather_url = f"https://api.openweathermap.org/data/2.5/weather?q={location}&appid={WEATHER_API_KEY}"

            with urllib.request.urlopen(weather_url) as res:
                data = json.loads(res.read().decode())

            condition = data['weather'][0]['main'].lower()

            # 3. Define bad weather conditions
            bad_conditions = ['rain', 'storm', 'clouds', 'thunderstorm', 'wind', 'hurricane']

            # 4. Send alert email via SNS if bad weather is detected
            if condition in bad_conditions:
                message = f"""Hi {name},

 WEATHER ALERT: {condition.upper()} is expected in your area ({location}).

Crop Type: {crop}

Please take precautions to protect your crops.

Stay Safe,
 AgriAlert Team
"""
                sns.publish(
                    TopicArn=TOPIC_ARN,
                    Subject=f" Weather Alert for {location}",
                    Message=message
                )

        return { "statusCode": 200, "body": " Weather alerts processed." }

    except Exception as e:
        return { "statusCode": 500, "body": f" Error: {str(e)}" }

IAM Policy for weather_alert Lambda:

This policy grants the Lambda function permissions to scan the Farmers DynamoDB table and publish messages to the AgriWeatherTopic SNS topic.

Name: WeatherAlertPolicy

JSON

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:Scan"
      ],
      "Resource": "arn:aws:dynamodb:ap-south-1:8257xxxxxx:table/Farmers"
    },
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:ap-south-1:8257xxxxxx:AgriWeatherTopic"
    }
  ]
}

Note: Replace 8257xxxxxx with your actual AWS Account ID.

Environment Variables for weather_alert Lambda:

These environment variables are crucial for the Lambda function to function correctly.

KeyValue
TOPIC_ARNarn:aws:sns:ap-south-1:8257xxxxxx:AgriWeatherTopic
WEATHER_API_KEY3fe7e51f5xxxxxxxxxxxxxxxxxxx495d (Replace xxxxxxxxxxxx with your actual OpenWeatherMap API Key)

Note on WEATHER_API_KEY: For production environments, it is highly recommended to store your API key securely in AWS Secrets Manager or AWS Systems Manager Parameter Store and retrieve it programmatically in your Lambda function, rather than directly in environment variables.

Steps to Create weather_alert Lambda:

  1. Navigate to Lambda Console: Open the AWS Management Console and search for “Lambda”.
  2. Create Function: Click “Create function”.
  3. Configure Function Settings:
    • Author from scratch: Selected by default.
    • Function name: Enter weather_alert.
    • Runtime: Select Python 3.12.
    • Architecture: x86_64 (default).
    • Execution role: Choose “Create a new role with basic Lambda permissions”. After creation, attach the WeatherAlertPolicy to this new role.
  4. Create function: Click “Create function”.
  5. Add Code: In the “Code” tab, replace the default code with the provided weather_alert Lambda code.
  6. Configure Environment Variables: In the “Configuration” tab, go to “Environment variables” and add the keys and values as specified above.
  7. Save: Click “Deploy” to save your changes.

4. Create SNS Topic

The SNS topic will be used to send weather alerts to subscribed farmers.

Steps to Create SNS Topic:

  1. Navigate to SNS Console: Open the AWS Management Console and search for “SNS”.
  2. Create Topic: Click on “Topics” in the left navigation pane, then click “Create topic”.
  3. Choose Type: Select Standard.
  4. Details:
    • Name: Enter AgriWeatherTopic.
    • Display name: (Optional, but good for identification) AgriWeatherAlerts.
  5. Access policy: Leave as default for now, the Lambda policies will grant the necessary permissions.
  6. Create topic: Click “Create topic”.
  7. Note ARN: After creation, note the Topic ARN. It will look something like arn:aws:sns:ap-south-1:8257xxxxxx:AgriWeatherTopic. This ARN will be used in your Lambda environment variables.

5. Create API Gateway (HTTP API)

This sets up a public endpoint for your register_farmer Lambda function, allowing your frontend to interact with it.

Steps to Create API Gateway (HTTP API):

  1. Navigate to API Gateway Console: Open the AWS Management Console and search for “API Gateway”.
  2. Create API: Click “Create API” and choose “HTTP API” by clicking “Build”.
  3. Add Integration:
    • Integrations: Click “Add integration”.
    • Integration type: Select Lambda function.
    • Lambda function: Select register_farmer.
  4. API Name: Enter AgriAlertAPI.
  5. Review and Create: Click “Next”.
  6. Configure Routes:
    • Method: POST
    • Path: /register
    • Integration target: register_farmer (should be pre-selected).
  7. Review and Create: Click “Next”.
  8. Define Stages: Leave defaults or customize if needed. Click “Next”.
  9. Enable CORS:
    • Click “Enable CORS”
    • Access-Control-Allow-Origins: *
    • Access-Control-Allow-Methods: POST
    • Access-Control-Allow-Headers: (Leave default or add more if needed, typically Content-Type is sufficient for this case).
    • Access-Control-Max-Age: (Optional)
  10. Create: Click “Create”.
  11. Note Invoke URL: After creation, go to the “Stages” section, then click on the default stage (e.g., $default). Note your Invoke URL, it will look like: https://abc123xyz.execute-api.ap-south-1.amazonaws.com/register

6. SNS Email Confirmation

Once a farmer submits the registration form via the API Gateway endpoint:

  • Data is stored in DynamoDB.
  • SNS sends a confirmation email to the farmer’s provided email address.
  • Crucially, the farmer must click the confirmation link in the email to opt-in and receive future weather alerts. This is a security feature of SNS to prevent spam.

7. Schedule Weather Alert Lambda via CloudWatch (EventBridge)

This sets up a daily trigger for your weather_alert Lambda function to automatically send out weather alerts.

Steps to Schedule with CloudWatch (EventBridge):

  1. Navigate to CloudWatch Console: Open the AWS Management Console and search for “CloudWatch”.
  2. Create Rule: In the left navigation pane, click “EventBridge” -> “Rules”, then click “Create rule”.
  3. Define Rule Details:
    • Name: DailyWeatherAlertTrigger (or similar).
    • Description: Triggers weather_alert Lambda daily at 6:30 AM.
  4. Define Event Pattern:
    • Event source: Select Schedule.
    • Schedule pattern: Choose Cron expression.
    • Cron expression: Enter cron(30 6 * * ? *)
      • This expression means “at 6:30 AM (UTC) every day”. Adjust the time based on your desired local time and UTC offset. For example, if Kolkata is UTC+5:30, and you want it at 6:30 AM IST, you’d need to adjust the UTC time accordingly. For 6:30 AM IST, the UTC time would be 1:00 AM, so cron(0 1 * * ? *).
  5. Select Targets:
    • Target: Select Lambda function.
    • Function: Select your weather_alert Lambda function from the dropdown.
  6. Configure input: Leave as default (Matched event).
  7. Create rule: Click “Create rule”.

8. Deploying the Frontend (HTML)

This frontend provides the user interface for farmers to register.

Frontend HTML Code:

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title> AgriAlert - Farmer Registration</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
  <style>
    body {
      background: linear-gradient(to right, #e0f7fa, #e8f5e9);
      font-family: 'Segoe UI', sans-serif;
      min-height: 100vh;
    }
    .card {
      border-radius: 1rem;
      box-shadow: 0 8px 16px rgba(0,0,0,0.1);
    }
    .btn-custom {
      background: linear-gradient(45deg, #4caf50, #2e7d32);
      color: white;
      border: none;
    }
    .btn-custom:hover {
      background: linear-gradient(45deg, #66bb6a, #388e3c);
    }
    .left-img {
      background-image: url('https://cdn.pixabay.com/photo/2024/10/27/07/12/women-9152739_1280.jpg');
      background-size: cover;
      background-position: center;
      border-top-left-radius: 1rem;
      border-bottom-left-radius: 1rem;
    }
    #message {
      font-weight: bold;
    }
    @media (max-width: 768px) {
      .left-img {
        display: none;
      }
    }
  </style>
</head>
<body>
  <div class="container py-5">
    <div class="row justify-content-center">
      <div class="col-lg-10">
        <div class="card">
          <div class="row g-0">

            <div class="col-md-6 left-img"></div>

            <div class="col-md-6 p-4">
              <h3 class="text-success text-center mb-3"><i class="bi bi-cloud-sun-fill"></i> AgriAlert</h3>
              <p class="text-muted text-center">Get notified about dangerous weather near your farm 🌩️</p>

              <form id="regForm">
                <div class="mb-3">
                  <label class="form-label"><i class="bi bi-person-fill"></i> Full Name</label>
                  <input class="form-control" name="name" placeholder="Your Name" required>
                </div>

                <div class="mb-3">
                  <label class="form-label"><i class="bi bi-envelope-fill"></i> Email Address</label>
                  <input class="form-control" type="email" name="email" placeholder="example@domain.com" required>
                </div>

                <div class="mb-3">
                  <label class="form-label"><i class="bi bi-geo-alt-fill"></i> Location</label>
                  <input class="form-control" name="location" placeholder="City / Area (spell correctly)" required>
                  <small class="text-danger"> Spelling must be correct for weather data</small>
                </div>

                <div class="mb-3">
                  <label class="form-label"><i class="bi bi-flower1"></i> Crop Type</label>
                  <select class="form-select" name="cropType" required>
                    <option value="">-- Select Crop --</option>
                    <option>Wheat</option>
                    <option>Paddy</option>
                    <option>Maize</option>
                    <option>Fruits</option>
                    <option>Sugarcane</option>
                    <option>Mustard</option>
                    <option>Vegetables</option>
                  </select>
                </div>

                <div class="mb-3">
                  <label class="form-label"><i class="bi bi-aspect-ratio"></i> Farm Area (acres)</label>
                  <input class="form-control" type="number" step="0.1" name="farmArea" placeholder="e.g. 2.5" required>
                </div>

                <button type="submit" class="btn btn-custom w-100"><i class="bi bi-send-check-fill"></i> Register</button>
              </form>

              <div id="message" class="text-center mt-3"></div>
            </div>

          </div>
        </div>
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    document.getElementById("regForm").onsubmit = async function (e) {
      e.preventDefault();
      const form = new FormData(e.target);
      const input = Object.fromEntries(form.entries());

      try {
        const res = await axios.post(
          "YOUR_API_GATEWAY_INVOKE_URL", // <<<<< IMPORTANT: REPLACE THIS WITH YOUR ACTUAL API GATEWAY INVOKE URL
          JSON.stringify(input),
          { headers: { "Content-Type": "application/json" } }
        );

        console.log(" Response:", res.data); // CloudWatch-friendly log
        document.getElementById("message").innerText = res.data.message || " Registered successfully!";
        document.getElementById("message").style.color = "green";
        document.getElementById("regForm").reset(); // clear form
      } catch (err) {
        console.error(" Error:", err); // CloudWatch log
        const errorMsg = err?.response?.data?.error || " Registration failed. Please try again later.";
        document.getElementById("message").innerText = errorMsg;
        document.getElementById("message").style.color = "red";
      }
    };
  </script>
</body>
</html>

Steps to Deploy the Frontend:

The easiest way to host this simple HTML file is using Amazon S3 for static website hosting.

  1. Create an S3 Bucket:
    • Navigate to the Amazon S3 console.
    • Click “Create bucket”.
    • Bucket name: Choose a unique name (e.g., agrialert-frontend-YOUR_ACCOUNT_ID).
    • AWS Region: Select ap-south-1 (or the same region as your other AWS resources).
    • Object Ownership: Select “ACLs enabled” and “Bucket owner preferred”.
    • Block Public Access settings for this bucket: Uncheck “Block all public access” to enable public access for website hosting. Acknowledge the warning.
    • Click “Create bucket”.
  2. Upload Frontend File:
    • Save the HTML code provided above into a file named index.html.
    • Before uploading, locate the line in the JavaScript section:JavaScript "https://jb7got4nh4.dfdffgfgexecute-api.ap-south-1.amazonaws.com/register", // Replace with your full HTTP API endpoint Replace "https://jb7got4nh4.dfdffgfgexecute-api.ap-south-1.amazonaws.com/register" with the actual Invoke URL of your API Gateway that you noted in Step 5.
    • In your S3 bucket, click “Upload”.
    • Drag and drop or add your index.html file.
    • Click “Upload”.
  3. Enable Static Website Hosting:
    • In your S3 bucket, go to the “Properties” tab.
    • Scroll down to “Static website hosting” and click “Edit”.
    • Select “Enable”.
    • Hosting type: “Host a static website”.
    • Index document: index.html
    • Error document: (Optional, but recommended) error.html (you can create a simple error page).
    • Click “Save changes”.
  4. Set Bucket Policy for Public Read Access:
    • In your S3 bucket, go to the “Permissions” tab.
    • Under “Bucket policy”, click “Edit”.
    • Paste the following policy, replacing your-bucket-name with your actual S3 bucket name. This allows public read access to objects in your bucket.JSON { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": [ "s3:GetObject" ], "Resource": "arn:aws:s3:::your-bucket-name/*" } ] }
    • Click “Save changes”.
  5. Access Your Frontend:
    • Go back to the “Properties” tab of your S3 bucket.
    • Scroll down to “Static website hosting” and you will find your “Bucket website endpoint”. This is the URL where your frontend is now hosted. Open this URL in your web browser.

Security Best Practices

  • API Keys: As mentioned, avoid hardcoding WEATHER_API_KEY directly in your Lambda environment variables in a production setup. Use AWS Secrets Manager or AWS Systems Manager Parameter Store to store these sensitive credentials and retrieve them at runtime.
    • AWS Secrets Manager: Ideal for database credentials, API keys, etc., as it supports automatic rotation.
    • AWS Systems Manager Parameter Store: Good for configuration data and less frequently rotated secrets.
  • IAM Least Privilege: Ensure your Lambda execution roles have only the necessary permissions (PutItem for register_farmerScan for weather_alertSubscribe for SNS, Publish for SNS). Do not grant * permissions unless absolutely required and justified.
  • CORS: While Access-Control-Allow-Origin: * is used for demonstration, in a production environment, restrict this to your specific frontend domain(s) for enhanced security.
  • Error Handling: The provided Lambda functions include basic try-except blocks. For a production system, implement more granular error handling and logging (e.g., using AWS CloudWatch Logs, AWS X-Ray for tracing) to quickly identify and resolve issues.
  • Input Validation: Implement robust input validation in your register_farmer Lambda to prevent malicious or malformed data from being stored in your DynamoDB table.
  • SNS Confirmation: The SNS email confirmation step is crucial for preventing unwanted subscriptions. Ensure users understand they need to confirm their email.
  • Regular Security Audits: Periodically review your AWS configurations, IAM policies, and Lambda code for potential security vulnerabilities.
e4772453 9967 43a3 98c9 1375e27c2175
e7128776 9548 4455 bd18 4121b498039b
10730b04 d1de 46d8 9e30 20b41b0a5b25
720d0c63 6aba 4928 b3d8 731809edd1a2 (1)

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *